一、架构的演变

1.1 前后端分离

image-20220326224659391

  • 优点: 将对静态资源的访问和对接口的访问分离,Tomcat只负责数据的访问,可以支持更多的并发访问

1.2 集群

image-20220326225502758

  • 负载均衡:将对集群的并发请求按需分配到不同的服务器节点上

1.3 分布式

  • 基于redis实现分布式锁

  • 分布式数据库mycat

  • redis集群

  • 数据库中间件 ElasticSearch

  • 消息中间件 RabbitMQ

#

1.4 微服务

  • 原理:将原来一个应用中开发的多个模块进行拆分,单独开发和部署
  • 保证可用性和性能

二、商城项目

2.1 技术储备

  • (√)SpringBoot:进行SSM的整合
  • (√)Maven聚合工程:实现模块的复用

2.2 Maven聚合工程

2.2.1 创建父工程lymall

  • 创建一个maven工程,设置packing为pom
  • 父工程继承 spring-boot-starter-parent
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.6.5</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <groupId>com.ly</groupId>
    <artifactId>lymall</artifactId>
    <version>2.0.1</version>
    <modules>
        <module>common</module>
        <module>beans</module>
        <module>mapper</module>
        <module>service</module>
        <module>api</module>
    </modules>
    <packaging>pom</packaging>

</project>

2.2.2 创建common工程

  • 新建new module(Maven工程)
  • 修改pom.xml,设置package为jar
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>lymall</artifactId>
        <groupId>com.ly</groupId>
        <version>2.0.1</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>common</artifactId>
    <packaging>jar</packaging>

</project>

2.2.3 创建beans工程

  • 新建new module(Maven工程)
  • 修改pom.xml,设置package为jar
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>lymall</artifactId>
        <groupId>com.ly</groupId>
        <version>2.0.1</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>beans</artifactId>
    <packaging>jar</packaging>

</project>

2.2.4 创建mapper工程

  • 新建new module(Maven工程)
  • 修改pom.xml,设置package为jar
  • 在pom中依赖beans
<dependencies>
    <dependency>
        <groupId>com.ly</groupId>
        <artifactId>beans</artifactId>
        <version>2.0.1</version>
    </dependency>
</dependencies>

2.2.5 创建service工程

  • 新建new module(Maven工程)
  • 修改pom.xml,设置package为jar
  • 在pom中依赖mapper、common
<dependencies>
    <dependency>
        <groupId>com.ly</groupId>
        <artifactId>common</artifactId>
        <version>2.0.1</version>
    </dependency>
    <dependency>
        <groupId>com.ly</groupId>
        <artifactId>mapper</artifactId>
        <version>2.0.1</version>
    </dependency>
</dependencies>

2.2.6 创建api工程

  • 新建new module(Spring Boot工程)
  • 修改pom.xml,继承lymall
    <parent>
        <artifactId>lymall</artifactId>
        <groupId>com.ly</groupId>
        <version>2.0.1</version>
    </parent>
  • 删除自己的groupId和version

  • 将spring boot的依赖定义到父工程中

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>
  • 在父工程的modules中添加api
  • 在api中依赖service

    
        com.ly
        service
        2.0.1
    

image-20220327112618548

2.3 依赖继承分析

如果将依赖添加到父工程的pom中,根据依赖的继承关系,所有的子工程都会继承父工程的依赖:

  • 好处:当多个子工程都需要此种依赖时,无需重复引入
  • 缺点:如果子工程不需要这个依赖,根据继承关系,将会被强制继承

2.4 依赖配置

2.4.1 common子工程

  • lombok

2.4.2 beans子工程

  • Lombok

2.4.3 mapper子工程——MyBatis整合

  • 在pom中新增Mybatis依赖
 <!--mysql驱动-->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.27</version>
</dependency>

<!--springboot-starter-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
    <version>2.6.3</version>
</dependency>

<!--mybatis-starter-->
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.2.0</version>
</dependency>
  • 在mapper工程下创建application.yml
 spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/vueblog?useUnicode=true&useSSL=false&characterEncoding=utf8&serverTimezone=Asia/Shanghai
    username: root
    password: 123456
