其他分享
首页 > 其他分享> > 乐优商城

乐优商城

作者:互联网

项目解读

技术特点

从上面的数据我们不仅要看到钱,更要看到背后的技术实力。正是得益于电商行业的高强度并发压力,促使了BAT等巨头们的技术进步。电商行业有些什么特点呢?

常见电商模式

电商行业的一些常见模式:

一些专业术语

项目介绍

系统架构

Bz6Uu8.md.png

系统架构解读

整个乐优商城可以分为两部分:后台管理系统、前台门户系统。

后台系统会采用前后端分离开发,而且整个后台管理系统会使用Vue.js框架搭建出单页应用(SPA)。

前台门户

无论是前台还是后台系统,都共享相同的微服务集群,包括:

技术选型

前端:

后端技术:

为了保证开发环境的统一,希望每个人都按照我的环境来配置:

域名

我们在开发的过程中,为了保证以后的生产、测试环境统一。尽量都采用域名来访问项目。

一级域名:www.leyou.com,leyou.com leyou.cn

二级域名:manage.leyou.com/item , api.leyou.com

我们可以通过switchhost工具来修改自己的host对应的地址,只要把这些域名指向127.0.0.1,那么跟你用localhost的效果是完全一样的。

搭建后台管理前端

安装依赖

你应该注意到,这里并没有node_modules文件夹,方便给大家下发,已经把依赖都删除了。不过package.json中依然定义了我们所需的一切依赖:

1530368695265

我们只需要打开终端,进入项目目录,输入:npm install命令,即可安装这些依赖。

1530374769782.png

大概需要几分钟。

如果安装过程出现以下问题

1530374827792

建议删除node_modules目录,重新安装。或者copy其他人的node_modules使用

运行

1540706914029

在package.json文件中有scripts启动脚本配置,可以输入命令:npm run dev或者npm start

1530374954209

发现默认的端口是9001。访问:http://localhost:9001

会自动进行跳转:

1525958950616

目录结构

1525962755237

调用关系

1525964023585

理一下:

1543399927909

UI框架

Vue虽然会帮我们进行视图的渲染,但样式还是由我们自己来完成。这显然不是我们的强项,因此后端开发人员一般都喜欢使用一些现成的UI组件,拿来即用,常见的例如:

然而这些UI组件的基因天生与Vue不合,因为他们更多的是利用DOM操作,借助于jQuery实现,而不是MVVM的思想。

而目前与Vue吻合的UI框架也非常的多,国内比较知名的如:

然而我们都不用,我们今天推荐的是一款国外的框架:Vuetify

官方网站:https://vuetifyjs.com/zh-Hans/

项目页面布局

接下来我们一起看下页面布局。

Layout组件是我们的整个页面的布局组件:

一个典型的三块布局。包含左,上,中三部分:

1525965779366

里面使用了Vuetify中的2个组件和一个布局元素:

1532577155616

跨域问题

https://www.cnblogs.com/zgrey/p/13972270.html

文件上传 fastDFS

https://www.cnblogs.com/zgrey/p/13972567.html

搭建前台页面

3.2.live-server

没有webpack,我们就无法使用webpack-dev-server运行这个项目,实现热部署。

所以,这里我们使用另外一种热部署方式:live-server,

3.2.1.简介

地址;https://www.npmjs.com/package/live-server

1526460917348

这是一款带有热加载功能的小型开发服务器。用它来展示你的HTML / JavaScript / CSS,但不能用于部署最终的网站。

3.2.2.安装和运行参数

安装,使用npm命令即可,这里建议全局安装,以后任意位置可用

npm install -g live-server

运行时,直接输入命令:

live-server

另外,你可以在运行命令后,跟上一些参数以配置:

3.2.3.测试

我们进入leyou-portal目录,输入命令:

live-server --port=9002

1528480541193

3.3.域名访问

现在我们访问只能通过:http://127.0.0.1:9002

我们希望用域名访问:http://www.leyou.com

第一步,修改hosts文件,添加一行配置:

127.0.0.1 www.leyou.com

第二步,修改nginx配置,将www.leyou.com反向代理到127.0.0.1:9002

