一、架构的演变
1.1 前后端分离
- 优点: 将对静态资源的访问和对接口的访问分离,Tomcat只负责数据的访问,可以支持更多的并发访问
1.2 集群
- 负载均衡:将对集群的并发请求按需分配到不同的服务器节点上
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
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 登录业务
4.1.1 用户登录(示例)
@GetMapping("/login")
public ResultVO login(@RequestParam("username") String name,
@RequestParam(value = "password",defaultValue = "111111") String pwd){
return userService.checkLogin(name,pwd);
}
- 接口说明:接收账号并进行校验,返回校验结果
- 请求URL:http://localhost:8080/user/login
- 请求方式:GET
- 请求参数:
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) {
}
}
- 访问swagger页面
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")
})
效果图:
@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>
- 文档访问
- 被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协议与后端接口进行交互
五、商城设计及实现——用户管理
创建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校验
在单体项目中,视图资源(页面)和接口(控制器)都在同一台服务器,用户的多次请求都是基于同一个会话(session),因此可以借助session来进行用户认证判断:
1.当用户登录成功之后,将用户信息存放到session
2.当用户再次访问受限资源时,验证session中是否存在用户信息,可以根据session有无用户信息来判断用户
是否登录
7.2 前后端开发
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
- 依赖
<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 拦截器
- 配置拦截器
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传值
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
}
})