mybatis:
  mapper-locations: classpath:mappers/*Mapper.xml
  type-aliases-package: com.ly.lymall.entity
  • 在api工程中的启动类通过 @MapperScan 声明dao包的路径
package com.ly;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@MapperScan("com.ly.lymall.dao")
public class ApiApplication {

    public static void main(String[] args) {
        SpringApplication.run(ApiApplication.class, args);
    }

}

2.5 基于Springboot单元测试

2.5.1 引入依赖

 <dependency>
     <groupId>junit</groupId>
     <artifactId>junit</artifactId>
     <scope>test</scope>
</dependency>

2.5.2 单元测试

package com.ly.lymall.dao;

import com.ly.ApiApplication;
import com.ly.lymall.entity.User;
import org.junit.jupiter.api.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import javax.annotation.Resource;

/**
 * @author ly
 * @create 2022-03-27 12:22
 */

@RunWith(SpringRunner.class)
@SpringBootTest(classes = ApiApplication.class)
public class UserDaoTest {

    @Resource
    private UserDao userDAO;

    @Test
    public void queryUserByName() {
        User user = userDAO.queryUserByName("Lucy");
        System.out.println(user);
    }
}

2.6 整合Druid

2.6.1 添加依赖

  • mapper工程中添加Druid的springboot-starter
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>1.2.6</version>
</dependency>

2.6.2 配置Druid

  • 修改数据源配置application.yml
spring:
  datasource:
    druid:
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://localhost:3306/vueblog?useUnicode=true&useSSL=false&characterEncoding=utf8&serverTimezone=Asia/Shanghai
      username: root
      password: 123456
mybatis:
  mapper-locations: classpath:mappers/*Mapper.xml
  type-aliases-package: com.ly.lymall.entity

2.7 易错难点

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <configuration>
                <excludes>
                    <exclude>
                        <groupId>org.projectlombok</groupId>
                        <artifactId>lombok</artifactId>
                    </exclude>
                </excludes>
            </configuration>
        </plugin>
    </plugins>
</build>
  • 该配置不应该被放置于parent工程中,这会导致子工程打包出现错误
  • 这个插件的 repackage 目标会处理 jar 包,导致依赖它的模块无法使用它。在 parent 项目中使用它会导致每个子项目都执行了该目标,进而出现编译失败。

三、商城数据库设计

3.1 数据库设计流程

  • 根据项目功能分析数据实体(数据实体,就是应用系统中要存储的数据对象)
    • 商品、订单、购物车、用户、评价、地址…
  • 提取数据实体的数据项(数据对象的属性)
    • 商品(商品id、商品名称、商品描述,特征)
    • 地址(姓名、地址、电话…)
  • 使用数据库设计三范式检查数据项是否合理
  • 分析实体关系:E-R图
  • 数据库建模(三线图)、建模工具
  • 建库建表-SQL

3.2 数据库设计分析

3.2.1 利用PDMan数据库建模

  • 可视化创建数据表(数据表)
  • 视图显示数据表之间的关系(关系图)
  • 导出SQL指令(导出DDL脚本)
  • 记录数据设计的版本(数据库模型版本的管理)
  • 同步数据库模型到数据库(建立数据库链接即可)

3.2.2 SKU和SPU

  • SPU,是 standard product unit,标准化产品单元,是商品信息聚合的最小单位,属性值、特性相同的商品就可以成为一个SPU。

  • SKU,全称 stock keeping unit,库存量单位。SKU 是用来定价和管理库存的。同款不同尺码不同色都是独立的 SKU,需要有独立的条形码和库存管理

四、商城业务流程设计

在企业项目开发中,当完成项目的需求分析、功能分析、数据库分析与设计之后,项目组按照项目中的功能进行开发任务的分配

编程接口,简称API(Application Programming Interface),就是软件系统不同组成部分衔接的约定

4.1 登录业务

image-20220327170316247

4.1.1 用户登录(示例)

@GetMapping("/login")
public ResultVO login(@RequestParam("username") String name,
                      @RequestParam(value = "password",defaultValue = "111111") String pwd){
    return userService.checkLogin(name,pwd);
}
key 类型 是否必须 说明
username String 用户登录的账号
password String 用户登录的密码,有默认值
  • 响应结果:
key 响应 备注
code 响应状态码 1000 表示成功,1001表示失败
msg 响应提示信息 提示信息
data 响应数据 登录成功响应user信息,失败响应null

4.2 Swagger

前后端分离开发,后端需要编写接口说明文档,会耗费比较多的时间
swagger是一个用于生成服务器接口的规范性文档、并且能够对接口进行测试的工具

4.2.1 作用

  • 生成接口说明文档

  • 对接口进行测试

4.2.2 整合

  • 添加依赖(Swagger2\Swagger ul)
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger2</artifactId>
    <version>2.9.2</version>
</dependency>

<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger-ui</artifactId>
    <version>2.9.2</version>
</dependency>
  • 在工程中创建SwaggerConfig
package com.ly.lymall.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

/**
 * @author ly
 * @create 2022-03-27 18:44
 */