server {
    listen       80;
    server_name  www.leyou.com;

    proxy_set_header X-Forwarded-Host $host;
    proxy_set_header X-Forwarded-Server $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

    location / {
        proxy_pass http://127.0.0.1:9002;
        proxy_connect_timeout 600;
        proxy_read_timeout 600;
    }
}

重新加载nginx配置:nginx.exe -s reload

3.4.common.js

为了方便后续的开发,我们在前台系统中定义了一些工具,放在了common.js中:

1526643361038

部分代码截图:

1526643526973

首先对axios进行了一些全局配置,请求超时时间,请求的基础路径,是否允许跨域操作cookie等

定义了对象 ly ,也叫leyou,包含了下面的属性:

ElasticSearch

https://www.cnblogs.com/zgrey/p/13973528.html#autoid-1-21-0

页面静态化

2.1.简介

2.1.1.问题分析

现在,我们的页面是通过Thymeleaf模板引擎渲染后返回到客户端。在后台需要大量的数据查询,而后渲染得到HTML页面。会对数据库造成压力,并且请求的响应时间过长,并发能力不高。

大家能想到什么办法来解决这个问题?

首先我们能想到的就是缓存技术,比如之前学习过的Redis。不过Redis适合数据规模比较小的情况。假如数据量比较大,例如我们的商品详情页。每个页面如果10kb,100万商品,就是10GB空间,对内存占用比较大。此时就给缓存系统带来极大压力,如果缓存崩溃,接下来倒霉的就是数据库了。

所以缓存并不是万能的,某些场景需要其它技术来解决,比如静态化。

2.1.2.什么是静态化

静态化是指把动态生成的HTML页面变为静态内容保存,以后用户的请求到来,直接访问静态页面,不再经过服务的渲染。

而静态的HTML页面可以部署在nginx中,从而大大提高并发能力,减小tomcat压力。

2.1.3.如何实现静态化

目前,静态化页面都是通过模板引擎来生成,而后保存到nginx服务器来部署。常用的模板引擎比如:

我们之前就使用的Thymeleaf,来渲染html返回给用户。Thymeleaf除了可以把渲染结果写入Response,也可以写到本地文件,从而实现静态化。

2.2.Thymeleaf实现静态化

2.2.1.概念

先说下Thymeleaf中的几个概念:

Context

上下文: 用来保存模型数据,当模板引擎渲染时,可以从Context上下文中获取数据用于渲染。

当与SpringBoot结合使用时,我们放入Model的数据就会被处理到Context,作为模板渲染的数据使用。

TemplateResolver

模板解析器:用来读取模板相关的配置,例如:模板存放的位置信息,模板文件名称,模板文件的类型等等。

当与SpringBoot结合时,TemplateResolver已经由其创建完成,并且各种配置也都有默认值,比如模板存放位置,其默认值就是:templates。比如模板文件类型,其默认值就是html。

TemplateEngine

模板引擎:用来解析模板的引擎,需要使用到上下文、模板解析器。分别从两者中获取模板中需要的数据,模板文件。然后利用内置的语法规则解析,从而输出解析后的文件。来看下模板引擎进行处理的函数:

templateEngine.process("模板名", context, writer);

三个参数:

在输出时,我们可以指定输出的目的地,如果目的地是Response的流,那就是网络响应。如果目的地是本地文件,那就实现静态化了。

而在SpringBoot中已经自动配置了模板引擎,因此我们不需要关心这个。现在我们做静态化,就是把输出的目的地改成本地文件即可!

2.2.2.具体实现

Service代码:

@Service
public class GoodsHtmlService {

    @Autowired
    private GoodsService goodsService;

    @Autowired
    private TemplateEngine templateEngine;

    private static final Logger LOGGER = LoggerFactory.getLogger(GoodsHtmlService.class);