@Configuration
@EnableSwagger2
public class SwaggerConfig {

    /*Docket 封装接口文档信息*/
    @Bean
    public Docket getDocket(){

        ApiInfoBuilder builder = new ApiInfoBuilder();
        builder.title("《小一商城》后端接口文档")
                .description("此文档详细说明了api接口规范")
                .version("v 2.0.1")
                .contact( new Contact("小李","www.lycode.com","2396446252@qq.com"));
        ApiInfo apiInfo = builder.build();
        Docket docket = new Docket(DocumentationType.SWAGGER_2)     //指定文档风格
                        .apiInfo(apiInfo)       //指定生成的文档中的封面信息:文档标题、版本、作者
                        .select()
                        .apis(RequestHandlerSelectors.basePackage("com.ly.lymall.controller"))  //指定响应控制器生成接口文档
                        .paths(PathSelectors.any())
                        .build();
        return docket;
    }

}
  • 解决整合Swagger2 启动类报错
在启动类上添加 @EnableWebMvc 注解
  • 解决Swagger2 404问题
package com.ly.lymall.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.util.List;

/**
 * @author ly
 * @create 2022-03-27 19:47
 */
@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/statics/**").addResourceLocations("classpath:/statics/");
        // 解决 SWAGGER 404报错
        registry.addResourceHandler("/swagger-ui.html").addResourceLocations("classpath:/META-INF/resources/");
        registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
    }

    @Override
    public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {

    }
}

4.2.3 Swagger中的实用注解

  • @Api
  • @ApiOperation
  • @ApiImplicitParams 、 @ApiImplicitParam
@Api(value = "提供用户的登录和注册接口",tags = "用户管理")

@ApiOperation("用户登录接口")
@ApiImplicitParams({
    @ApiImplicitParam(dataType = "String",name = "username",value = "用户登录账号",required = true),
    @ApiImplicitParam(dataType = "String",name = "password",value = "用户登录密码",required = false,defaultValue = "111111")
})

效果图:

image-20220327200600413

  • @ApiModel、@ApiModelProperty 为接口参数对象添加注释

  • @ApiIgnore 添加此注解的方法将不能显示在swagger页面上

4.2.4 swagger-ui 插件

  • 导入插件依赖
<dependency>
    <groupId>com.github.xiaoymin</groupId>
    <artifactId>swagger-bootstrap-ui</artifactId>
    <version>1.9.6</version>
</dependency>
  • 文档访问

http://ip:port/doc.html

  • 被404 拦截——在WebConfig配置
// 解决 doc 404报错
registry.addResourceHandler("doc.html").addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");

4.3 RESTful规范

前后端分离开发的项目中,前后端之间是接口进行请求和响应,后端向前端提供请求时就要对外暴露一个URL;URL的设计不能是随意的,需要遵从一定的设计规范——RESTful

RESTful是一种Web api的标准,也就是一种url设计风格/规范

  • 每个URL请求路径代表服务器上的唯一资源
传统的URL设计:
    http://localhost:8080/goods/delete?goodsId=1 商品1
    http://localhost:8080/goods/delete7goodsId=2 商品2

RESTful设计:
    http://localhost:8088/goods/delete/1 商品1
    http://localhost:8080/goods/delete/2 商品2

@RequestMapping("/delete/{gid}")
public ResultVO deleteGoods(@PathVariable("gid") int goodsId){
    return new ResultVO(10800,"delete success",null);
}
  • 使用不同的请求方式表示不同的操作

    • post (添加)
    • put (修改)
    • get (查询)
    • delete(删除)
    • option(预检)
  • 接口响应的资源的表现形式采用JSON(或者XML)

    • 在控制类或者每个接口方法添加@ResponseBody 注解将返回的对象格式为json
    • 或者直接在控制器类使用@RestController 注解声明控制器
  • 前端(Android\ios\pc)通过无状态的HTTP协议与后端接口进行交互

五、商城设计及实现——用户管理

image-20220327205305415

创建Mapper、entity、mapperxml

  • 创建实体类
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
    private int userId;
    private String username;
    private String password;
    private String nickname;
    private String realname;
    private String userImg;
    private String userMobile;
    private String userEmail;
    private String userSex;
    private Date userBirth;
    private Date userRegtime;
    private Date userModtime;
}
  • 创建DAO接口、定义操作方法
public interface UserDao {
    //用户注册
    public int insertUser(User user);

    //根据用户名查询
    public User queryUserByName(String name);

}
  • 创建DAO接口的mapper文件并完成配置
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.ly.lymall.dao.UserDao">
    
   <insert id="insertUser">
        insert into Users(username,password,user_regtime,user_modtime)
        values(#{username},#{password},#{user_regtime},#{user_modtime})
   </insert>
    
    <resultMap id="userMap" type="User">
        <id column="user_id" property="userId"/>
        <result column="username" property="username"/>
        <result column="password" property="password"/>
        <result column="nickname" property="nickname"/>
        <result column="realname" property="realname"/>
        <result column="user_img" property="userImg"/>
        <result column="user_mobile" property="userMobile"/>
        <result column="user_email" property="userEmail"/>
        <result column="user_sex" property="userSex"/>
        <result column="user_birth" property="userBirth"/>
        <result column="user_regtime" property="userRegtime"/>
        <result column="user_modtime" property="userModtime"/>

    </resultMap>
    
    <select id="queryUserByName">
        select user_id,username,password,nickname,realname,user_img,user_mobile,user_email,user_sex,user_birth,user_regtime,user_modtime
            from users
            where username=#{name}
    </select>
</mapper>

整合tkMapper——多表生成

Service服务

  • 创建service接口
public interface UserService {
    //用户登录
    public ResultVO checkLogin(String name, String pwd);
    //用户注册
    public ResultVO UserResgit(String name, String pwd);
}
  • 实现service功能
package com.ly.lymall.service.impl;

import com.ly.lymall.dao.UserDao;
import com.ly.lymall.entity.User;
import com.ly.lymall.service.UserService;
import com.ly.lymall.utils.MD5Utils;
import com.ly.lymall.vo.ResultVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Date;

/**
 * @author ly
 * @create 2022-03-27 14:52
 */