    /**
     * 创建html页面
     *
     * @param spuId
     * @throws Exception
     */
    public void createHtml(Long spuId) {

        PrintWriter writer = null;
        try {
            // 获取页面数据
            Map<String, Object> spuMap = this.goodsService.loadModel(spuId);

            // 创建thymeleaf上下文对象
            Context context = new Context();
            // 把数据放入上下文对象
            context.setVariables(spuMap);

            // 创建输出流
            File file = new File("C:\\project\\nginx-1.14.0\\html\\item\\" + spuId + ".html");
            writer = new PrintWriter(file);

            // 执行页面静态化方法
            templateEngine.process("item", context, writer);
        } catch (Exception e) {
            LOGGER.error("页面静态化出错:{},"+ e, spuId);
        } finally {
            if (writer != null) {
                writer.close();
            }
        }
    }

}

2.2.3.什么时候创建静态文件

我们编写好了创建静态文件的service,那么问题来了:什么时候去调用它呢

想想这样的场景:

假如大部分的商品都有了静态页面。那么用户的请求都会被nginx拦截下来,根本不会到达我们的leyou-goods-web服务。只有那些还没有页面的请求,才可能会到达这里。

因此,如果请求到达了这里,我们除了返回页面视图外,还应该创建一个静态页面,那么下次就不会再来麻烦我们了。

所以,我们在GoodsController中添加逻辑,去生成静态html文件:

@GetMapping("{id}.html")
public String toItemPage(@PathVariable("id")Long id, Model model){

    // 加载所需的数据
    Map<String, Object> map = this.goodsService.loadModel(id);
    // 把数据放入数据模型
    model.addAllAttributes(map);

    // 页面静态化
    this.goodsHtmlService.asyncExcute(id);

    return "item";
}

注意:生成html 的代码不能对用户请求产生影响,所以这里我们使用额外的线程进行异步创建。

2.2.4.重启测试

访问一个商品详情,然后查看nginx目录:

1532757980379

2.3.nginx代理静态页面

接下来,我们修改nginx,让它对商品请求进行监听,指向本地静态页面,如果本地没找到,才进行反向代理:

server {
    listen       80;
    server_name  www.leyou.com;

    proxy_set_header X-Forwarded-Host $host;
    proxy_set_header X-Forwarded-Server $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

    location /item {
        # 先找本地
        root html;
        if (!-f $request_filename) { #请求的文件不存在,就反向代理
            proxy_pass http://127.0.0.1:8084;
            break;
        }
    }

    location / {
        proxy_pass http://127.0.0.1:9002;
        proxy_connect_timeout 600;
        proxy_read_timeout 600;
    }
}

重启测试:

发现请求速度得到了极大提升:

1532758206086

静态代理后的访问流程

image-20201114163824157

阿里短信发送验证码

java客户端

我们通过官网提供的帮助来完成java客户端学习:

1532790118302

下载SDK工具包

1527232030334

下载完成后得到压缩包:

1532941087871

解压后目录结构:

1532941225594

它这里提供的案例代码比较老,jdk版本也比较低。

2.2.安装SDK

我们需要把api_SDK中的两个依赖装入本地maven中,进入api_sdk目录,有两个项目需要处理:

1532941276340

然后进入到项目根目录:

1532941356724

打开cmd命令行,输入命令:

mvn install -Dmaven.test.skip=true -Dgpg.skip=true

然后进入另一个项目,上面的操作执行一遍

2.3.demo

建议大家直接使用课前资料提供的demo工程:

1532942155738

导入到idea中:

1527235082318

2.3.1.填写AccessKey

1527235189991

这里要填写刚刚申请的AccessKey的id和secret

2.3.2.填写电话及短信模板

1527235312969

这里要修改3个地方:

运行main函数测试:

1527236340771

短信发送成功了:

1527236388702

效果:

1527236647275

JWT 实现无状态登录

1.无状态登录原理

1.1.什么是有状态?

有状态服务,即服务端需要记录每次会话的客户端信息,从而识别客户端身份,根据用户身份进行请求的处理,典型的设计如tomcat中的session。

例如登录:用户登录后,我们把登录者的信息保存在服务端session中,并且给用户一个cookie值,记录对应的session。然后下次请求,用户携带cookie值来,我们就能识别到对应session,从而找到用户的信息。

缺点是什么?

1.2.什么是无状态

微服务集群中的每个服务,对外提供的都是Rest风格的接口。而Rest风格的一个最重要的规范就是:服务的无状态性,即:

带来的好处是什么呢?

1.3.如何实现无状态

无状态登录的流程:

流程图:

整个登录过程中,最关键的点是什么?

token的安全性

token是识别客户端身份的唯一标示,如果加密不够严密,被人伪造那就完蛋了。

采用何种方式加密才是安全可靠的呢?

我们将采用JWT + RSA非对称加密

1.4.JWT

1.4.1.简介

JWT,全称是Json Web Token, 是JSON风格轻量级的授权和身份认证规范,可实现无状态、分布式的Web应用授权;官网:https://jwt.io

1533033734163

GitHub上jwt的java客户端:https://github.com/jwtk/jjwt

1.4.2.数据格式

JWT包含三部分数据:

生成的数据格式:token==个人证件 jwt=个人身份证

1527322512370

可以看到分为3段,每段就是上面的一部分数据

1.4.3.JWT交互流程

流程图:

1527305891424

步骤翻译:

因为JWT签发的token中已经包含了用户的身份信息,并且每次请求都会携带,这样服务的就无需保存用户信息,甚至无需去数据库查询,完全符合了Rest的无状态规范。

1.4.4.非对称加密

加密技术是对信息进行编码和解码的技术,编码是把原来可读信息(又称明文)译成代码形式(又称密文),其逆过程就是解码(解密),加密技术的要点是加密算法,加密算法可以分为三类:

RSA算法历史:

1977年,三位数学家Rivest、Shamir 和 Adleman 设计了一种算法,可以实现非对称加密。这种算法用他们三个人的名字缩写:RSA

1.5.结合Zuul的鉴权流程

我们逐步演进系统架构设计。需要注意的是:secret是签名的关键,因此一定要保密,我们放到鉴权中心保存,其它任何服务中都不能获取secret。

1.5.1.没有RSA加密时

在微服务架构中,我们可以把服务的鉴权操作放到网关中,将未通过鉴权的请求直接拦截,如图:

1527312464328

发现什么问题了?

每次鉴权都需要访问鉴权中心,系统间的网络请求频率过高,效率略差,鉴权中心的压力较大。

1.5.2.结合RSA的鉴权

直接看图:

1527313765010

测试案例

加入工具类

2020-11-18 160635

public class JwtTest {
    private static final String pubKeyPath = "G:\\leyou\\rsa.pub";

    private static final String priKeyPath = "G:\\leyou\\rsa.pri";

    private PublicKey publicKey;

    private PrivateKey privateKey;

   /* @Test
    public void testRsa() throws Exception {
        RsaUtils.generateKey(pubKeyPath, priKeyPath, "ffasjfldsf%……kdaf()");
    }*/

    @Before
    public void testGetRsa() throws Exception {
        this.publicKey = RsaUtils.getPublicKey(pubKeyPath);
        this.privateKey = RsaUtils.getPrivateKey(priKeyPath);
    }
    @Test
    public void testGetKey(){
        System.out.println("公钥"+'\t'+publicKey);
        System.out.println("密钥"+'\t'+privateKey);
    }


    @Test
    public void testGenerateToken() throws Exception {
        // 生成token
        String token = JwtUtils.generateToken(new UserInfo(20L, "jack"), privateKey, 5);
        System.out.println("token = " + token);
    }

    @Test
    public void testParseToken() throws Exception {
        String token = "eyJhbGciOiJSUzI1NiJ9.eyJpZCI6MjAsInVzZXJuYW1lIjoiamFjayIsImV4cCI6MTYwNTY4NTcwMH0.EcGuaw2YYD-sagSBsCmEdZJRmHJ-5hW67meqrZJ__gU0ejpfYJMvqAaNkQmqSpu3i0RfHKVCx0M_mKXwsZfJLlUy6uD39VKxIffVFIbYz0AomdWfH0i3EkXMQ5bR97ptOmtLP5wp6tSjFeQaPQY1GDttujrziqrQ4j1z9baPuJo";
        // 解析token
        UserInfo user = JwtUtils.getInfoFromToken(token, publicKey);
        System.out.println("id: " + user.getId());
        System.out.println("userName: " + user.getUsername());
    }
}

标签:请求,--,乐优,用户,html,商城,模板,页面
来源: https://www.cnblogs.com/zgrey/p/14059732.html