@Service
@Scope("singleton")
public class UserServiceImpl implements UserService {

    @Autowired
    private UserDao userDao;

    @Override
    public ResultVO checkLogin(String name, String pwd) {
        //根据账号查询用户信息
        User user = userDao.queryUserByName(name);

        //判断
        if (user == null) {
            //用户名不存在
            return new ResultVO(1,"用户名不存在",null);

        }else {
            //对输入的pwd进行加密:md5
            String md5Pwd = MD5Utils.md5(pwd);
            //使用加密后的密码 和 user中的密码进行匹配
            if (md5Pwd.equals(user.getPassword())){
                //验证成功
                return new ResultVO(1000,"登录成功",user);
            }else {
                //密码错误
                return new ResultVO(1002,"密码错误",null);
            }
        }
    }

    @Transactional
    public ResultVO UserResgit(String name, String pwd) {
        synchronized (this){    //线程锁,注册这个过程不能同时进行,会产生冲突
            //1.根据用户名查找,这个用户有没有被注册
            User user = userDao.queryUserByName(name);

            //2.如果没有被注册则进行保存操作
            if (user == null) {
                String md5Pwd = MD5Utils.md5(pwd);
                user = new User();
                user.setUsername(name);
                user.setPassword(md5Pwd);
                user.setUserModtime(new Date());
                user.setUserRegtime(new Date());
                int i = userDao.insertUser(user);
                if (i>0) {
                    return new ResultVO(10000,"注册成功!",null);
                }else {
                    return new ResultVO(10002,"注册失败!",null);
                }
            }else {
                return new ResultVO(10001,"用户名已经被注册",null);
            }
        }
    }
}

contoller视图层

  • 创建controller
package com.ly.lymall.controller;

import com.ly.lymall.service.UserService;
import com.ly.lymall.vo.ResultVO;
import io.swagger.annotations.*;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;

/**
 * @author ly
 * @create 2022-03-27 15:11
 */
@RestController
@RequestMapping("/user")
@Api(value = "提供用户的登录和注册接口",tags = "用户管理")
public class UserController {

    @Resource
    private UserService userService;

    @ApiOperation("用户登录接口")
    @ApiImplicitParams({
            @ApiImplicitParam(dataType = "String",name = "username",value = "用户登录账号",required = true),
            @ApiImplicitParam(dataType = "String",name = "password",value = "用户登录密码",required = true)
    })
    @GetMapping("/login")
    public ResultVO login(@RequestParam("username") String name,
                          @RequestParam(value = "password") String pwd){
        return userService.checkLogin(name,pwd);
    }

    @ApiOperation("用户注册接口")
    @ApiImplicitParams({
            @ApiImplicitParam(dataType = "String",name = "username",value = "用户注册账号",required = true),
            @ApiImplicitParam(dataType = "String",name = "password",value = "用户注册密码",required = true)
    })
    @PostMapping("/regist")
    public ResultVO regist(String username,String password){
       return userService.UserResgit(username,password);
    }
}

接口测试

  • 基于swagger2进行测试

六、前端功能实现

6.1 跨域问题

  • 后端使用@CrossOrigin —— 设置响应头允许跨域访问
  • 前端使用JSONP设置

6.2 前端页面间传值

  • cookie
  • localStorage

七、前后端分离开发——用户认证

7.1 单体开发

session校验

image-20220408081930311

在单体项目中,视图资源(页面)和接口(控制器)都在同一台服务器,用户的多次请求都是基于同一个会话(session),因此可以借助session来进行用户认证判断:

1.当用户登录成功之后,将用户信息存放到session

2.当用户再次访问受限资源时,验证session中是否存在用户信息,可以根据session有无用户信息来判断用户
是否登录

7.2 前后端开发

image-20220408082213671

7.2.1 token校验

  • 后端
package com.ly.lymall.utils;

import java.util.Base64;

//base64 加密 解密 激活邮件的时候 为 邮箱地址 code验证码 进行加密
//当 回传回来后 进行邮箱地址 和 code 的解密
public class Base64Utils {
    //加密
    public static String encode(String msg){
        return Base64.getEncoder().encodeToString(msg.getBytes());
    }
    //解密
    public static String decode(String msg){
        return new String(Base64.getDecoder().decode(msg));
    }
}
package com.ly.lymall.controller;

import com.ly.lymall.utils.Base64Utils;
import com.ly.lymall.vo.ResStauts;
import com.ly.lymall.vo.ResultVO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/shopcar")
@CrossOrigin
@Api(value = "提供购物车功能呢接口",tags = "购物车管理")
public class ShopcarController {

    @GetMapping("/list")
    @ApiImplicitParam(dataType = "string",name = "token",value = "校验令牌",required = true)
    public ResultVO listcar(String token){
        //获取token,校验token
        if (token == null){
            return new ResultVO(ResStauts.NO,"请先登录!",null);
        }else {
            String decode = Base64Utils.decode(token);
            if (decode.endsWith("Ly666")){
                //token校验成功
                return new ResultVO(ResStauts.OK,"success",null);
            }else {
                return new ResultVO(ResStauts.NO,"token不合法或已过期!",null);
            }
        }
    }
}
 //如果登录验证成功,则需要生成令牌token(token就是按照特定规则生成的字符串)
String token = Base64Utils.encode(name+"Ly666");

//验证成功
return new ResultVO(ResStauts.OK,token,users.get(0));
  • 前端
<script type="text/javascript">
    var baseUrl = "http://localhost:8080/"

    var vm = new Vue({
        el: '#container',
        data: {
            token: "",
        },
        created(){
            //进入页面时,需要查询购物车列表(访问购物车列表接口)
            this.token = getCookieValue("token");
            console.log("token:"+this.token);
            axios({
                method: "get",
                url: baseUrl + "shopcar/list",
                params: {
                    token: this.token
                }
            }).then((res) => {
                console.log(res)
            })
        },
    })
</script>

7.2.2 JWT校验

如果按照上述规则生成token:
1.简易的token生成规则安全性较差,如果要生成安全性很高的token对加密算法要求较高;

2.无法完成时效性的校验(登录过期)

  • JWT: Json Web Token

image-20220408100320673

  • 依赖
<dependency>
    <groupId>com.auth0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>3.18.3</version>
</dependency>

<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>
  • 生成token
 //如果登录验证成功,则需要生成令牌token(token就是按照特定规则生成的字符串)
//使用jwt
JwtBuilder builder = Jwts.builder();

HashMap<String,Object> map = new HashMap<>();


String token = builder.setSubject(name)                 //主题:token中携带的数据
    .setIssuedAt(new Date())                        //创建时间
    .setId(users.get(0).getUserId() + "")           //用户id为token的id
    .setClaims(map)                                     //map中存放用户的角信息
    .setExpiration(new Date(System.currentTimeMillis() + 24 * 60 * 60 * 1000))  //设置社会过期时间
    .signWith(SignatureAlgorithm.HS256, "Ly666")     //设置加密方式和密码
    .compact();

7.2.3 拦截器

image-20220408122913803

  • 配置拦截器
import com.ly.lymall.interceptor.CheckLoginInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class InterceptorConfig implements WebMvcConfigurer {

    @Autowired
    private CheckLoginInterceptor checkLoginInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(checkLoginInterceptor)
                .addPathPatterns("/shopcar/**")
                .addPathPatterns("/orders/**")
                .excludePathPatterns("/user/**");
    }
}
  • 拦截器实现
package com.ly.lymall.interceptor;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.ly.lymall.vo.ResStauts;
import com.ly.lymall.vo.ResultVO;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.JwtParser;
import io.jsonwebtoken.Jwts;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * @author ly
 * @create 2022-04-08 12:42
 */
@Component
public class CheckLoginInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        String token = request.getParameter("token");
        if (token == null){
            ResultVO vo = new ResultVO(ResStauts.NO, "请先登录", null);
            //登录提示
            doResponse(response,vo);
            return false;
        }else {
            try {
                //验证token
                JwtParser parser = Jwts.parser();
                parser.setSigningKey("Ly666");
                //如果token正确(密码正确,有效期内)正常执行,否则异常
                Jws<Claims> claimsJws = parser.parseClaimsJws(token);
                return true;
            } catch (Exception e) {
                return false;
        }
        }
    }

    private void doResponse(HttpServletResponse response,ResultVO resultVO) throws IOException {
        response.setContentType("application/json");
        response.setCharacterEncoding("utf-8");
        PrintWriter out = response.getWriter();
        String s = new ObjectMapper().writeValueAsString(resultVO);
        out.print(s);
        out.flush();
        out.close();
    }
}

7.3 前端header传值

image-20220415084718060

package com.ly.lymall.interceptor;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.ly.lymall.vo.ResStauts;
import com.ly.lymall.vo.ResultVO;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.JwtParser;
import io.jsonwebtoken.Jwts;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * @author ly
 * @create 2022-04-08 12:42
 */
@Component
public class CheckLoginInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        String method = request.getMethod();
        if ("OPTIONS".equalsIgnoreCase(method)){        //前端第一次请求时option,需要放行,TCP三次握手原理
            return true;
        }

        String token = request.getHeader("token");

        System.out.println("-----------------------"+token);

        if (token == null){
            ResultVO vo = new ResultVO(ResStauts.NO, "请先登录", null);
            //登录提示
            doResponse(response,vo);
            return false;
        }else {
            try {
                //验证token
                JwtParser parser = Jwts.parser();
                parser.setSigningKey("Ly666");
                //如果token正确(密码正确,有效期内)正常执行,否则异常
                Jws<Claims> claimsJws = parser.parseClaimsJws(token);
                return true;
            } catch (Exception e) {
                ResultVO vo = new ResultVO(ResStauts.NO, "登录过期", null);
                doResponse(response,vo);
            }
            return false;
        }
    }

    private void doResponse(HttpServletResponse response,ResultVO resultVO) throws IOException {
        response.setContentType("application/json");
        response.setCharacterEncoding("utf-8");
        PrintWriter out = response.getWriter();
        String s = new ObjectMapper().writeValueAsString(resultVO);
        out.print(s);
        out.flush();
        out.close();
    }
}
  axios({
      method: "get",
      url: baseUrl + "shopcar/list",
      headers: {    //header传值
          token: this.token
      }
  })

文章作者: 寜笙
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 寜笙 !
 上一篇
2022-03-26 寜笙
下一篇 
2022-03-18 寜笙
  目录