《锋迷商城》学习笔记1-前后端分离
注:本文是千锋《锋迷商城》项目的学习笔记
一、分布式项目实战简介
课程大纲:https://www.processon.com/view/link/606bde8b1e08534321fd2103
1.1前言介绍
1.1.1 课程背景
1.1.2 项目功能
(1)用户管理
(2)首页功能实现
(3)商品详情
(4)购物车
(5)收货地址管理
(6)提交订单
(7)用户中心
(8)订单管理
(9)评价管理
(10)中心首页数据显示
1.1.3 课程规划
(1)Part1 前后端分离实战
- 项目介绍
- 项目搭建
- 数据库设计
PDMan:
tkMapper简介:
基于Mybatis提供了很多第三方插件,这些插件通常可以完成:通用数据库操作方法的封装(GeneralDAO),数据库逆向工程工作(根据数据表生成实体类,生成映射文件)
(1)MyBatis-plus
(2)tkMapper
tkMapper就是一个MyBatis插件。在MyBatis的基础上提供了很多工具,让开发变简单,提高开发效率。
提供了针对单表通用的数据库操作方法。
提供了逆向工程功能,根据数据表生成实体类,dao接口,映射文件。
- 接口开发
SpringSecurity:
swagger接口测试:
- 功能实现
- 部署上线
- 环境准备(云服务,基于linux系统)
- 项目部署
- 测试
(2)Part2 分布式系统设计
-
从单体到高可用
-
分布式核心技术:高并发、高可用
- 微服务架构技术:高性能
1.2项目介绍
1.2.1 项目背景
锋迷商城——电商平台
- B2C商家对客户
- C2B2C客户对商家对客户
1.2.1.1 B2C商家对客户
平台运营⽅即商品的卖家 ⼩⽶商城
商品
⽤户
1.2.1.2 C2B2C
平台运营⽅不卖商品(也可以卖)
卖家是平台的⽤户
买家也是平台⽤户
- ⽤户(店铺/卖家)
- ⽤户(买家)
- 服务
- 商品
1.2.1.3 Java
Java语⾔的应⽤领域很⼴,但主要应⽤于web领域的项⽬开发,web项⽬类型分为两类:
- 企业级开发 (供企业内部使⽤的系统:企业内部的管理系统CRM\ERP、学校的教务管理系统)——一般没有注册功能,是企业、学校、机构刚开始分配的
- 客户管理系统(CRM),其最通俗的解释——他是管理客户的,以客户为导向的系统。
- 企业资源计划(ERP)则是企业内部为主的资源控制,成本核算等系统。
- 互联⽹开发(提供给所有互联⽹⽤户使⽤的系统——⽤户量)—— 电商
1.2.2 项目功能
https://www.processon.com/view/link/606bde8b1e08534321fd2103
1.2.3 技术选型
SSM 企业开发框架——基础的开发技术
1.2.3.1 单体项目
项⽬的⻚⾯和代码都在同⼀个项⽬,项⽬开发完成之后直接部署在⼀台服务器
注:访问界面中的资源,也需要发送请求。一个tomcat大约接收的请求为(200-300)。
下面这张图,光访问登陆界面就发送了8次请求(1次访问login界面、1次js、1次css、4次imgs),那么一个人就会发8次请求,tomcat将会承受很大的压力,导致崩溃。
单体项⽬遇到的问题:⽤户对⻚⾯静态资源以及对Java代码的访问压⼒都会落在Tomcat服务器上,导致服务器崩溃,因此单体项目存在不了高并发,不能接收高的访问请求,所以不会采用单体项目。
1.2.3.2 技术清单
- 项⽬架构:前后端分离
- 前端技术:vue(只做渲染,没有通讯功能)、axios(只做通讯功能,没有渲染)、妹⼦UI(amazeUI)、layui、bootstrap
- 后端技术:[SpringBoot(Spring+SpringMVC)+MyBatis](整合ssm)、RESTful、swagger
- 服务器搭建:Linux、Nginx(前端服务不在用tomcat,因为高并发的原因,改用Nginx)
二、项目架构的演进
2.1 单体架构
- 前后端都部署在同⼀台服务器上(前后端代码都在同⼀个应⽤中)
- 缺点:对静态资源的访问压⼒也会落在Tomcat上
2.2 前后端分离
- 前后端分离:前端和后端分离开发和部署(前后端部署在不同的服务器)
- 优点:将对静态资源的访问和对接⼝(数据服务)的访问进⾏分离。Nginx处理对静态资源的访问,Tomcat服务器只负责数据服务(接口)的访问。
2.3 集群与负载均衡
假设:前端访问量占比为4;后端访问量占比为1;则当1500访问增加到3000时,只用前后端分离,后端一台tomcat服务器已经不能承受这么大的访问量。
前后端分离——》后端的集群部署
- 只用前后端分离,只能在一定程度上解决高并发问题,随着用户访问量的增大,一台服务器将会不够用,仍然会产生问题。那么,既然一台服务器不够,那就采用多台服务器集群。因此采用集群能更好的解决高并发问题。
- 优点:提供并发能⼒、可⽤性
2.4 分布式
在项目采用的集群部署之后,服务器可能够用,但当服务器都访问同一个数据库时,数据库可能因需求过大崩溃掉,或者导致数据不一致等问题。所以采用分布式数据库可以解决这个问题。
分布式,对应用服务器集群进行性操作。
- 基于redis实现 分布式锁:解决多台服务器之间数据不同步问题
- 分布式数据库mycat:数据库中间件是连接Java应用程序和数据库中间的软件。主要处理三个mysql数据库之间的负债均衡。
- redis集群
- 数据库中间件:ElasticSeart搜索引擎。服务器海量数据搜索,如果从mysql中查询效率太低。而直接从es中去查询,解决搜索较慢问题,提高搜索效率。
- 消息中间件:RabbitMQ消息队列。所以MQ就是传输异步消息,提升性能。
2.5 微服务架构
假设一个项目有4个模块,在同一个服务器下,有一个模块出现故障,则整个系统都会崩溃。而微服务,就解决这个问题,当一个模块出现问题时,不影响其他模块的使用。
- 微服务架构:将原来在⼀个应⽤中开发的多个模块进⾏拆分,单独开发和部署。
- 保证可⽤性、性能:一个模块的崩溃不会影响到其他模块的使用。
三、《锋迷商城》项⽬搭建
基于Maven的聚合⼯程完成项⽬搭建,前端采⽤vue+axios,后端使⽤SpringBoot整合
SSM
3.1 技术储备
-
(√)SpringBoot: 实现⽆配置的SSM整合
-
(√)Maven聚合⼯程:实现模块的复⽤
3.2 创建Maven聚合⼯程
3.2.1 构建⽗⼯程fmmall
-
创建⼀个maven⼯程、packing设置为 pom
-
⽗⼯程继承 spring-boot-starter-parent(可以在创建完api接口后在继承SpringBoot依赖)
<?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.7.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.zsh</groupId>
<artifactId>fmmall</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>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencyManagement>
<dependencies>
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
</dependencies>
</dependencyManagement>
</project>
3.2.2 创建common⼯程
通用类、帮助类。含有项目共同所需的部分(例如:MD5密码加密操作…)
-
选择fmmall,右键—New—Module (Maven⼯程)
-
修改common的pom.xml,设置packing=jar,让它可以被打成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>fmmall</artifactId>
<groupId>com.zsh</groupId>
<version>2.0.1</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>common</artifactId>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
</project>
3.2.3 创建beans⼯程
-
选择fmmall,右键—New—Module (Maven⼯程)
-
修改beans的pom.xml,设置packing ----- jar
3.2.4 创建mapper(dao)⼯程
实现数据库的操作,dao接口需要实体类bean
-
选择fmmall,右键—New—Module (Maven⼯程)
-
修改mapper的pom.xml,设置packing ----- jar
-
在mapper的pom.xml,依赖beans
<dependencies>
<dependency>
<groupId>com.zsh</groupId>
<artifactId>beans</artifactId>
<version>2.0.1</version>
</dependency>
</dependencies>
3.2.5 创建service⼯程
进行业务处理,可能会用到共同的部分(例如:MD5密码操作…),所以需要引入common
-
选择fmmall,右键—New—Module (Maven⼯程)
-
修改service的pom.xml,设置packing ----- jar
-
在service的pom.xml,依赖mapper、commom
<dependencies>
<dependency>
<groupId>com.zsh</groupId>
<artifactId>mapper</artifactId>
<version>2.0.1</version>
</dependency>
<dependency>
<groupId>com.zsh</groupId>
<artifactId>common</artifactId>
<version>2.0.1</version>
</dependency>
</dependencies>
3.2.6 创建api⼯程
-
选择fmmall,右键—New—Module (SpringBoot⼯程)
-
修改api的pom.xml,继承fmmall,删除⾃⼰的groupId 和 version
<parent>
<artifactId>fmmall</artifactId>
<groupId>com.zsh</groupId>
<version>2.0.1</version>
</parent>
-
将spring boot的依赖配置到⽗⼯程fmmall的pom.xml
-
在⽗⼯程fmmall的pom.xml的modules添加api
<?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.7.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.zsh</groupId>
<artifactId>fmmall</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>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</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>
</project>
- 在api中,依赖service
<dependencies>
<dependency>
<groupId>com.zsh</groupId>
<artifactId>service</artifactId>
<version>2.0.1</version>
</dependency>
</dependencies>
3.3 Maven聚合⼯程依赖分析
如果将依赖添加到⽗⼯程的pom中,根据依赖的继承关系,所有的⼦⼯程中都会继承⽗⼯程的依赖:
好处:当有多个⼦⼯程都需要相同的依赖时,⽆需在⼦⼯程中重复添加依赖
缺点:如果某些⼦⼯程不需要这个依赖,还是会被强⾏继承
如果在⽗⼯程中没有添加统⼀依赖,则每个⼦⼯程所需的依赖需要在⼦⼯程的pom中⾃⾏添加
如果存在多个⼦⼯程需要添加相同的依赖,则需在⽗⼯程pom进⾏依赖版本的管理()
- 依赖配置说明
- 在⽗⼯程的pom⽂件中⼀次性添加各个⼦⼯程所需的所有依赖
- 在各个⼦⼯程中单独添加当前⼦⼯程需要的依赖
3.4 整合MyBatis
3.4.1 common⼦⼯程
- lombok
3.4.2 beans⼦⼯程
- lombok
3.4.3 MyBatis整合
- 在mapper⼦⼯程的pom⽂件,新增mybatis所需的依赖
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<!--spring-boot-starter-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>2.4.4</version>
</dependency>
<!--mybatis starter-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.4</version>
</dependency>
- 在mapper⼦⼯程的 resources ⽬录创建 application.yml
- 虽然mapper子工程是创建的maven项目,但是我们引入了spring-boot-starter的依赖,所以可以看做springboot项目,只是没有springboot的启动类。即可以创建yml文件。
- type-aliases-package: com.qfedu.fmmall.entity:因为实体类要在beans工程中创建,所以想要在mapper工程的yml文件配置,就要保持该路径与beans工程中的路径一致。
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/db_2010_mybatis?
characterEncoding=utf-8
username: root
password: admin123
mybatis:
mapper-locations: classpath:mappers/*Mapper.xml
type-aliases-package: com.qfedu.fmmall.entity
- 在api⼦⼯程的启动类通过 @MpperScan 声明dao包的路径
@SpringBootApplication
@MapperScan("com.qfedu.fmmall.dao")
public class ApiApplication {
public static void main(String[] args) {
SpringApplication.run(ApiApplication.class, args);
}
}
3.4.4 各个子工程的包的命名规范
- 因为api子工程为权限最高的工程,拥有启动类。所以,在启动时,要想扫描到其他工程中的类,则命名规范要一致。
- ApiApplication启动类,只能扫描到同级或低级目录。
- 如下图所示,启动类在com.qfedu下,则只能扫描到com.qfedu目录或com.qfedu.*目录下的文件。
3.5 基于SpringBoot的单元测试
https://blog.csdn.net/huangjinjin520/article/details/122444795
3.5.1 添加依赖
<!--test starter-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
3.5.2 测试类
@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);//单元测试输出到控制台显示
}
}
3.6 整合Druid
3.6.1 添加依赖
- 在mapper⼦⼯程添加druid-starter
<!--druid starter-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.22</version>
</dependency>
3.6.2 修改数据源配置
- 修改mapper⼦⼯程application.yml⽂件
sspring:
datasource:
druid:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8&useSSL=true&serverTimezone=UTC
username: root
password: 0413
mybatis:
mapper-locations: classpath:mappers/*Mapper.xml
type-aliases-package: com.zsh.fmmall.entity
3.6.3 测试druid是否整合成功
3.7 项目整合测试——开发流程(登录测试)
3.7.1 beans/entity(实体/持久化)层代码编写
3.7.2 mapper/dao层代码编写
(1)userDao.java
(2)UserMapper.xml
<?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.zsh.fmmall.dao.userDao">
<!--namespace根据自己需要创建的的mapper的路径和名称填写-->
<!-- MyBatis自动ORM失效:在查询操作时,因实体类属性名与表的列名不一致,导致失效-->
<!-- MyBatis只能自动维护库表”列名“与”属性名“相同时的一一对应关系,二者不同时,无法自动ORM(对象关系映射)。-->
<!-- 所以使用resultMap作为ORM映射依据、结果映射-->
<resultMap id="userMap" type="User">
<!--关联主键与列名-->
<id column="id" property="userId"/>
<!--关联属性与列名-->
<result column="username" property="userName"/>
<result column="pwd" property="userPwd"/>
<result column="realname" property="realName"/>
</resultMap>
<!--使用resultMap作为ORM映射依据-->
<select id="queryUserByName" resultMap="userMap">
select id,username,pwd,realname
from ssm_user
where username=#{name}
</select>
</mapper>
(3)application.yml
spring:
datasource:
druid:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8&useSSL=true&serverTimezone=UTC
username: root
password: "0413"
mybatis:
mapper-locations: classpath:mappers/*Mapper.xml
type-aliases-package: com.zsh.fmmall.entity
3.7.3 common层代码编写
3.7.4 service层代码编写
(1)UserService.java
(2)UserServiceImpl.java
@Service
public class UserServiceImpl implements UserService {
@Resource
private userDao userdao;
@Override
public ResultVO checkLogin(String name, String pwd) {
//1、根据账号(用户名)查询用户信息
User user = userdao.queryUserByName(name);
//2、根据用户信息进行判断
if(user==null){
//用户名(账号)不存在
return new ResultVO(0,"用户名不存在",null);
}else {
//3、用户名存在,然后判断密码是否匹配
//(1)对输入的密码pwd进行MD5加密
//(2)使用加密后的密码与user查询到的密码进行匹配
if(user.getUserPwd().equals(pwd)){
return new ResultVO(1,"登陆成功",user);
}else{
return new ResultVO(0,"密码错误,登陆失败",null);
}
}
}
}
3.7.5 api/controller层代码编写
3.7.6 测试
(1)打包(父工程):因为使用的Maven聚合工程,所以先对项目打包。
(2)运行启动类,测试
3.7.7可能出现的问题
com.alibaba.druid.pool.DruidDataSource : create connection SQLException, url: jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8&useSSL=true&serverTimezone=UTC, errorCode 1045, state 28000
(1)连接数据库异常,原因是Spring中的yml文件,password密码不能识别int类型,只能识别字符串。
四、《锋迷商城》数据库设计
4.1 软件开发步骤
-
问题定义/提出问题
-
可⾏性分析(技术、成本、法律法规)
-
需求分析(需求采集、需求分析)---->甲⽅
-
概要设计
- 架构设计/系统设计(技术选型、架构模式、项⽬搭建)
- 数据库设计
- UI设计
- 业务流程设计
-
详细设计
- 实现步骤(业务流程的实现细节)
-
编码
-
根据设计好的实现步骤进⾏代码实现
-
开发过程中开发者要进⾏单元测试
-
-
测试
- 集成测试
- 功能测试(⿊盒)
- 性能测试(⽩盒)
-
交付/部署实施
4.2 数据库设计流程
-
根据项⽬功能分析数据实体(数据实体,就是应⽤系统中要存储的数据对象)
- 商品、订单、购物⻋、⽤户、评价、地址…
-
提取数据实体的数据项(数据对象的属性)
-
商品(商品id、商品名称、商品描述,特征)
-
地址(姓名、地址、电话…)
-
-
使⽤数据库设计三范式检查数据项是否合理(防止数据冗余、数据不一致问题)
-
不合理:
-
合理:
-
-
分析实体关系:E-R图
-
数据库建模(三线图)、建模⼯具
-
建库建表-SQL
4.3 数据库设计分析
4.3.1 PDMan建模⼯具使⽤
-
可视化创建数据表(数据表)
-
视图显示数据表之间的关系(关系图)
- 现在我们的项目都使用逻辑关联,而不在使用物理关联
- 现在我们的项目都使用逻辑关联,而不在使用物理关联
-
导出SQL指令(模型–导出DDL脚本)
-
记录数据设计的版本-数据库模型版本的管理(模型版本)
-
同步数据模型到数据库(开始-数据库连接)
4.3.2 分析《锋迷商城》的数据库模型
-
⽤户
-
用户信息表
-
联系人
-
登录历史表
-
-
⾸⻚
-
轮播图
-
类别展示
-
-
商品
-
商品信息表(这里属于SPU)
-
商品规格(这里属于SKU)
-
商品图片
-
商品详情
-
-
购物⻋
- 购物车信息表
-
订单 和 订单项/商品快照
-
订单信息表
-
订单项/商品快照表
-
-
评论
- 商品评价表
4.4 SPU 和 SKU
4.4.1 SPU
SPU(Standard Product Unit):标准化产品单元。是商品信息聚合的最⼩单位,是⼀组可复⽤、易检索的标准化信息的集合,该集合描述了⼀个产品的特性。
通俗点讲,属性值、特性相同的商品就可以称为⼀个SPU。
也就是产品的最小单位
产品SPU:
商品id | 名称 |
---|---|
1 | 荣耀8 |
2 | ⼩⽶10 |
4.4.2 SKU
- SKU(中⽂译为最⼩存货单位,英⽂全称为Stock Keeping Unit,简称SKU,定义为保存库存控制的最⼩可⽤单位)
- 也就是产品的套餐、属性、配置,产品的套餐不同,配置不同,价格存货数量也不同。
产品SKU:
套餐 id | 容量 | 数量 | 价格 | 商品id |
---|---|---|---|---|
101 | 8G /128G | 10 | 1800 | 1 |
102 | 4G /128G | 20 | 1500 | 1 |
103 | 8G /128G | 12 | 2999 | 2 |
104 | 12G/256G | 23 | 3999 | 2 |
4.5 建库建表
4.5.1 创建数据表
- 从PDMan导出sql,导⼊到mysql
4.5.2 准备测试数据
- ⾸⻚轮播图 index_img
- ⾸⻚类别信息 category
-
商品信息
-
sku
五、《锋迷商城》业务流程设计-接⼝规范
在企业项⽬开发中,当完成项⽬的需求分析、功能分析、数据库分析与设计之后,项⽬
组就会按照项⽬中的功能进⾏开发任务的分配
5.1 前后端分离与单体架构流程实现的区别
**单体架构:**⻚⾯和控制之间可以进⾏跳转,同步请求控制器,流程控制(页面跳转)由控制来完成
**前后端分离架构:**前端和后端分离开发和部署,前端只能通过异步向后端发送请求,后端只负责接收请求及参数、处理请求、返回处理结果,但是后端并不负责流程控制(不负责页面跳转),流程控制(页面跳转)是由前端完成
5.1.1 单体架构
- 例如:用户注册模块的单体架构
5.1.2 前后端分离架构
- 例如:用户注册模块的前后端分离架构
5.2 接⼝介绍
5.2.1 接⼝概念
狭义的理解:就是控制器中可以接受⽤户请求的某个⽅法
广义的理解:应⽤程序编程接⼝,简称API(Application Programming Interface),就是软件系统不同组成部分衔接的约定
5.2.2 接⼝规范
作为⼀个后端开发者,我们不仅要完成接⼝程序的开发,还要编写接⼝的说明⽂档——接⼝规范
5.3 Swagger
前后端分离开发,后端需要编写接⼝说明⽂档,会耗费⽐较多的时间
swagger是⼀个⽤于⽣成后端服务器接⼝的规范性⽂档、并且能够对接⼝进⾏测试的⼯具
5.3.1 作⽤
-
⽣成接⼝说明⽂档
-
对接⼝进⾏测试
5.3.2 Swagger整合
- 在api⼦⼯程添加依赖(Swagger2 \ Swagger UI)
- swagger2:接口信息的收集
<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>
- 在api⼦⼯程创建swagger的配置(Java配置⽅式)
@Configuration
@EnableSwagger2
public class SwaggerConfig {
/*swagger会帮助我们生成接口文档
* 1:配置生成的文档信息
* 2: 配置生成规则*/
/*Docket封装接口文档信息*/
@Bean
public Docket getDocket(){
//创建封面信息对象
ApiInfoBuilder apiInfoBuilder = new ApiInfoBuilder();
apiInfoBuilder.title("《锋迷商城》后端接口说明")
.description("此文档详细说明了锋迷商城项目后端接口规范....")
.version("v 2.0.1")
.contact( new Contact("赵书颢","www.zsh.com","zsh@zsh.com") );
ApiInfo apiInfo = apiInfoBuilder.build();
Docket docket = new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo) //指定生成的文档中的封面信息:文档标题、版本、作者
.select()
.apis(RequestHandlerSelectors.basePackage("com.zsh.fmmall.controller"))
.paths(PathSelectors.any())
.build();
return docket;
}
}
-
测试:
-
启动SpringBoot应⽤,访问:http://localhost:8080/swagger-ui.html
-
-
可能会出现的bug(因版本不匹配导致出错)
-
错误
-
问题分析:
因为Springfox 使用的路径匹配是基于AntPathMatcher的,而Spring Boot 2.6.X及以上版本使用的是PathPatternMatcher。
-
修复方案:
-
方案一:
-
方案二:SpringBoot版本不降级解决方案
-
-
5.3.3 Swagger注解说明
swagger提供了⼀套注解,可以对每个接⼝进⾏中文的详细说明,让文档可读性更好,更容易读懂。
@Api
类注解,在控制器类添加此注解,可以对控制器类进⾏功能说明
@Api(value = "提供商品添加、修改、删除及查询的相关接⼝",tags = "商品管理")
-
@ApiOperation
⽅法注解:说明接⼝⽅法的作⽤
-
@ApiImplicitParams
和@ApiImplicitParam
⽅法注解,说名接⼝⽅法的参数
@ApiOperation("⽤户登录接⼝")
@ApiImplicitParams({
@ApiImplicitParam(dataType = "string",name = "username", value = "⽤户登录账号",required = true),
@ApiImplicitParam(dataType = "string",name = "password", value = "⽤户登录密码",required = false,defaultValue = "111111")
})
@RequestMapping(value = "/login",method = RequestMethod.GET)
public ResultVO login(@RequestParam("username") String name,
@RequestParam(value = "password",defaultValue =
"111111") String pwd){
return userService.checkLogin(name,pwd);
}
-
@ApiModel
和@ApiModelProperty
当接⼝参数和返回值为对象类型时(我们就不对对象进行说明了),在实体类中添加注解说明(对实体属性进行说明),对返回结果进行说明-
在beans模块pom.xml中添加swagger依赖
<dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.9.2</version> </dependency>
-
@Data
@NoArgsConstructor
@AllArgsConstructor
@ApiModel(value = "User对象",description = "⽤户/买家信息")
public class User {
@ApiModelProperty(dataType = "int",required = false)
private int userId;
@ApiModelProperty(dataType = "String",required = true, value = "⽤
户注册账号")
private String userName;
@ApiModelProperty(dataType = "String",required = true, value = "⽤
户注册密码")
private String userPwd;
@ApiModelProperty(dataType = "String",required = true, value = "⽤
户真实姓名")
private String userRealname;
@ApiModelProperty(dataType = "String",required = true, value = "⽤
户头像url")
private String userImg; }
@ApiIgnore
接⼝⽅法注解,添加此注解的⽅法将不会⽣成到接⼝⽂档中
5.3.4 ** swagger-ui插件**
- 导⼊插件的依赖:该插件使swagger-ui的界面更加人性化,更有可读性
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>swagger-bootstrap-ui</artifactId>
<version>1.9.6</version>
</dependency>
- ⽂档访问
http://localhost:8080/doc.html
5.4 RESTful
前后端分离开发的项⽬中,前后端之间是接⼝进⾏请求和响应,后端向前端提供请求时就要对外暴露⼀个URL;
URL的设计不能是随意的,需要遵从⼀定的设计规范——RESTful
RESTful 是⼀种Web api的标准,也就是⼀种url设计⻛格/规范:restful通过请求方式区别操作,而不是通过路径。
- 每个URL请求路径代表服务器上的唯⼀资源
传统的URL设计:http://localhost:8080/goods/delete 这个表示url路径
http://localhost:8080/goods/delete?goodsId=1 商品1
http://localhost:8080/goods/delete?goodsId=2 商品2
RESTful设计:http://localhost:8080/goods/delete/{gid} 这个表示url路径
http://localhost:8080/goods/delete/1 商品1
http://localhost:8080/goods/delete/2 商品2
@RequestMapping("/delete/{gid}")
public ResultVO deleteGoods(@PathVariable("gid") int goodsId){
System.out.println("-----"+goodsId);
return new ResultVO(10000,"delete success",null);
}
-
使⽤不同的请求⽅式表示不同的操作
SpringMVC对RESTful⻛格提供了很好的⽀持,在我们定义⼀个接⼝的URL时,可以通过 @RequestMapping(value=“/{id}”,method=RequestMethod.GET) 形式指定请求⽅式,也可使⽤特定请求⽅式的注解设定URL
@PostMapping("/add")
@DeleteMapping("/{id}")
@PutMapping("/{id}")
@GetMapping("/{id}")
- post 添加
- get 查询
- put 修改
- delete 删除
- option (预检)
根据ID删除⼀个商品: //http://localhost:8080/goods/1 请求方式为:[delete] //@DeleteMapping("/{id}") @RequestMapping(value = "/{id}",method = RequestMethod.DELETE) public ResultVO deleteGoods(@PathVariable("id") int goodsId){ System.out.println("-----"+goodsId); return new ResultVO(10000,"delete success",null); } 根据ID查询⼀个商品: //http://localhost:8080/goods/1 请求方式为:[get] //GetMapping("/{id}") @RequestMapping(value = "/{id}",method = RequestMethod.GET) public ResultVO getGoods(@PathVariable("id") int goodsId){ return null; }
-
接⼝响应的资源(数据)的表现形式采⽤JSON(或者XML)
-
在控制类或者每个接⼝⽅法添加
@ResponseBody
注解将返回的对象格式为json -
(常用)或者直接在控制器类使⽤
@RestController
注解声明控制器返回的对象格式为json
-
-
前端(Android\ios\pc\小程序...)通过⽆状态的HTTP协议与后端接⼝进⾏交互
- 无论前端使用哪种技术,均可以与后端接口进行交互
六、《锋迷商城》设计及实现—⽤户管理
6.1 实现流程
6.2 后端接⼝开发
6.2.1 完成DAO操作
- 保存用户信息
- 根据用户名查询用户信息
1、 创建实体类
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
@ApiModel(value = "User对象",description = "⽤户/买家信息")
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;
}
2、 创建DAO接⼝、定义操作⽅法
public interface UserDAO {
//⽤户注册
public int insert(User user);
//根据⽤户名查询⽤户信息
public User query(String name);
}
3、 创建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.zsh.fmmall.dao.userDao">
<insert id="insertUser">
insert into users (username,password,user_img,user_regtime,user_modtime)
values(#{username},#{password},#{userImg},#{userRegtime},#{userModtime})
</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" resultMap="userMap">
select user_id,
username,
password,
nickname,
realname,
user_img,
user_mobile,
user_email,
user_sex,
user_birth,
user_regtime,
user_modtime
from user
where username=#{name};
</select>
</mapper>
6.2.2 完成Service业务
-
创建service接⼝
public interface UserService { //⽤户注册 public ResultVO userResgit(String name, String pwd); //⽤户登录 public ResultVO checkLogin(String name, String pwd); }
-
创建service接⼝实现类,完成业务实现
因为注册涉及数据库的操作,所以要考虑事务的管理和锁的操作
登录只查询,没有数据库的操作,所以不需要考虑事务
- 该事务的处理可以加载在service层里的某个增删改查方法中。
- 为了安全,可以添加事物处理,如果是平常的查询操作,则用事务的@Transactional(propagation=Propagation.SUPPORTS)
- 如果是增删该操作,则用事务的 @Transactional(propagation =Propagation.REQUIRED)
-
考虑到高并发的情况,在你注册用户时,可能别人也在注册,你们同一时间注册的信息可能会重复,即考虑进行事务的管理。
- @Transactional 是声明式事务管理 编程中使用的注解
- 数据库事务默认的隔离级别是可重复读:保证数据的一致性
- 可重复读,保证事务开启或第一次查询那一刻,后面所有对整个数据库所有表的读都是读那一刻的版本,当然包括重复读同一张表,也可以理解为"一致版本读"。
- 只有在整个事务都不出现错误的情况下,才会注册成功。
- 如果刚开始查询,user用户没有被注册。而在你注册的过程中,那么你即使注册到一半了(或点击注册按钮了),但有人比你提前注册成功。则事务也会回滚,你也会注册失败。
-
为了保险起见,在加事务的基础上,我们可以加锁——synchronized (this) 锁(线程锁)的理解:
-
为了保证互斥性,那么要确保所加的锁,是锁的同一个对象。
-
那么就可以使用@Scop(singleton)注解
-
在UserServiceImpl中使用该注解,表示无论new多少次,只有UserServiceImpl这一个对象。无论你有多少个线程,spring中的service对象只有这一个。保证为单例的。
- synchronized (this)中,this指的是UserServiceImpl这个类加锁,多个线程使用同一个对象,保证互斥性。
-
同样在controller中也可以使用该注解,保证controller为单例的,那么(controller中包括service)service也为单例的。
-
synchronized (this)加锁,因为spring在不配置的情况下,默认为单例的,所以这里锁this可以用,那么**@Scop(singleton)注解也可以省略**。
-
-
因为注册时要进行密码加密处理,所有把加密工具类要放到common模块中使用(加密属于业务处理,放在service中也可以)
@Service public class UserServiceImpl implements UserService { @Resource private userDao userdao; //因为用户注册涉及到了数据库的操作,所以考虑事务管理与锁的操作 @Transactional//@Transactional 是声明式事务管理 public ResultVO userResgit(String name, String pwd) { synchronized (this){//this表示UserServiceImpl这个类,对这个类添加锁的操作,保证互斥性/一致性 //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.setUserImg("img/default.png"); user.setUserRegtime(new Date());//注册时间 user.setUserModtime(new Date());//更新时间 int i = userdao.insertUser(user); if(i>0){ return new ResultVO(0,"注册成功",null); }else { return new ResultVO(1,"注册失败",null); } }else { return new ResultVO(2,"用户名已经被注册,请换个名字",null); } } } @Override public ResultVO checkLogin(String name, String pwd) { User user = userdao.queryUserByName(name); if(user==null){//用户名不存在 return new ResultVO(1,"登录失败,⽤户名不存在!",null); }else {//用户名存在,然后对密码进行匹配 String md5Pwd = MD5Utils.md5(pwd);//数据库中采用了MD5加密,即先加密在判断是否匹配 if(md5Pwd.equals(user.getPassword())){ return new ResultVO(0,"登录成功",user); }else { return new ResultVO(2,"登录失败,密码错误!",null); } } } }
- 该事务的处理可以加载在service层里的某个增删改查方法中。
-
可能会出现的问题。(idea显示Service中UserDao接口注入失败)
该错误不影响运行,只是因为使用了聚合项目,api是service的下一层(在api启动类中@MapperScan(“com.zsh.fmmall.dao”)才扫描的dao接口),导致mapper/dao模块没有扫描成功。直接运行api启动类之后,项目启动成功即能扫描成功。
可以在dao接口添加@repository注解,idea即不报错
@repository(实现dao访问):把dao接口交给spring管理
用于标注数据访问层,也可以说用于标注数据访问组件,即DAO组件.
6.2.3 完成Controller提供接⼝
-
创建controller,调⽤service
-
添加接⼝注解
@RestController
@RequestMapping("/user")
@Api(value = "提供⽤户的登录和注册接⼝",tags = "⽤户管理")
public class UserController {
@Resource
private UserService userService;
@ApiOperation("⽤户登录接⼝")
@ApiImplicitParams({
@ApiImplicitParam(dataType = "string",name = "username", value = "⽤户登录账号",required =false),
@ApiImplicitParam(dataType = "string",name = "password", value = "⽤户登录密码",required = false)
})
@GetMapping("/login")
public ResultVO login(@RequestParam("username")String name, @RequestParam("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(@RequestParam(value = "username",required = true) String name,@RequestParam(value = "password",required = true) String pwd){
return userService.userResgit(name,pwd);
}
}
6.2.4 接⼝测试
- 基于swagger进⾏测试
6.3 tkMapper——Dao层的实现规律
6.3.1 Dao层的实现规律
实体类与数据表存在对应关系,并且是有规律的——只要知道了数据表的结构,就能够⽣成实体类;
所有实体的DAO接⼝中定义的⽅法也是有规律的,不同点就是实体类型不同
对于GeneralDAO接⼝定义的数据库操作⽅法因为使⽤了泛型, ⽆需映射⽂件 ;对于UserDAO和GoodsDAO需要映射⽂件,所有DAO的相同操作的映射⽂件也是有规律可循的
由此可知,dao层许多代码因有规律可循,则可以不用手动去写,让机器自动生成,提高编写效率——tkMapper就应运而生。
6.3.2 tkMapper简介
基于MyBatis提供了很多第三⽅插件,这些插件通常可以完成数据操作⽅法的封装(GeneralDAO)、数据库逆向⼯程⼯作(根据数据表⽣成实体类、⽣成映射⽂件)
MyBatis-plus
tkMapper
tkMapper就是⼀个MyBatis插件,是在MyBatis的基础上提供了很多⼯具,让开发变得简单,提⾼开发效率。
- 提供了针对单表通⽤的数据库操作⽅法
- 逆向⼯程(根据数据表⽣成实体类、dao接⼝、映射⽂件)
6.3.3 tkMapper详细介绍
tkMapper详细介绍:tkMapper.md
6.4 整合tkMapper
6.4.1 在beans项目模块,添加tkMapper的依赖
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper-spring-boot-starter</artifactId>
<version>2.1.5</version>
</dependency>
6.4.2 修改启动类的@MapperScan 注解的包
- 修改为:
import tk.mybatis.spring.annotation.MapperScan;
6.5 添加tkMapper的逆向工程(在mapper项目模块)
逆向⼯程(就要连接数据库),根据创建好的数据表,⽣成实体类、DAO、映射⽂件
不用手动去编写,而是自动生成,提高了开发效率
因为逆向工程会自动帮我们生成(实体类、DAO、映射⽂件),所以之前我们写的(实体类、DAO、映射⽂件)都可以删除掉
6.5.1 添加逆向⼯程依赖
此依赖是⼀个mybatis的maven插件
作为插件使用,在(mapper项目模块的pom.xml)plugin中配置
<build>
<plugins>
<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.3.5</version>
<!--配置驱动依赖,给插件使用-->
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper</artifactId>
<version>4.1.5</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
6.5.2 逆向⼯程配置
-
在resources⽬录下创建generatorConfig.xml配置文件
-
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE generatorConfiguration PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN" "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd"> <generatorConfiguration> <!-- 引⼊数据库连接配置 --> <!-- <properties resource="jdbc.properties"/>--> <context id="Mysql" targetRuntime="MyBatis3Simple" defaultModelType="flat"> <property name="beginningDelimiter" value="`"/> <property name="endingDelimiter" value="`"/> <!-- 配置 GeneralDAO,该接口不能被扫描到,所以不能放在dao目录,新建个别的目录例如general --> <plugin type="tk.mybatis.mapper.generator.MapperPlugin"> <property name="mappers" value="com.zsh.fmmall.general.GeneralDAO"/> </plugin> <!-- 配置数据库连接 --> <jdbcConnection driverClass="com.mysql.jdbc.Driver" connectionURL="jdbc:mysql://localhost:3306/fmmall" userId="root" password="0413"> </jdbcConnection> <!-- 配置实体类存放路径 --> <javaModelGenerator targetPackage="com.zsh.fmmall.entity" targetProject="src/main/java"/> <!-- 配置 XML 存放路径 --> <sqlMapGenerator targetPackage="/" targetProject="src/main/resources/mappers"/> <!-- 配置 DAO 存放路径 --> <javaClientGenerator targetPackage="com.zsh.fmmall.dao" targetProject="src/main/java" type="XMLMAPPER"/> <!-- 配置需要指定⽣成的数据库和表,% 代表所有表 --> <table tableName="%"> <!-- mysql 配置 --> <!-- <generatedKey column="id" sqlStatement="Mysql" identity="true"/>--> </table> <!-- <table tableName="tb_roles">--> <!-- <!– mysql 配置 –>--> <!-- <generatedKey column="roleid" sqlStatement="Mysql" identity="true"/>--> <!-- </table>--> <!-- <table tableName="tb_permissions">--> <!-- <!– mysql 配置 –>--> <!-- <generatedKey column="perid" sqlStatement="Mysql" identity="true"/>--> <!-- </table>--> </context> </generatorConfiguration>
6.5.3将配置⽂件设置到逆向⼯程的maven插件
<configuration>
<configurationFile>
${basedir}/src/main/resources/generator/generatorConfig.xml
</configurationFile>
</configuration>
6.5.4 执⾏逆向⽣成
6.5.5 成功后,修改entity实体的位置
因自动生成,不知道你的是聚合工程,所以实体自动创建在了mapper模块中。所以我们要把实体移动到beans模块中存储。
-
移动前:
-
移动后:
6.5.6 基于Swagger开始测试,是否成功配置
-
修改UserServiceImpl接口
-
修改前:因文件发生变化导致出错
-
修改后:
@Service public class UserServiceImpl implements UserService { @Resource private UsersMapper usersMapper; //因为用户注册涉及到了数据库的操作,所以考虑事务管理与锁的操作 @Transactional//@Transactional 是声明式事务管理 public ResultVO userResgit(String name, String pwd) { synchronized (this){//this表示UserServiceImpl这个类,对这个类添加锁的操作,保证互斥性/一致性 //1.根据⽤户查询,这个⽤户是否已经被注册 Example example = new Example(Users.class); Example.Criteria criteria = example.createCriteria(); criteria.andEqualTo("username",name); List<Users> users = usersMapper.selectByExample(example); //2.如果没有被注册则进⾏保存操作,密码需要加密保存 if(users.size()==0){ String md5Pwd = MD5Utils.md5(pwd); Users user = new Users(); user.setUsername(name); user.setPassword(md5Pwd); user.setUserImg("img/default.png"); user.setUserRegtime(new Date());//注册时间 user.setUserModtime(new Date());//更新时间 int i = usersMapper.insertUseGeneratedKeys(user);//insertUseGeneratedKeys可以进行主键回填 if(i>0){ return new ResultVO(0,"注册成功",user); }else { return new ResultVO(1,"注册失败",null); } }else { return new ResultVO(2,"用户名已经被注册,请换个名字",null); } } } @Override public ResultVO checkLogin(String name, String pwd) { Example example = new Example(Users.class); Example.Criteria criteria = example.createCriteria(); criteria.andEqualTo("username",name); List<Users> users = usersMapper.selectByExample(example); if(users.size()==0){//用户名不存在 return new ResultVO(1,"登录失败,⽤户名不存在!",null); }else {//用户名存在,然后对密码进行匹配 String md5Pwd = MD5Utils.md5(pwd);//数据库中采用了MD5加密,即先加密在判断是否匹配 if(md5Pwd.equals(users.get(0).getPassword())){//因为users上边是集合,我们只有一个,所以get(0) return new ResultVO(0,"登录成功",users.get(0)); }else { return new ResultVO(2,"登录失败,密码错误!",null); } } } }
-
-
单元测试用不到就删除掉
-
运行启动类测试
-
测试结果:http://localhost:8080/doc.html
6.6 前端跨域访问
前后端分离开发,一定要解决跨域请求问题。
跨域请求发生在响应阶段。
前端向后端发送请求,在后端向前端做出响应时,因跨域请求(不在一个服务器)导致浏览器阻塞响应(浏览器保护机制,默认不允许跨域请求,认为不安全,不允许拿到结果),导致前端出错。
前端跨域访问出错问题:
6.6.1 跨域访问概念
- 什么时跨域访问?
AJAX 跨域访问是⽤户访问A⽹站时所产⽣的对B⽹站的跨域访问请求均提交到A⽹站的指定⻚⾯
跨域请求:简单来说就是发送请求方与接收请求方,协议(prorocol)、ip地址(ip)、端口号(port)三个有一个不一样,就是跨域请求。
前端向后端发送请求,但不在一个服务器。
6.6.2 如何解决跨域访问?
-
前端使⽤JSONP设置
-
后端使⽤ @CrossOrigin — 就是设置响应头允许跨域
- 在**controller控制类\接口,**设置注解 @CrossOrigin即可
-
- 在**controller控制类\接口,**设置注解 @CrossOrigin即可
-
测试:
-
在login.html页面编写ajax请求:
-
测试结果,前端跨域访问问题解成功
-
6.7 前端JS的bug调试
6.7.1 前端跳转测试代码
6.7.2 前端debugger浏览器调试
- 可以在代码中加入
debugger
,标识断点,然后打开浏览器进入调试界面。
调试按钮
- 从左至右依次含义是:
- 进入下一个断点。如果有多个断点,点击时可以直接进入下个断点,忽略断点内部所有逻辑。
- 忽略代码内部实现,直接跳到当前断点的下个方法。
- 按照代码顺序,逐行执行,进入函数内部。
- 跳出当前函数内部,执行下一步
- 不知道和3有啥区别 ?
浏览器运行截图:
6.8 前端⻚⾯之间的传值
- 为什么不能用session?
- 原因是:
- session是服务器端(cookie是前端客户端)
- 自从前后端分离之后,就不用session了
- 即使想用session,那么代表又要向后端发送一次请求。而这个session可能不知道发送了多长时间了,没法判断你要的是哪个session。
6.8.1 cookie
cookie是浏览器端的缓存文件,容量大小受浏览器产品的限制
cookie可以与后端进行交换数据,而localStorage只能提供前端之间的传值
- ⼯具⽅法封装:
var operator = "=";
function getCookieValue(keyStr){
var value=null;
var allInfo=window.document.cookie;
// console.log(name);
var arr=allInfo.split("; ");//username=zsh; userimg=img/default.png
for(var i=0;i<arr.length;i++){
var str=arr[i];//username=zsh
// console.log(str)
var k=str.split(operator)[0];//username
var v=str.split(operator)[1];//zsh
if(k==keyStr){
value=v;
break;
}
}
return value;
}
function setCookieValue(key,value){
document.cookie=key+operator+value;
}
-
A⻚⾯
setCookieValue("username",userInfo.username); setCookieValue("userimg",userInfo.userImg);
-
login.html
- 导入cookie_util.js工具方法
- 接收cookie值
-
6.8.2 localStorage
为了存储更大容量的数据
前端页面传值用localStorage传值更方便,它封装好了对象。
localStorage和cooKie一样只能存储字符串
想要传对象,只需先转化成json字符串,再转化成对象
localStorage的存储周期更长久
-
A⻚⾯
localStorage.setItem("user",JSON.stringify(userInfo));//把一个对象转成json字符串的形式
-
B⻚⾯
var jsonStr = localStorage.getItem("user");//获取到json字符串 var userInfo = eval("("+jsonStr+")");//把一个json字符串再转成对象类型 //移出localStorage键值对,解决localStorage的存储周期更长久问题 localStorage.removeItem("user");
6.9 VUE简介
6.9.1 使⽤jQuery的复杂性问题
-
使⽤jQuery进⾏前后端分离开发,既可以实现前后端交互(ajax),⼜可以完成数据渲染;
-
存在的问题:jQuery需要通过HTML标签拼接、DOM节点操作完成数据的显示,开发效率低且容易出错,渲染效率较低
-
vue 是继jQuery之后的⼜⼀优秀的前端框架:专注于前端数据的渲染——语法简单、渲染效率⾼
6.9.2 VUE详细介绍
6.10 基于vue的用户登录实现
6.10.1 前端数据校验
6.10.1.1 删除掉js操作
存在的问题:jQuery需要通过HTML标签拼接、DOM节点操作完成数据的显示,开发效率低且容易出错,渲染效率较低
vue 是继jQuery之后的⼜⼀优秀的前端框架:专注于前端数据的渲染——语法简单、渲染效率⾼
- 所以我们删除掉js数据渲染的操作,改用vue的形式
6.10.1.2 使用vue进行数据数据校验
-
引用vue的js文件
-
<script type="text/javascript" src="static/js/v2.6.10/vue.js"></script>
-
-
给账号和密码的输入框,绑定键盘输入事件,表单标签的双向绑定(获取输入的值,而不在用js获取值)。
- v-model:表单标签的双向绑定
- @keyup:在监听键盘事件时,我们经常需要检查特定的按键。Vue 允许在
v-on
或@
监听按键事件时添加按键修饰符。
-
给登录按钮绑定方法监听事件:@click
-
提示信息进行绑定标签属性
-
vue的最终测试代码
<script type="text/javascript" src="static/js/v2.6.10/vue.js"></script>
<script type="text/javascript">
var vm = new Vue({
el:"#container",
data:{
username:"",
password:"",
tips:"",
colorStyle:"color:red",
isRight:false
},
methods:{
doSubmit:function(){
if(vm.isRight){
console.log("可以提交了!!");
}else{
vm.tips="请输入正确的账号名和密码!!";
}
},
checkInfo(){
if(vm.username==""){
vm.tips="请输入账号!!";
vm.isRight=false;
}else if(vm.username.length<8||vm.username.length>20){
vm.tips="账号长度必须为8-20个字符!!";
vm.isRight=fasle;
}else{
//账号合法,校验密码
if(vm.password==""){
vm.tips="请输入密码!!";
vm.isRight=false;
}else if(vm.password.length<6||vm.password.length>16){
vm.tips="账号长度必须为6-16个字符!!";
vm.isRight=fasle;
}else{
vm.tips="";
vm.isRight=true;
}
}
}
}
});
</script>
- 结果展示
-
6.10.2 登录验证
6.10.2.1 导入axios数据通讯框架
易用、简洁且高效的http库(专注于数据通信,异步请求)
vue本身不具备通信能⼒,通常结合axios—⼀个专注于异步通信的js框架来使⽤
axios 专注于数据通信
vue 数据渲染
在线cdn:
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
6.10.2.2 运行后端启动类
因为后端已经完成,前端直接看swagger接口说明文档即可
可以获得后端接口地址、请求方式、请求参数等内容。便于前端测试。
6.10.2.3 通过axios实现前后端数据通信
6.10.2.4 完善后端返回结果
- 因返回给前端的code响应状态码,是直接在service层写的,导致可读性差,复用性差。所以我们可以把状态码定义成常量,便于修改。
- 单独放在一个类中,定义为常量。
- 修改后
6.11基于vue的用户注册前端实现
6.11.1 前端数据校验
6.11.1.1 导入vue与axios的js文件
<script type="text/javascript" src="static/js/v2.6.10/vue.min.js"></script>
<script type="text/javascript" src="static/js/v2.6.10/axios.min.js"></script>
6.11.1.2 根据注册界面,先写出vue的大体模板
用户名:username
密码:password
校验密码:repassword
提示信息:tips
提示信息的属性:colorStyle
注册按钮的鼠标点击方法:doRegist:function(){ }
Input输入框的数据校验方法:checkInfo(){ }
数据校验是否合法,合法进行注册验证:isRight
<script type="text/javascript">
// 因为服务器的路径名,因部署不同可能不一样,定义在这里方便修改
var baseUrl="http://localhost:8080/";
var vm = new Vue({
el:"#container",
data:{
user{
username:"",
password:"",
repassword:""
},
tips:"",
colorStyle:"color:red",
isRight:false
},
methods:{
doRegist:function(){
},
checkInfo(){
}
}
});
</script>
6.11.1.3 给输入框绑定标签属性和键盘事件
v-model:标签属性的双向绑定
@keyup:键盘输入事件
6.11.1.4 注册按钮绑定鼠标点击事件
6.11.1.5 添加提示信息位置
6.11.1.6 数据校验
6.11.2 注册验证
axios有两种写法
-
写法一:
-
axios({method:"post",url:url,data:vm.user}).then((res)=>{});
-
-
写法二:
-
axios.post(url,vm.user).then((res)=>{});
-
七、前后端分离⽤户认证-JWT
7.1 基于session实现单体项⽬⽤户认证
在单体项⽬中如何保证受限资源在⽤户未登录的情况下不允许访问?
受限资源:只有登录后才能访问的资源。
单体项目中,每一次session会话,都会创建一个sessionID,而这个sessionID会响应到cookie中,在从cookie中带回一个sessionID。因此单体项目中是同一个session。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZLkWkhrq-1690696066404)(D:/Typora/img/image-20220808092635026.png)]
在单体项⽬中,视图资源(⻚⾯)和接⼝(控制器)都在同⼀台服务器,⽤户的多次请求都是基于同⼀个会话(session),因此可以借助session来进⾏⽤户认证判断:
1.当⽤户登录成功之后,将⽤户信息存放到session
2.当⽤户再次访问受限资源时,验证session中是否存在⽤户信息,可以根据session有⽆⽤户信息来判断⽤户是否登录
7.2 基于token(令牌)实现前后端分离⽤户认证
由于在前后端分离项⽬开发中,前后端之间是通过异步交互完成数据访问的,请求是⽆
状态的,因此不能基于session实现⽤户的认证。
前后端分离开发,不是同一台服务器。每一次的请求都是异步请求,当这次请求完成后,表示一次session会话完成。另一次请求,表示新的异步请求(开启一个新的session会话),因此不能用session实现用户的认证。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uKGnUhj4-1690696066405)(D:/Typora/img/image-20220808092735531.png)]
7.3 基于token的⽤户认证的实现
7.3.1登录认证接⼝⽣成token
// UserController
@GetMapping("/login")
public ResultVO login(@RequestParam("username") String name,
@RequestParam("password") String pwd){
ResultVO resultVO = userService.checkLogin(name, pwd);
return resultVO; }
// UserServiceImpl
@Override
public ResultVO checkLogin(String name, String pwd) {
Example example = new Example(Users.class);
Example.Criteria criteria = example.createCriteria();
criteria.andEqualTo("username",name);
List<Users> users = usersMapper.selectByExample(example);
if(users.size()==0){//用户名不存在
return new ResultVO(BaseCode.no,"登录失败,⽤户名不存在!",null);
}else {//用户名存在,然后对密码进行匹配
String md5Pwd = MD5Utils.md5(pwd);//数据库中采用了MD5加密,即先加密在判断是否匹配
if(md5Pwd.equals(users.get(0).getPassword())){//因为users上边是集合,我们只有一个,所以get(0)
//如果登录验证成功,则需要生成令牌token(token就是按照特定规则生成的字符串)
String token = Base64Utils.encode(name +"zsh0413");
return new ResultVO(BaseCode.ok,token,users.get(0));
}else {
return new ResultVO(BaseCode.no,"登录失败,密码错误!",null);
}
}
}
7.3.2 登录⻚⾯接收到token存储到cookie
// login.html
doSubmit:function(){
if(vm.isRight){
var url = baseUrl+"user/login";
axios.get(url,{
params:{
username:vm.username,
password:vm.password
}
}).then((res)=>{
var vo = res.data;
if(vo.code == 10000){
//如果登录成功,就把token存储到cookie
setCookieValue("token",vo.msg);
window.location.href = "index.html";
}else{
vm.tips = "登录失败,账号或密码错误!";
}
});
}else{
vm.tips = "请正确输⼊帐号和密码!";
}
}
7.3.3 购物⻋⻚⾯加载时访问购物⻋列表接⼝
-
获取token
-
携带token访问接⼝
<script type="text/javascript">
var baseUrl = "http://localhost:8080/";
var vm = new Vue({
el:"#container",
data:{
token:""
},
created:function(){
//当进⼊到购物⻋⻚⾯时,就要查询购物⻋列表(访问购物⻋列表接⼝)
this.token = getCookieValue("token");
console.log("token:"+this.token);
axios({
method:"get",
url:baseUrl+"shopcart/list",
params:{
token:this.token
}
}).then(function(res){
console.log(res);
});
}
});
</script>
7.3.4在购物⻋列表接⼝校验token
@RestController
@RequestMapping("/shopCart")
@Api(value = "提供购物车业务相关接口",tags = "购物车管理")
@CrossOrigin//利用后端注解的方式解决跨域请求
public class ShopCartController {
@ApiOperation("购物车列表接⼝")
@ApiImplicitParam(dataType = "string",name = "token", value = "授权令牌",required =true)
@GetMapping("/list")
public ResultVO listCarts(String token){
//1、获取token
//2、校验token
// System.out.println("购物车列表接⼝————————————");
if(token==null){
return new ResultVO(BaseCode.no,"请先登录",null);
}else {
String decode = Base64Utils.decode(token);
if(decode.endsWith("zsh0413")){//endswith()表示判断是否以指定的字符串结尾
return new ResultVO(BaseCode.ok,"sucess测试成功",null);
}else{
return new ResultVO(BaseCode.no,"登录过期,请重新登录!!",null);
}
}
}
}
7.4 JWT
- 什么是JWT?
JWT(JSON WEB TOKEN):JSON网络令牌,JWT是一个轻便的安全跨平台传输格式,定义了一个紧凑的自包含的方式在不同实体之间安全传输信息(JSON格式)。它是在Web环境下两个实体之间传输数据的一项标准。实际上传输的就是一个字符串。广义上讲JWT是一个标准的名称;狭义上JWT指的就是用来传递的那个token字符串。
- JWT用来做什么?怎么来的?
- 由于http协议是无状态的,所以客户端每次访问都是新的请求。这样每次请求都需要验证身份,传统方式是用session+cookie来记录/传输用户信息,而JWT就是更安全方便的方式。它的特点就是简洁,紧凑和自包含,而且不占空间,传输速度快,而且有利于多端分离,接口的交互等等。
- JWT是一种Token规范,主要面向的还是登录、验证和授权方向,当然也可以用只来传递信息。一般都是存在header里,也可以存在cookie里。
- 详细介绍:
- http://t.zoukankan.com/PinkPink-p-9962650.html
如果按照上述规则⽣成token:
1.简易的token⽣成规则安全性较差,如果要⽣成安全性很⾼的token对加密算法要求较⾼;
2.⽆法完成时效性的校验(登录过期)
7.4.1 JWT简介
专门生成token令牌的一种算法,生成token的一种规则。可以完成时效性的校验。
-
JWT: Json Web Token
-
官⽹:https://jwt.io
-
jwt的结构
- 第一部分:加密信息和token的类型
- 第二部分:用户数据
- 第三部分:校验数据、安全数据
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Z9m4jDPA-1690696066406)(D:/Typora/img/image-20220808093235463.png)]
7.4.2 ⽣成JWT
-
添加依赖
- 在service层
<!--提供jwt在了java中的实现--> <dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>3.10.3</version> </dependency> <!--提供了jwt的算法实现--> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency>
-
⽣成token
//使用jwt规则生成token字符串 JwtBuilder builder = Jwts.builder(); HashMap<String, Object> map = new HashMap<>(); String token = builder.setSubject(name) //主题,就是token中携带的数据 .setIssuedAt(new Date()) //设置token的⽣成时间 .setId(users.get(0).getUserId() + "") //设置⽤户id为token id .setClaims(map) //map中可以存放⽤户的⻆⾊权限信息 .setExpiration(new Date(System.currentTimeMillis() + 24 * 60 * 60 * 1000)) //设置过期时间(1天之后:24小时、60分和秒、1000毫秒) .signWith(SignatureAlgorithm.HS256, "QIANfeng6666") //设置加密⽅式和加密密码 .compact();
7.4.3 JWT校验
- 如果token正确则正常解析,如果token不正确或者过期,则通过抛出的异常进⾏识别
try {
//验证token
JwtParser parser = Jwts.parser();
parser.setSigningKey("QIANfeng6666"); //解析token的SigningKey必须和⽣成token时设置密码⼀致
//如果token正确(密码正确,有效期内)则正常执⾏,否则抛出异常
Jws<Claims> claimsJws = parser.parseClaimsJws(token);
Claims body = claimsJws.getBody(); //获取token中⽤户数据
String subject = body.getSubject(); //获取⽣成token设置的subject
String v1 = body.get("key1", String.class); //获取⽣成token时存储的Claims的map中的值
return new ResultVO(ResStatus.OK,"success",null);
}catch (ExpiredJwtException e){
return new ResultVO(ResStatus.NO,"登录过期,请重新登录!",null);
}catch (UnsupportedJwtException e){
return new ResultVO(ResStatus.NO,"Tonken不合法,请⾃重!",null);
}catch (Exception e){
return new ResultVO(ResStatus.NO,"请重新登录!",null);
}
7.4.4 拦截器校验Token
-
创建拦截器
@Component public class CheckTokenInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token = request.getParameter("token"); if(token == null){ ResultVO resultVO = new ResultVO(ResStatus.NO, "请先登 录!", null); //提示请先登录 doResponse(response,resultVO); }else{ try { //验证token JwtParser parser = Jwts.parser(); //解析token的SigningKey必须和⽣成token时设置密码⼀致 parser.setSigningKey("QIANfeng6666"); //如果token正确(密码正确,有效期内)则正常执⾏,否则抛出异常 Jws<Claims> claimsJws =parser.parseClaimsJws(token); return true; }catch (ExpiredJwtException e){ ResultVO resultVO = new ResultVO(ResStatus.NO, "登录过期,请重新登录!", null); doResponse(response,resultVO); }catch (UnsupportedJwtException e){ ResultVO resultVO = new ResultVO(ResStatus.NO,"Token不合法,请⾃重!", null); doResponse(response,resultVO); }catch (Exception e){ ResultVO resultVO = new ResultVO(ResStatus.NO, "请先登录!", null); doResponse(response,resultVO); } } 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(); } }
-
配置拦截器:把拦截器交给spring管理
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9TtmFjbL-1690696066408)(D:/Typora/img/image-20220828103834392.png)]
@Configuration public class InterceptorConfig implements WebMvcConfigurer { @Autowired private CheckTokenInterceptor checkTokenInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(checkTokenInterceptor) .addPathPatterns("/shopCart/**") .addPathPatterns("/orders/**") .excludePathPatterns("/user/**"); } }
7.5 请求头传递token
前端但凡访问受限资源,都必须携带token发送请求;token可以通过请求⾏(params)、
请求头(header)以及请求体(data)传递(请求体一般都是post请求),但是习惯性使⽤header传递。所以用请求头传值已经成了一种规范。
- 如果使用请求头去传递token的话,那我们在拦截器中就要使用 String token = request.getHeader(“token”); 拿到请求头中的token。
7.5.1 axios通过请求头传值
axios({
method:"get",
url:baseUrl+"shopcart/list",
headers:{
token:this.token
}
}).then(function(res){
console.log(res);
});
7.5.2 在拦截器中放⾏options请求
- 浏览器的预检机制。
- 第一次请求是预检请求(method=“OPTIONS”),不会传输数据,所以token拿不到。
- 只有预检请求的返回值为true,才表明浏览器允许放行,则可以发送第二次请求。
- 第二次请求,才真正是我们自己的请求(例如get请求),正常响应,提交数据,才能拿到token的值。
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-F4kUd7TM-1690696066410)(D:/Typora/img/image-20220828105436701.png)]
@Component
public class CheckTokenInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
//放⾏options请求
String method = request.getMethod();
if("OPTIONS".equalsIgnoreCase(method)){
return true;
}
String token = request.getHeader("token");
System.out.println("-------------"+token);
if(token == null){
ResultVO resultVO = new ResultVO(BaseCode.no, "请先登录!", null);
//提示请先登录
doResponse(response,resultVO);
}else{
try {
//验证token
JwtParser parser = Jwts.parser();
//解析token的SigningKey必须和⽣成token时设置密码⼀致
parser.setSigningKey("zsh0413");
//如果token正确(密码正确,有效期内)则正常执⾏,否则抛出异常
Jws<Claims> claimsJws = parser.parseClaimsJws(token);
return true;
}catch (ExpiredJwtException e){
ResultVO resultVO = new ResultVO(BaseCode.no, "登录过期,请重新登录!", null);
doResponse(response,resultVO);
}catch (UnsupportedJwtException e){
ResultVO resultVO = new ResultVO(BaseCode.no, "Token不合法,请⾃重!", null);
doResponse(response,resultVO);
}catch (Exception e){
ResultVO resultVO = new ResultVO(BaseCode.no, "请先登录!", null);
doResponse(response,resultVO);
}
}
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();
}
}
⼋、⾸⻚—轮播图
8.1 实现首页显示用户信息与头像
8.1.1 首页显示用户信息与头像
- 没登录时:
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-obrVGpVj-1690696066413)(D:/Typora/img/image-20220828162451281.png)]
- 登录成功后:
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Etf6EG0S-1690696066414)(D:/Typora/img/image-20220828162534173.png)]
8.1.2 登录时传用户信息
- 在login界面,通过cookie传输用户名称和头像:login.html
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qBn9Qa3y-1690696066415)(D:/Typora/img/image-20220828154204478.png)]
- 在index.html界面,从cookie中拿取用户信息
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-togNGTxI-1690696066416)(D:/Typora/img/image-20220828162740159.png)]
- 在index.html界面,给标签属性绑值
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QJuAEbXo-1690696066418)(D:/Typora/img/image-20220828163057928.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TcjyL3xB-1690696066419)(D:/Typora/img/image-20220828163137075.png)]
8.2 实现流程分析
-
流程图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mwId0sJe-1690696066420)(D:/Typora/img/image-20220808093923417.png)]
-
接⼝
- 查询轮播图信息返回
8.3 完成后台接⼝开发
8.3.1 数据库操作实现
- 分析数据表结构
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lk4w5t3v-1690696066422)(D:/Typora/img/image-20220818220714592.png)]
- 添加测试数据
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Fl58EFUd-1690696066423)(D:/Typora/img/image-20220818220735903.png)]
- 编写sql语句,可以在mysql中测试sql
select img_id,
img_url,
img_bg_color,
prod_id,
category_id,
index_type,
seq,
status,
create_time,
update_time
from index_img
where status=1
order by seq
- 在Mapper接⼝(DAO)中定义操作⽅法
@Repository
public interface IndexImgMapper extends GeneralDAO<IndexImg> {
//1.查询轮播图信息: 查询status=1 且 按照seq进⾏排序
public List<IndexImg> listIndexImgs();
}
- 配置映射⽂件
<!--BaseResultMap是由逆向⼯程⽣成的-->
<select id="listIndexImgs" resultMap="BaseResultMap">
select img_id,
img_url,
img_bg_color,
prod_id,
category_id,
index_type,
seq,
status,
create_time,
update_time
from index_img
where status=1
order by seq
</select>
8.3.2 业务层实现
- IndexImgService接⼝
public interface IndexImgService {
public ResultVO listIndexImgs();
}
- IndexImgServiceImpl实现类
@Service
public class IndexImgServiceImpl implements IndexImgService {
@Autowired
private IndexImgMapper indexImgMapper;
public ResultVO listIndexImgs() {
List<IndexImg> indexImgs = indexImgMapper.listIndexImgs();
if(indexImgs.size()==0){
return new ResultVO(ResStatus.NO,"fail",null);
}else{
return new ResultVO(ResStatus.OK,"success",indexImgs);
}
}
}
8.3.3 控制层实现
- IndexController类
@RestController
@CrossOrigin//前端请求跨域访问
@RequestMapping("/index")
@Api(value = "提供⾸⻚数据显示所需的接⼝",tags = "⾸⻚管理")
public class IndexController {
@Autowired
private IndexImgService indexImgService;
@GetMapping("/indeximg")
@ApiOperation("⾸⻚轮播图接⼝")
public ResultVO listIndexImgs(){
return indexImgService.listIndexImgs();
}
}
8.3.4 swagger测试
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BV2NSdz4-1690696066425)(D:/Typora/img/image-20220828211304521.png)]
**8.4 **完成前端功能
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TTMzRXXZ-1690696066430)(D:/Typora/img/image-20220828174932178.png)]
所以为了解决这个问题:当进⼊到index.html,在进⾏⻚⾯初始化之后,就需要请求轮播图数据进⾏轮播图的显示
通过设置延迟加载,使得轮播图先添加到静态界面,随后才开始初始化动态显示效果
index.html |
---|
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-j7ICdKuV-1690696074957)(D:/Typora/img/image-20220818221542038.png)] |
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-A32x5kr1-1690696074958)(D:/Typora/img/image-20220828175755703.png)] |
九、⾸⻚-分类列表
9.1 实现流程分析
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YKqqF5DC-1690696066431)(D:/Typora/img/image-20220818221646985.png)]
-
⽅案⼀:⼀次性查询三级分类
- 优点:只需要⼀次查询,根据⼀级分类显示⼆级分类时响应速度较快
- 缺点:数据库查询效率较低,⻚⾯⾸次加载的速度也相对较慢
-
⽅案⼆:先只查询⼀级分类,⽤户点击/⿏标移动到⼀级分类,动态加载⼆级分类
- 优点:数据库查询效率提⾼,⻚⾯⾸次加载速度提⾼
- 缺点:需要多次连接数据库
9.2 接⼝开发
9.2.1 掌握sql的关联/连接查询
-
例子1:假设有3张表,c1(一级分类表)、c2(二级分类表)、c3(三级分类表)[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dSxmtVP9-1690696066432)(D:/Typora/img/image-20220829093350163.png)]
-
俩张表之间的关联查询:
-
select * from c1 inner join c2 on c2.parent_id=c1.category_id;
-
-
三张表之间的关联查询:
-
select * from c1 inner join c2 on c2.parent_id=c1.category_id inner join c3 on c3.parent_id=c2.category_id;
-
-
-
例子2:假设在一张表(category)中,存储了一级分类、二级分类、三级分类。且按照catergory_level字段进行区分分类类别。[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uiElTOAd-1690696066434)(D:/Typora/img/image-20220829094718521.png)]
-
只查询一级分类:
-
select * from category where category_level=1;
-
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qGbqPDBO-1690696066435)(D:/Typora/img/image-20220829095203180.png)]
-
-
查询一级分类下所有关联的二级分类:
-
select * from category c1 inner join category c2 on c2.parent_id=c1.category_id where c1.category_level=1;
-
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lnOORdRE-1690696066436)(D:/Typora/img/image-20220829100055121.png)]
-
-
查询一级分类下所有关联的二级分类、二级分类下关联的三级分类
-
(因为有的二级分类下没有三级分类,但我们也需要显示出来,所以用left join 可以查到左表所有的信息)
-
select * from category c1 inner join category c2 on c2.parent_id=c1.category_id left join category c3 on c3.parent_id=c2.category_id where c1.category_level=1;
-
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9VBGkBMq-1690696066437)(D:/Typora/img/image-20220829101340096.png)]
-
-
**9.2.2 **数据库操作实现
- 数据表结构
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iG8PX2oM-1690696066438)(D:/Typora/img/image-20220818221922449.png)]
-
添加测试数据
-
编写接⼝实现所需的SQL(可以先在数据库中进行测试)
-
连接查询
select c1.category_id 'category_id1', c1.category_name 'category_name1', c1.category_level 'category_level1', c1.parent_id 'parent_id1', c1.category_icon 'category_icon1', c1.category_slogan 'category_slogan1', c1.category_pic 'category_pic1', c1.category_bg_color 'category_bg_color1', c2.category_id 'category_id2', c2.category_name 'category_name2', c2.category_level 'category_level2', c2.parent_id 'parent_id2', c3.category_id 'category_id3', c3.category_name 'category_name3', c3.category_level 'category_level3', c3.parent_id 'parent_id3' from category c1 inner join category c2 on c2.parent_id=c1.category_id left join category c3 on c3.parent_id=c2.category_id where c1.category_level=1
-
⼦查询
-
如果parent_id=0 表示查询一级分类(因为一级分类所有的parent_id=0)
-
如果parent_id=数字num,num代表的是一级分类的id的话,那就查询一级分类下的所有二级分类。
-
如果num代表二级分类的id的话,那就查询二级分类下所有的三级分类
-
-- 根据⽗级分类的id查询类别信息 select * from category where parent_id=3;
-
-
创建⽤于封装查询的类别信息的CategoryVO
- 在beans⼦⼯程的entity包新建⼀个CategoryVO⽤于封装查询到类别信息,相对于Category来说,新增了如下属性:
public class CategoryVO { //⽤于存放当前分类的⼦分类 private List<CategoryVO> categories; public List<CategoryVO> getCategories() { return categories; } @Override public String toString() { return "CategoryVO{" + "categoryId=" + categoryId + ", categoryName='" + categoryName + '\'' + ", categoryLevel=" + categoryLevel + ", parentId=" + parentId + ", categoryIcon='" + categoryIcon + '\'' + ", categorySlogan='" + categorySlogan + '\'' + ", categoryPic='" + categoryPic + '\'' + ", categoryBgColor='" + categoryBgColor + '\'' + '}'; } }
-
在CategoryMapper定义接口的操作⽅法
- 连接查询和子查询选一个去做就可以了,俩者功能一样,但方法不同
- 子查询不需要知道有多少级的分类,只要能够查到parent_id那么有多少查多少。
@Repository
public interface CategoryMapper extends GeneralDAO<Category> {
//1.连接查询:只查询一次,必须指定有多少分级
public List<CategoryVO> selectAllCategories();
//2.⼦查询:根据parentId查询⼦分类,不需要知道分级,有多少查多少
public List<CategoryVO> selectAllCategories2(int parentId);
}
- 映射配置
<?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.zsh.fmmall.dao.CategoryMapper">
<resultMap id="BaseResultMap" type="com.zsh.fmmall.entity.Category">
<!-- WARNING - @mbg.generated -->
<id column="category_id" jdbcType="INTEGER" property="categoryId" />
<result column="category_name" jdbcType="VARCHAR" property="categoryName" />
<result column="category_level" jdbcType="INTEGER" property="categoryLevel" />
<result column="parent_id" jdbcType="INTEGER" property="parentId" />
<result column="category_icon" jdbcType="VARCHAR" property="categoryIcon" />
<result column="category_slogan" jdbcType="VARCHAR" property="categorySlogan" />
<result column="category_pic" jdbcType="VARCHAR" property="categoryPic" />
<result column="category_bg_color" jdbcType="VARCHAR" property="categoryBgColor" />
</resultMap>
<!--1、连接查询 -->
<!-- 一级目录 -->
<resultMap id="categoryVOMap" type="com.zsh.fmmall.entity.CategoryVO">
<id column="category_id1" jdbcType="INTEGER" property="categoryId" />
<result column="category_name1" jdbcType="VARCHAR" property="categoryName" />
<result column="category_level1" jdbcType="INTEGER" property="categoryLevel" />
<result column="parent_id1" jdbcType="INTEGER" property="parentId" />
<result column="category_icon1" jdbcType="VARCHAR" property="categoryIcon" />
<result column="category_slogan1" jdbcType="VARCHAR" property="categorySlogan" />
<result column="category_pic1" jdbcType="VARCHAR" property="categoryPic" />
<result column="category_bg_color1" jdbcType="VARCHAR" property="categoryBgColor" />
<!--二级目录-->
<collection property="categories" ofType="com.zsh.fmmall.entity.CategoryVO">
<id column="category_id2" jdbcType="INTEGER" property="categoryId" />
<result column="category_name2" jdbcType="VARCHAR" property="categoryName" />
<result column="category_level2" jdbcType="INTEGER" property="categoryLevel" />
<result column="parent_id2" jdbcType="INTEGER" property="parentId" />
<!-- 三级目录 -->
<collection property="categories" ofType="com.zsh.fmmall.entity.CategoryVO">
<id column="category_id3" jdbcType="INTEGER" property="categoryId" />
<result column="category_name3" jdbcType="VARCHAR" property="categoryName" />
<result column="category_level3" jdbcType="INTEGER" property="categoryLevel" />
<result column="parent_id3" jdbcType="INTEGER" property="parentId" />
</collection>
</collection>
</resultMap>
<select id="selectAllCategories" resultMap="categoryVOMap">
select
c1.category_id 'category_id1',
c1.category_name 'category_name1',
c1.category_level 'category_level1',
c1.parent_id 'parent_id1',
c1.category_icon 'category_icon1',
c1.category_slogan 'category_slogan1',
c1.category_pic 'category_pic1',
c1.category_bg_color 'category_bg_color1',
c2.category_id 'category_id2',
c2.category_name 'category_name2',
c2.category_level 'category_level2',
c2.parent_id 'parent_id2',
c3.category_id 'category_id3',
c3.category_name 'category_name3',
c3.category_level 'category_level3',
c3.parent_id 'parent_id3'
from category c1
inner join category c2 on c2.parent_id=c1.category_id
left join category c3 on c3.parent_id=c2.category_id
where c1.category_level=1
</select>
<!--——————————————————————————————————————————————————————————-->
<!--2、子查询-->
<resultMap id="categoryVOMap2" type="com.zsh.fmmall.entity.CategoryVO">
<id column="category_id" jdbcType="INTEGER" property="categoryId" />
<result column="category_name" jdbcType="VARCHAR" property="categoryName" />
<result column="category_level" jdbcType="INTEGER" property="categoryLevel" />
<result column="parent_id" jdbcType="INTEGER" property="parentId" />
<result column="category_icon" jdbcType="VARCHAR" property="categoryIcon" />
<result column="category_slogan" jdbcType="VARCHAR" property="categorySlogan" />
<result column="category_pic" jdbcType="VARCHAR" property="categoryPic" />
<result column="category_bg_color" jdbcType="VARCHAR" property="categoryBgColor" />
<collection property="categories" column="category_id"
select="com.zsh.fmmall.dao.CategoryMapper.selectAllCategories2"/>
</resultMap>
<!-- 根据⽗级分类的id查询⼦级分类 -->
<select id="selectAllCategories2" resultMap="categoryVOMap2">
select
category_id,
category_name,
category_level,
parent_id,
category_icon,
category_slogan,
category_pic,
category_bg_color
from category
where parent_id=#{parentId}
</select>
</mapper>
9.2.3 进行单元测试,判断是否查询成功
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JwHXXMqz-1690696066439)(D:/Typora/img/image-20220829105017953.png)]
- 连接查询:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XnvGf4XM-1690696066443)(D:/Typora/img/image-20220829105053515.png)]
- 在CategoryVO实体类中添加toString方法,以便可以显示查询结果。
- 结果显示:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2oK37Yj6-1690696066447)(D:/Typora/img/image-20220829105257725.png)]
- 子查询:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zz5HgnIZ-1690696066450)(D:/Typora/img/image-20220829114334374.png)]
**9.2.4 **业务层实现
连接查询与子查询,选一个操作即可,这里使用了连接查询。
- CategoryService接⼝
public interface CategoryService {
public ResultVO listCategories();
}
- CategoryServiceImpl
@Service
public class CategoryServiceImpl implements CategoryService {
@Autowired
private CategoryMapper categoryMapper;
@Override
public ResultVO listCategories() {
List<CategoryVO> categoryVOS = categoryMapper.selectAllCategories();
if(categoryVOS.size()==0){
return new ResultVO(BaseCode.no,"false",null);
}
return new ResultVO(BaseCode.ok,"success",categoryVOS);
}
}
**9.2.5 **控制层实现
连接查询与子查询,选一个操作即可,这里使用了连接查询。
- IndexController
@Autowired
private CategoryService categoryService;
@GetMapping("/category-list")
@ApiOperation("商品分类查询接⼝")
public ResultVO listCatetory(){
return categoryService.listCategories();
}
9.2.6 swagger测试
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1IhdqZOp-1690696066451)(D:/Typora/img/image-20220829115236819.png)]
9.3 前端功能实现
9.3.1 index.html前端获取数据
-
连接查询与子查询,选一个操作即可,这里使用了连接查询。
-
通过axios数据通信,拿到分类数据
- 在我们成功读取数据的前提下,因为动态显示,可能导致前端界面没有显示数据。原因是数据渲染的顺序问题(即在我们拿到数据之前,数据还没拿到,而前端js先对数据进行了动态渲染,所以导致二级悬浮菜单不显示)
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sw7Nmrvw-1690696066453)(D:/Typora/img/image-20220829213728393.png)]
-
在index.html界面,找到一级分类所在位置,先动态显示一级分类
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qI8guwE6-1690696066454)(D:/Typora/img/image-20220829195133116.png)]
-
动态显示二级分类
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6cTkfJx2-1690696066456)(D:/Typora/img/image-20220829195708194.png)]
-
动态显示三级分类
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-74F1doms-1690696066457)(D:/Typora/img/image-20220829213903219.png)]
-
显示结果:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NlYRc4fI-1690696066462)(D:/Typora/img/image-20220829213947440.png)]
⼗、⾸⻚-商品推荐
10.1 流程分析
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bMZsQ3xe-1690696066467)(D:/Typora/img/image-20220818222918783.png)]
10.2 接⼝开发
10.2.1 数据库实现
商品推荐算法:推荐最新上架的商品
说明:商品推荐算法是根据多个维度进⾏权重计算,计算出⼀个匹配值
-
数据表分析及数据准备
-
商品表:
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-f0AAgUsv-1690696066469)(D:/Typora/img/image-20220830091617120.png)]
-
商品的图片信息表
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sX72tmNn-1690696066470)(D:/Typora/img/image-20220830091642809.png)]
-
-
sql:推荐使用子查询
- 连接查询出现重复值问题,需去重,效率低
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-z15TDGJM-1690696066471)(D:/Typora/img/image-20220830092853562.png)]
- 连接查询出现重复值问题,需去重,效率低
-- 商品推荐:查询最新上架的商品(根据商品的创建时间进行降序排序,查询前三条)
select * from product order by create_time desc limit 0,3;
-- ⼦查询:根据商品id查询商品图⽚
select * from product_img where item_id=2;
-
在beans⼦⼯程entity包创建ProductVO,相⽐较Product新增了List imgs⽤于存储商品的
图⽚
public class ProductVO{ private List<ProductImg> imgs; public List<ProductImg> getImgs() { return imgs; } public void setImgs(List<ProductImg> imgs) { this.imgs = imgs; } }
-
Mapper接⼝定义操作⽅法:
- ProductMapper
public interface ProductMapper extends GeneralDAO<Product> { //查询商品信息 public List<ProductVO> selectRecommendProducts(); }
- ProductImgMapper
public interface ProductImgMapper extends GeneralDAO<ProductImg> { //根据商品id查询当前商品的图⽚信息 public List<ProductImg> selectProductImgByProductId(int productId); }
-
配置映射⽂件
-
ProductMapper.xml
-
进行了子查询操作:为了查询到商品所对应的img
-
<collection property="imgs" select="com.qfedu.fmmall.dao.ProductImgMapper.selectProductImgByProductId" column="product_id"/>
-
<resultMap id="ProductVOMap" type="com.qfedu.fmmall.entity.ProductVO"> <id column="product_id" jdbcType="VARCHAR" property="productId" /> <result column="product_name" jdbcType="VARCHAR" property="productName" /> <result column="category_id" jdbcType="INTEGER" property="categoryId" /> <result column="root_category_id" jdbcType="INTEGER" property="rootCategoryId" /> <result column="sold_num" jdbcType="INTEGER" property="soldNum" /> <result column="product_status" jdbcType="INTEGER" property="productStatus" /> <result column="create_time" jdbcType="TIMESTAMP" property="createTime" /> <result column="update_time" jdbcType="TIMESTAMP" property="updateTime" /> <result column="content" jdbcType="LONGVARCHAR" property="content" /> <collection property="imgs" select="com.qfedu.fmmall.dao.ProductImgMapper.selectProductImgByPro ductId" column="product_id"/> </resultMap> <select id="selectRecommendProducts" resultMap="ProductVOMap"> select product_id, product_name, category_id, root_category_id, sold_num, product_status, content, create_time, update_time from product order by create_time desc limit 0,3 </select>
-
ProductImgMapper.xml
<?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.qfedu.fmmall.dao.ProductImgMapper"> <resultMap id="BaseResultMap" type="com.qfedu.fmmall.entity.ProductImg"> <id column="id" jdbcType="VARCHAR" property="id" /> <result column="item_id" jdbcType="VARCHAR" property="itemId" /> <result column="url" jdbcType="VARCHAR" property="url" /> <result column="sort" jdbcType="INTEGER" property="sort" /> <result column="is_main" jdbcType="INTEGER" property="isMain" /> <result column="created_time" jdbcType="TIMESTAMP" property="createdTime" /> <result column="updated_time" jdbcType="TIMESTAMP" property="updatedTime" /> </resultMap> <select id="selectProductImgByProductId" resultMap="BaseResultMap"> select id, item_id, url, sort, is_main, created_time, updated_time from product_img where item_id=#{productId} </select> </mapper>
-
10.2.2 业务层实现
- ProductService接⼝
public interface ProductService {
public ResultVO listRecommendProducts();
}
- ProductServiceImpl实现类
@Service
public class ProductServiceImpl implements ProductService {
@Autowired
private ProductMapper productMapper;
public ResultVO listRecommendProducts() {
List<ProductVO> productVOS =
productMapper.selectRecommendProducts();
ResultVO resultVO = new ResultVO(ResStatus.OK, "success",
productVOS);
return resultVO;
}
}
10.2.3 控制层实现
- IndexController
@Autowired
private ProductService productService;
@GetMapping("/list-recommends")
@ApiOperation("查询推荐商品接⼝")
public ResultVO listRecommendProducts() {
return productService.listRecommendProducts();
}
10.2.4 swagger接口说明文档,进行后端测试
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WJd9KX97-1690696066472)(D:/Typora/img/image-20220830103705422.png)]
10.3 前端实现
10.3.1 在index.html界面,添加新品推荐的vue操作
//新品推荐
var url3=baseUrl+"index/list-recommends";
axios.get(url3).then((res)=>{
var vo=res.data;
this.recommendProduct=vo.data;
// console.log(vo.data);
});
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pIZYLijA-1690696066473)(D:/Typora/img/image-20220830120404364.png)]
10.3.2 给新品推荐的标签,绑定vue,进行动态显示
-
利用v-for循环显示
-
<a v-for="product in recommendProduct" href="introduction.html"> <div class="am-u-sm-4 am-u-lg-3 "> <div class="info "> <h3>{{product.productName}}</h3> <h4>{{product.content}}</h4> </div> <div class="recommendationMain one"> <img :src="'static/pimgs/'+product.imgs[0].url "></img> </div> </div> </a>
-
-
单个商品显示
-
<a href="introduction.html"> <div class="am-u-sm-4 am-u-lg-3 "> <div class="info "> <h3>{{recommendProduct[0].productName}}</h3> <h4>{{recommendProduct[0].content}}</h4> </div> <div class="recommendationMain one"> <img :src="'static/pimgs/'+recommendProduct[0].imgs[0].url "> </img> </div> </div> </a>
-
-
显示效果
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jWZWewmC-1690696066480)(D:/Typora/img/image-20220830120814600.png)]
⼗⼀、⾸⻚-分类商品推荐
按照商品的分类(⼀级分类)推荐销量最⾼的6个商品
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-41AkuMAt-1690696066483)(D:/Typora/img/image-20220830172538783.png)]
11.1 流程分析
加载分类商品推荐有两种实现⽅案:
⽅案⼀:当加载⾸⻚⾯时不加载分类的推荐商品,监听进度条滚动事件,当进度条触底
(滚动指定的距离)就触发分类推荐商品的加载,每次只加载⼀个分类的商品。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sn1KXUCn-1690696066484)(D:/Typora/img/image-20220818223954958.png)]
⽅案⼆:⼀次性加载所有分类的推荐商品,整体进行初始化
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YijhVY5H-1690696066486)(D:/Typora/img/image-20220818224005350.png)]
11.2 接⼝实现
11.2.1 数据库实现
- 数据准备
-- 添加商品
-- 添加⼗个分类下的商品:
insert into
product(product_id,product_name,category_id,root_category_id,sold_n
um,product_status,content,create_time,updated_time)
values('5','商品5',10,1,122,1,'商品说明','2021-04-26 11:11:11','2021-
04-26 11:11:11');
insert into
product(product_id,product_name,category_id,root_category_id,sold_n
um,product_status,content,create_time,updated_time)
values('6','商品6',10,1,123,1,'商品说明','2021-04-26 11:11:11','2021-
04-26 11:11:11');
insert into
product(product_id,product_name,category_id,root_category_id,sold_n
um,product_status,content,create_time,updated_time)
values('7','商品7',10,1,124,1,'商品说明','2021-04-26 11:11:11','2021-
04-26 11:11:11');
insert into
product(product_id,product_name,category_id,root_category_id,sold_n
um,product_status,content,create_time,updated_time)
values('8','商品8',10,1,125,1,'商品说明','2021-04-26 11:11:11','2021-
04-26 11:11:11');
insert into
product(product_id,product_name,category_id,root_category_id,sold_n
um,product_status,content,create_time,updated_time)
values('9','商品9',10,1,126,1,'商品说明','2021-04-26 11:11:11','2021-
04-26 11:11:11');
insert into
product(product_id,product_name,category_id,root_category_id,sold_n
um,product_status,content,create_time,updated_time)
values('10','商品10',10,1,127,1,'商品说明','2021-04-26
11:11:11','2021-04-26 11:11:11');
insert into
product(product_id,product_name,category_id,root_category_id,sold_n
um,product_status,content,create_time,updated_time)
values('11','商品11',10,1,128,1,'商品说明','2021-04-26
11:11:11','2021-04-26 11:11:11');
insert into
product(product_id,product_name,category_id,root_category_id,sold_n
um,product_status,content,create_time,updated_time)
values('12','商品12',46,2,122,1,'商品说明','2021-04-26
11:11:11','2021-04-26 11:11:11');
insert into
product(product_id,product_name,category_id,root_category_id,sold_n
um,product_status,content,create_time,updated_time)
values('13','商品13',46,2,123,1,'商品说明','2021-04-26
11:11:11','2021-04-26 11:11:11');
insert into
product(product_id,product_name,category_id,root_category_id,sold_n
um,product_status,content,create_time,updated_time)
values('14','商品14',46,2,124,1,'商品说明','2021-04-26
11:11:11','2021-04-26 11:11:11');
insert into
product(product_id,product_name,category_id,root_category_id,sold_n
um,product_status,content,create_time,updated_time)
values('15','商品15',46,2,125,1,'商品说明','2021-04-26
11:11:11','2021-04-26 11:11:11');
insert into
product(product_id,product_name,category_id,root_category_id,sold_n
um,product_status,content,create_time,updated_time)
values('16','商品16',46,2,126,1,'商品说明','2021-04-26
11:11:11','2021-04-26 11:11:11');
insert into
product(product_id,product_name,category_id,root_category_id,sold_n
um,product_status,content,create_time,updated_time)
values('17','商品17',46,2,127,1,'商品说明','2021-04-26
11:11:11','2021-04-26 11:11:11');
insert into
product(product_id,product_name,category_id,root_category_id,sold_n
um,product_status,content,create_time,updated_time)
values('18','商品18',46,2,128,1,'商品说明','2021-04-26
11:11:11','2021-04-26 11:11:11');
-- 添加商品图⽚
insert into
product_img(id,item_id,url,sort,is_main,created_time,updated_time)
values('9','5','wwxb_1.png',1,1,'2021-04-26 11:11:11','2021-04-26
11:11:11');
insert into
product_img(id,item_id,url,sort,is_main,created_time,updated_time)
values('10','6','wwxb_1.png',1,1,'2021-04-26 11:11:11','2021-04-26
11:11:11');
insert into
product_img(id,item_id,url,sort,is_main,created_time,updated_time)
values('11','7','wwxb_1.png',1,1,'2021-04-26 11:11:11','2021-04-26
11:11:11');
insert into
product_img(id,item_id,url,sort,is_main,created_time,updated_time)
values('12','8','wwxb_1.png',1,1,'2021-04-26 11:11:11','2021-04-26
11:11:11');
insert into
product_img(id,item_id,url,sort,is_main,created_time,updated_time)
values('13','9','wwxb_1.png',1,1,'2021-04-26 11:11:11','2021-04-26
11:11:11');
insert into
product_img(id,item_id,url,sort,is_main,created_time,updated_time)
values('14','10','wwxb_1.png',1,1,'2021-04-26 11:11:11','2021-04-26
11:11:11');
insert into
product_img(id,item_id,url,sort,is_main,created_time,updated_time)
values('15','11','wwxb_1.png',1,1,'2021-04-26 11:11:11','2021-04-26
11:11:11');
insert into
product_img(id,item_id,url,sort,is_main,created_time,updated_time)
values('16','12','wwxb_1.png',1,1,'2021-04-26 11:11:11','2021-04-26
11:11:11');
insert into
product_img(id,item_id,url,sort,is_main,created_time,updated_time)
values('17','13','wwxb_1.png',1,1,'2021-04-26 11:11:11','2021-04-26
11:11:11');
insert into
product_img(id,item_id,url,sort,is_main,created_time,updated_time)
values('18','14','wwxb_1.png',1,1,'2021-04-26 11:11:11','2021-04-26
11:11:11');
insert into
product_img(id,item_id,url,sort,is_main,created_time,updated_time)
values('19','15','wwxb_1.png',1,1,'2021-04-26 11:11:11','2021-04-26
11:11:11');
insert into
product_img(id,item_id,url,sort,is_main,created_time,updated_time)
values('20','16','wwxb_1.png',1,1,'2021-04-26 11:11:11','2021-04-26
11:11:11');
insert into
product_img(id,item_id,url,sort,is_main,created_time,updated_time)
values('21','17','wwxb_1.png',1,1,'2021-04-26 11:11:11','2021-04-26
11:11:11');
insert into
product_img(id,item_id,url,sort,is_main,created_time,updated_time)
values('22','18','wwxb_1.png',1,1,'2021-04-26 11:11:11','2021-04-26
11:11:11');
- 查询SQL
-- 查询所有的⼀级分类
select * from category where category_level=1;
-- 查询每个分类下销量前6的商品
select * from product where root_category_id=2 order by sold_num
desc limit 0,6;
-- 查询每个商品的图⽚
select * from product_img where item_id = 1;
- 实体类:
- 因为首页-分类商品推荐:需要查询一级分类,以及每个一级分类下销量前6的商品,所以之前的实体类已经不满足需求。
- 我们需要向商品分类的实体类中添加存储商品信息的属性。
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class CategoryVO {
private Integer categoryId;
private String categoryName;
private Integer categoryLevel;
private Integer parentId;
private String categoryIcon;
private String categorySlogan;
private String categoryPic;
private String categoryBgColor;
//实现⾸⻚的类别显示
private List<CategoryVO> categories;
//实现⾸⻚分类商品推荐,ProductVO存有商品的图片信息
private List<ProductVO> products;
}
- 在Mapper接⼝中定义查询⽅法
CategoryMapper |
---|
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VyAt435V-1690696074960)(D:/Typora/img/image-20220818224600244.png)] |
ProductMapper |
---|
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qKCQh2rN-1690696074961)(D:/Typora/img/image-20220818224635730.png)] |
- 映射配置
ProductMapper.xml |
---|
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CHRk7ADe-1690696074965)(D:/Typora/img/image-20220818224737473.png)] |
CategoryMapper.xml |
---|
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hqfutjpO-1690696074966)(D:/Typora/img/image-20220818224851534.png)] |
11.2.2 业务层实现
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ll4q1jJZ-1690696066487)(D:/Typora/img/image-20220830183819218.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mrd7QJcs-1690696066488)(D:/Typora/img/image-20220830183943461.png)]
11.2.3 控制层实现
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pL0zAnmb-1690696066490)(D:/Typora/img/image-20220830184101973.png)]
11.2.4 swagger进行测试
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Bw7ahKYz-1690696066496)(D:/Typora/img/image-20220830184220843.png)]
11.3 前端实现
11.3.1 vue实现前端显示
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LADKLbeY-1690696066498)(D:/Typora/img/image-20220831112132487.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1My8ilOW-1690696066500)(D:/Typora/img/image-20220831112220617.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FMJ5Wl1u-1690696066502)(D:/Typora/img/image-20220831112250877.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OgX8ZlTI-1690696066503)(D:/Typora/img/image-20220831112422221.png)]
11.3.2 显示结果
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LBdUFHOp-1690696066504)(D:/Typora/img/image-20220831112450004.png)]
⼗⼆、商品详情展示—显示商品基本信息
点击⾸⻚推荐的商品、轮播图商品⼴告、商品列表⻚⾯点击商品,就会进⼊到商品的详
情⻚⾯
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mI26bNs8-1690696066505)(D:/Typora/img/image-20220831182735056.png)]
12.1 流程分析
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ua6L078A-1690696066506)(D:/Typora/img/image-20220818225002916.png)]
12.2 商品基础信息-接⼝实现
商品基本信息、商品套餐、商品图⽚
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JT20laZ1-1690696066515)(D:/Typora/img/image-20220831183404663.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-X7LmRiMz-1690696066517)(D:/Typora/img/image-20220831184505560.png)]
- SQL
-- 根据id查询商品基本信息(product_status商品的状态为1,表示上架、正在销售)
select * from product where product_id=3 and product_status=1;
-- 根据商品id查询当前商品的图⽚(√)
select * from product_img where item_id=3;
-- 根据商品id查询当前商品的套餐
select * from product_sku where product_id=3;
-
因为上述的三个查询都是单表查询,可以通过tkmapper完成,⽆需在Mapper接⼝定义新的⽅法
-
业务层实现
ProductService接⼝ |
---|
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-M0OraNvC-1690696074967)(D:/Typora/img/image-20220818225122728.png)] |
ProductServiceImpl类实现 |
---|
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5yuksmf9-1690696074968)(D:/Typora/img/image-20220831200606916.png)] |
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tksEOopz-1690696074970)(D:/Typora/img/image-20220818225200335.png)] |
- 控制层实现
ProductController类 |
---|
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HAbIOvH2-1690696074971)(D:/Typora/img/image-20220818225242823.png)] |
- swagger进行后端接口测试
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LSKaGgjU-1690696066519)(D:/Typora/img/image-20220831204947451.png)]
12.3 商品基础信息-前端显示
12.3.1 前端页面之间的url传值
页面A发送超链接(超链接中包含所需id),跳转到页面B。
-
页面A:传值
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OBp11IHo-1690696066521)(D:/Typora/img/image-20220831212024973.png)]
-
页面B:取值
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kKcPBmpE-1690696066522)(D:/Typora/img/image-20220831212216051.png)]
-
测试结果
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hAjMI7X0-1690696066523)(D:/Typora/img/image-20220831212310194.png)]
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LixXnVOW-1690696066533)(D:/Typora/img/image-20220831212238506.png)]
-
注:但是如果我们传的值是中文类型的话,那么拿值时就会出现一些看不懂的字符串。
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ery0hCaX-1690696066535)(D:/Typora/img/image-20220831212515983.png)]
- 原因是:在传值时会自动编码
- 解决方案,在拿值时进行解码即可
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Vjnsxueu-1690696066536)(D:/Typora/img/image-20220901102004807.png)]
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AzgVtz2h-1690696066537)(D:/Typora/img/image-20220831212905297.png)]
-
工具类的封装与使用:我们可以把方法单独封装成js来使用
- util.js:工具类的封装
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HZppEf9g-1690696066538)(D:/Typora/img/image-20220831213037628.png)]
- B页面调用
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7uouuHZR-1690696066539)(D:/Typora/img/image-20220831213136160.png)]
- util.js:工具类的封装
12.3.2 前端获取商品基本信息
- index.html页面超链接跳转时,传输商品id
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-a0Pf0H87-1690696066540)(D:/Typora/img/image-20220831214206808.png)]
- introduction.html页面,获取url信息
- 导入util.js(自己定义的工具类)
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4U8lV1j8-1690696066541)(D:/Typora/img/image-20220831215147217.png)]
- 获取商品基本信息
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uxwup6BZ-1690696066544)(D:/Typora/img/image-20220831215838995.png)]
12.3.3 前端显示商品基本信息和图片
-
商品名称
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NjeZNDf9-1690696066546)(D:/Typora/img/image-20220908201402945.png)]
-
问题一:因为页面进行首次渲染时,product:{}为空,导致首次渲染没有信息,刚打开页面会出现闪烁问题。
- 解决方案:让vue进行初次渲染时有数据显示[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7Ytl1iqZ-1690696066548)(D:/Typora/img/image-20220831220815129.png)]
-
商品销量[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2wGyWJtu-1690696066549)(D:/Typora/img/image-20220831221205705.png)]
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VXXLD7CJ-1690696066549)(D:/Typora/img/image-20220831221239787.png)]
-
商品图片
- 方法一:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QAXG7Txm-1690696066551)(D:/Typora/img/image-20220831222056955.png)]
- 方法二:
- 方法示例:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AK3PEGzl-1690696066554)(D:/Typora/img/image-20220831222743777.png)]
- 方法的运用:如果index==0,就加载class="tb-selected"样式[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yTx0V2bc-1690696066555)(D:/Typora/img/image-20220831223005787.png)]
-
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-txi8AFj4-1690696066556)(D:/Typora/img/image-20220831223346810.png)]
-
图片渲染顺序:放大镜效果
- 方式一:setTimeout【不推荐】[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jyZzpQ7X-1690696066557)(D:/Typora/img/image-20220831223539993.png)]
- 方式二:updated:渲染后才执行【推荐这种方法执行渲染】[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2OWSiuF7-1690696066558)(D:/Typora/img/image-20220831223732480.png)]
12.3.4 前端显示商品套餐列表
12.3.4.1 显示套餐列表
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JOjS4ILn-1690696066559)(D:/Typora/img/image-20220909101258015.png)]
- 方法一
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZojFQw1x-1690696066561)(D:/Typora/img/image-20220909095806987.png)]
- 方法二
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1wUtUtwm-1690696066562)(D:/Typora/img/image-20220909101344352.png)]
- 如果套餐不能点击的话,可能是因为js渲染出现了问题
- 解决方案,初始化渲染
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2E84e09z-1690696066566)(D:/Typora/img/image-20220909101529905.png)]
- 解决方案,初始化渲染
12.3.4.2 套餐列表的联动
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SzZPQWUU-1690696066569)(D:/Typora/img/image-20220909154057605.png)]
-
套餐价格的联动:
- 添加当前所选套餐得id的属性
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cXFmAmYz-1690696066570)(D:/Typora/img/image-20220909105701429.png)]
- 添加点击事件,并给事件进行参数的传值(使用dataset对象传值)
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-br9n5lRT-1690696066571)(D:/Typora/img/image-20220909110859503.png)]
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-S7Pdb62B-1690696066571)(D:/Typora/img/image-20220909110015752.png)]
- 价格通过当前套餐id进行联动
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VYVHcxpI-1690696066572)(D:/Typora/img/image-20220909110100158.png)]
- 添加当前所选套餐得id的属性
-
套餐属性的联动
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CmMx7otQ-1690696066572)(D:/Typora/img/image-20220909112333790.png)]
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CpnXsa9R-1690696066578)(D:/Typora/img/image-20220909112842283.png)]
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HrLvP9zP-1690696066581)(D:/Typora/img/image-20220909113046493.png)]
⼗三、商品详情展示—显示商品参数信息
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GIemOe33-1690696066583)(D:/Typora/img/image-20220831184131973.png)]
13.1 接⼝实现
根据商品id查询商品参数信息
-
数据库操作直接只⽤tkMapper的默认⽅法实现
-
业务层实现
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6Ke9fsAc-1690696074972)(D:/Typora/img/image-20220818230636201.png)] |
@Override
public ResultVO getProductParamsById(String productId) {
Example example = new Example(ProductParams.class);
Example.Criteria criteria = example.createCriteria();
criteria.andEqualTo("productId", productId);
List<ProductParams> productParams = productParamsMapper.selectByExample(example);
if(productParams.size()>0){
return new ResultVO(BaseCode.ok,"sucess",productParams.get(0));
}else {
return new ResultVO(BaseCode.no,"false",null);
}
}
- 控制层实现
@ApiOperation("通过商品id获取商品参数信息的接⼝")
@ApiImplicitParam(dataType = "string",name = "productId", value = "商品id",required =true)
@GetMapping("/params-info/{productId}")
public ResultVO getProductParamsById(@PathVariable("productId") String pid){
return productService.getProductParamsInfo(pid);
}
-
Swagger测试
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MQAE8WNa-1690696066584)(D:/Typora/img/image-20220909160829723.png)]
13.2 前端显示商品参数
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iy2RFj2m-1690696066585)(D:/Typora/img/image-20220909165752086.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eWRzLQGE-1690696066586)(D:/Typora/img/image-20220909165719489.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zGmyoJJF-1690696066588)(D:/Typora/img/image-20220909165740437.png)]
13.3 前端显示商品细节
- 运用了v-html模板语法,可以把字符类型的html标签,转化成html界面
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9UuYFgaS-1690696066589)(D:/Typora/img/image-20220909201347725.png)]
-
把这样的html标签作为product表的content信息
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wiJLRbQc-1690696066590)(D:/Typora/img/image-20220909201626206.png)]
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OOCJQ2JC-1690696066591)(D:/Typora/img/image-20220909201729862.png)]
-
前端界面操作与显示
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7a9XQ3qQ-1690696066593)(D:/Typora/img/image-20220909201925099.png)]
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-O4kTomnQ-1690696066595)(D:/Typora/img/image-20220909202410237.png)]
⼗四、商品详情展示—显示商品评论信息
每个商品底下都有评论,所以用户的评论是基于商品的,故查询条件是商品的id
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Uae3T8bh-1690696066599)(D:/Typora/img/image-20220831184159404.png)]
14.1 接⼝实现
注:我们这个接口暂时只能一次查询该商品下所有的评论信息,暂时还不具有分页、好评等信息。所以之后还需要修改。
14.1.1 数据库实现
-
数据表分析及数据准备
-
SQL
-- 根据ID查询商品的评价信息,关联查询评价⽤户的信息
select u.username,u.nickname,u.user_img,c.*
from product_comments c
INNER JOIN users u
ON u.user_id = c.user_id
WHERE c.product_id =3;
- 实体类封装,自定义类
ProductCommentsVO
- 涉及到多表查询,则tkmapper不能自动实现,需要我们手动去创建
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ProductCommentsVO {
private String commId;
private String productId;
private String productName;
private String orderItemId;
private Integer isAnonymous;
private Integer commType;
private Integer commLevel;
private String commContent;
private String commImgs;
private Date sepcName;
private Integer replyStatus;
private String replyContent;
private Date replyTime;
private Integer isShow;
//封装评论对应的⽤户数据
private String userId;
private String username;
private String nickname;
private String userImg;
}
- 在Mapper接⼝定义查询⽅法
@Repository
public interface ProductCommentsMapper extends
GeneralDAO<ProductComments> {
public List<ProductCommentsVO> selectCommontsByProductId(String
productId);
}
- 映射配置:
<!--1、连接查询-->
<resultMap id="BaseResultMapVO" type="com.zsh.fmmall.entity.ProductCommentsVO">
<!--
WARNING - @mbg.generated
-->
<id column="comm_id" jdbcType="VARCHAR" property="commId" />
<result column="product_id" jdbcType="VARCHAR" property="productId" />
<result column="product_name" jdbcType="VARCHAR" property="productName" />
<result column="order_item_id" jdbcType="VARCHAR" property="orderItemId" />
<result column="is_anonymous" jdbcType="INTEGER" property="isAnonymous" />
<result column="comm_type" jdbcType="INTEGER" property="commType" />
<result column="comm_level" jdbcType="INTEGER" property="commLevel" />
<result column="comm_content" jdbcType="VARCHAR" property="commContent" />
<result column="comm_imgs" jdbcType="VARCHAR" property="commImgs" />
<result column="sepc_name" jdbcType="TIMESTAMP" property="sepcName" />
<result column="reply_status" jdbcType="INTEGER" property="replyStatus" />
<result column="reply_content" jdbcType="VARCHAR" property="replyContent" />
<result column="reply_time" jdbcType="TIMESTAMP" property="replyTime" />
<result column="is_show" jdbcType="INTEGER" property="isShow" />
<!--评论对应的⽤户数据-->
<result column="user_id" jdbcType="VARCHAR" property="userId" />
<result column="username" jdbcType="VARCHAR" property="username" />
<result column="nickname" jdbcType="VARCHAR" property="nickname" />
<result column="user_img" jdbcType="VARCHAR" property="userImg" />
</resultMap>
<select id="selectCommentsByProductID" resultMap="BaseResultMapVO">
SELECT
u.user_id,
u.username,
u.nickname,
u.user_img ,
pc.comm_id,
pc.product_id,
pc.product_name,
pc.order_item_id,
pc.is_anonymous,
pc.comm_type,
pc.comm_level,
pc.comm_content,
pc.comm_imgs,
pc.sepc_name,
pc.reply_status,
pc.reply_content,
pc.reply_time,
pc.is_show
from users u
inner JOIN product_comments pc
ON u.user_id=pc.user_id
where pc.product_id=#{productID};
</select>
14.1.2 业务层实现
- 创建 ProductCommentsService 接⼝定义⽅法
//商品评论的业务层
@Repository
public interface ProductCommentsService {
public ResultVO listCommontsByProductId(String pid);//获取商品评论信息
}
- 创建实现类 ProductCommentsServiceImpl 实现查询操作
@Service
public class ProductCommentsServiceImpl implements ProductCommentsService {
@Autowired
private ProductCommentsMapper productCommentsMapper;
@Override
public ResultVO listCommontsByProductId(String pid) {
List<ProductCommentsVO> productCommentsVOS = productCommentsMapper.selectCommentsByProductID(pid);
// 这里没有查询是否成功,只显示是否有评论
if(productCommentsVOS.size()>1){//大于1说明该商品有用户评论
return new ResultVO(BaseCode.ok,"success",productCommentsVOS);
}else{//否则说明,该商品暂时没有人评论,可以提示用户进行评论
return new ResultVO(BaseCode.no,"该商品无人评论","该商品无人评论,请输入您的热评!!!");
}
}
}
14.1.3 控制层实现
- ProductController
@ApiOperation("获取商品用户评论信息的接⼝")
@ApiImplicitParam(dataType = "string",name = "productId", value = "商品id",required =true)
@GetMapping("/comment-info/{productId}")
public ResultVO listCommontsByProductId(@PathVariable("productId")String pid){
return productCommentsService.listCommontsByProductId(pid);
}
14.2 前端评论内容显示
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aw3iVvlQ-1690696066601)(D:/Typora/img/image-20220913110540104.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DEzNTwVt-1690696066602)(D:/Typora/img/image-20220913110958119.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XvcJXbqq-1690696066603)(D:/Typora/img/image-20220913111056310.png)]
⼗五、商品详情展示—商品评论分⻚及统计信息
15.1 流程分析
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QBzghuB8-1690696066604)(D:/Typora/img/image-20220911092835545.png)]
15.2 接⼝开发
15.2.1 改造商品评论列表接⼝(分页查询)
分⻚查询
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JAJgkX1x-1690696066605)(D:/Typora/img/image-20220912220535653.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rYHkq7pR-1690696066605)(D:/Typora/img/image-20220912220552522.png)]
- 定义PageHelper:总记录数、总页数、分页数据
@Data
@NoArgsConstructor
@AllArgsConstructor
public class PageHelper<T> {
//总记录数
private int count;
//总⻚数
private int pageCount;
//分⻚数据
private List<T> list;
}
- 改造数据库操作
ProductCommentsMapper 接⼝ |
---|
当我们自定义的mapper有多个参数时,我们需要用@Param()指定相应的名字。分页查询,包含2个参数,start起始索引和limit查询条数 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-v0LJBW8u-1690696074974)(D:/Typora/img/image-20220911093020535.png)] |
ProductCommentsMapper.xml映射配置 |
---|
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bfkoLEXt-1690696074975)(D:/Typora/img/image-20220911093041834.png)] |
- 改造业务逻辑层
ProductCommontsService接⼝ |
---|
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IJa1bgY5-1690696074975)(D:/Typora/img/image-20220911093125096.png)] |
ProductCommontsServiceImpl |
---|
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YuKsGw98-1690696074979)(D:/Typora/img/image-20220911093200942.png)] |
- 改造控制层
ProductController |
---|
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3se6bGtO-1690696074984)(D:/Typora/img/image-20220911093228633.png)] |
15.2.2 评价统计接⼝实现
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0lnb9mU8-1690696066606)(D:/Typora/img/image-20220913111206475.png)]
-
数据库实现
-
统计当前商品的总记录数
-
统计当前商品的好评/中评/差评
-
-
业务层实现:
ProductCommontsServiceImpl
@Override
public ResultVO getCommentsCountByProductId(String productId) {
//1.查询当前商品评价的总数
Example example = new Example(ProductComments.class);
Example.Criteria criteria = example.createCriteria();
criteria.andEqualTo("productId",productId);
int total = productCommentsMapper.selectCountByExample(example);
//2、查询好评的数量
criteria.andEqualTo("commType",1);
int goodTotal = productCommentsMapper.selectCountByExample(example);
/*
注:因为example不会被覆盖到,所以重复使用依然会先有productId条件,再有好评的条件。
所以中评差评需要new一个新的example对象
*/
//3、查询中评的数量
Example example1 = new Example(ProductComments.class);
Example.Criteria criteria1 = example1.createCriteria();
criteria1.andEqualTo("productId",productId);
criteria1.andEqualTo("commType",0);
int midTotal = productCommentsMapper.selectCountByExample(example1);
//4、查询差评的数量
Example example2 = new Example(ProductComments.class);
Example.Criteria criteria2 = example2.createCriteria();
criteria2.andEqualTo("productId",productId);
criteria2.andEqualTo("commType",-1);
int badTotal = productCommentsMapper.selectCountByExample(example2);
//5、计算好评数率
//(1)因为我要取到小数位,所以要把int整形转化为double双精度类型,例如:0.232321 乘以100位21.2321
double goodPercent=(Double.parseDouble(goodTotal+"")/Double.parseDouble(total+""))*100;
//(2)(percent+"").lastIndexOf(".")+3是查找percent中小数点后保留俩位小数。
// 遵循前闭后开原则,+3读取到小数点后3位,但不包括第三位。 例如:21.2321取为21.23
String percentValue=(goodPercent+"").substring(0,(goodPercent+"").lastIndexOf(".")+3);
HashMap<String,Object> map=new HashMap();
map.put("total",total);
map.put("goodTotal",goodTotal);
map.put("midTotal",midTotal);
map.put("badTotal",badTotal);
map.put("percent",percentValue);
return new ResultVO(BaseCode.ok,"sucess",map);
}
- ProductController接口
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WcYBwB5n-1690696066608)(D:/Typora/img/image-20220913192427195.png)]
15.3 前端实现
15.3.1 商品评论的分⻚
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AkDbX0nx-1690696066611)(D:/Typora/img/image-20220913111304002.png)]
- 引⽤elementUI分⻚组件:elementUI是基于vue的插件,引入分页依赖,进行显示
<!-- 引⼊样式 -->
<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
<!-- vue的引⼊必须在elementUI组件库引⼊之前 -->
<script type="text/javascript" src="js/vue.js"></script>
<!-- 引⼊组件库 -->
<script src="https://unpkg.com/element-ui/lib/index.js"></script>
-
引⽤分⻚组件
- 删除掉之前的分页
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EmtPJIVM-1690696066616)(D:/Typora/img/image-20220913094510121.png)]
- 引入新的分页组件
<!--分⻚ -->
<el-pagination background layout="prev, pager, next"
:current-page="pageNum"
:page-size="limit"
:total="count"
@current-change="pager">
</el-pagination>
-
监听分⻚组件的 ⻚码改变 事件(点击上⼀⻚、下⼀⻚、⻚码都会导致 ⻚码改变 )
分⻚组件的事件函数默认传递当前⻚码参数
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YwPlSJCO-1690696066618)(D:/Typora/img/image-20220913184649855.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4fTwlo6V-1690696066619)(D:/Typora/img/image-20220913184735980.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BVyxsACb-1690696066620)(D:/Typora/img/image-20220913184845254.png)]
15.3.2 商品评价统计
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5W93OggX-1690696066621)(D:/Typora/img/image-20220913111223585.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8Kgtc0Ja-1690696066621)(D:/Typora/img/image-20220913193257777.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ppayh3wX-1690696066622)(D:/Typora/img/image-20220913193429574.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GDLR4oiF-1690696066623)(D:/Typora/img/image-20220913193434151.png)]…
⼗六、购物⻋—添加购物⻋(登陆状态)
16.1 流程分析
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZJuHIbVY-1690696066623)(D:/Typora/img/image-20220911093651309.png)]
16.2 接⼝实现
16.2.1 修改购物⻋数据表结构
增加了sku_props套餐属性字段
shopping_cart |
---|
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PTSEqNmp-1690696074985)(D:/Typora/img/image-20220911093733042.png)] |
- 数据表修改完成之后,对此表重新进⾏逆向⼯程shopping_cart
修改generatorConfig.xml |
---|
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ejeuDcfg-1690696074986)(D:/Typora/img/image-20220913212549967.png)] |
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VbqxqAlA-1690696074987)(D:/Typora/img/image-20220913212649642.png)] |
成功后,把shopping_cart实体类放在bean中即可(逆向工程实体类位置可能不对),其他mapper与mapper.xml位置正确 |
16.2.2 数据库实现
- 单表添加操作,可以直接使⽤tkMapper完成
16.2.3 业务层实现
- ShoppingCartService 接⼝
//购物车实现
public interface ShoppingCartService {
public ResultVO addShoppingCart(ShoppingCart shopCart,String token);
}
- 实现类
@Service
public class ShoppingCartServiceImpl implements ShoppingCartService {
@Autowired
private ShoppingCartMapper shoppingCartMapper;
@Override
public ResultVO addShoppingCart(ShoppingCart shopCart, String token) {
int insert = shoppingCartMapper.insert(shopCart);
if(insert>0){
return new ResultVO(BaseCode.ok,"sucess",shopCart);
}else {
return new ResultVO(BaseCode.no,"fasle",null);
}
}
}
- ShopCartController.java
@RestController
@RequestMapping("/shopCart")
@Api(value = "提供购物车业务相关接口",tags = "购物车管理")
@CrossOrigin//利用后端注解的方式解决跨域请求
public class ShopCartController {
@Autowired
private ShoppingCartService shoppingCartService;
@ApiOperation("购物车添加接口")
@ApiImplicitParams({
@ApiImplicitParam(dataType = "string",name = "token", value = "授权令牌",required =true),
@ApiImplicitParam(dataType = "ShoppingCart",name = "shopCart", value = "购物车信息",required =true)
})
@PostMapping("/add")
public ResultVO addShopCarts(@RequestBody ShoppingCart shopCart,@RequestHeader("token") String token){
ResultVO resultVO = shoppingCartService.addShoppingCart(shopCart, token);
return resultVO;
}
}
-
先在登录界面拿到token值,然后使用swagger接口测试
-
或者在后端控制台拿token,我打印出来了
-
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-otZMKOQC-1690696066624)(D:/Typora/img/image-20220913221325243.png)]
-
测试成功[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Oq7eJzWT-1690696066625)(D:/Typora/img/image-20220913221205192.png)]
-
-
前端传值,添加传输的用户id
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mzNhZpDT-1690696066630)(D:/Typora/img/image-20220913214239973.png)]
16.3 前端实现
16.3.1 记录选择的套餐属性
详细的代码优化过程,请查看[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cpvteNJr-1690696066631)(D:/Typora/img/image-20220915102041897.png)]
- 在vue的data中定义
chooseSkuProps
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-svF4Lgw0-1690696074988)(D:/Typora/img/image-20220911093855025.png)] |
- 为sku的属性添加点击事件
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8xiQVkUU-1690696074988)(D:/Typora/img/image-20220911093935601.png)] |
- 在methods中定义事件函数
changeProp
但这个事件,只有点击属性时才会记录,如果我们只是切换套餐(使用默认的套餐属性),那么没有点击,就不会记录。 |
---|
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4uFPjt4C-1690696074989)(D:/Typora/img/image-20220911094006015.png)] |
- 添加套餐切换的监听事件:
为了解决没点击套餐属性不能记录的问题,我们给套餐添加一个监听器,当套餐切换时,自动记录默认属性 |
---|
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DpEDnMLu-1690696074990)(D:/Typora/img/image-20220911094031184.png)] |
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aIUAdoUN-1690696066632)(D:/Typora/img/image-20220915150856613.png)]
16.3.2 套餐属性选中效果
- 在套餐属性标签上添加name属性
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KDUaErqJ-1690696074991)(D:/Typora/img/image-20220911094053929.png)] |
- 在属性的点击事件函数实现选中效果
removeClass先删除所有同类属性选中效果。然后在给点击的选单独项添加选中效果。 |
---|
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BVzPQ2h7-1690696074991)(D:/Typora/img/image-20220911094123534.png)] |
16.3.3 修改商品数量
- 在vue的data中定义 num 存储商品数量(默认值为1)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qFAb0Qca-1690696066633)(D:/Typora/img/image-20220915104706028.png)]
- 为+,-添加点击事件监听
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-V0Jfta6t-1690696074992)(D:/Typora/img/image-20220911094152219.png)] |
- 定义点击事件函数
parseInt(this.num):这样写的原因是,(没使用加减)直接在库存输入框里写数量会默认为字符串类型,然后点击+、-会导致在字符串后面+1或-1。而转化成int类型,库存就会正常加减 |
---|
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9gALFMaT-1690696074993)(D:/Typora/img/image-20220911094216466.png)] |
16.3.4 提交购物⻋
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XXZBGcoJ-1690696074995)(D:/Typora/img/image-20220915105559217.png)] |
---|
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nknKtMvs-1690696074998)(D:/Typora/img/image-20220915105755819.png)] |
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tIuQ8UwG-1690696075001)(D:/Typora/img/image-20220915110009259.png)] |
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yINK3NS8-1690696075002)(D:/Typora/img/image-20220911094255024.png)] |
- 注:因为后端要接收添加购物车的时间,我们前端没有给值,那么可以在后端给值。
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gMRk6F2d-1690696066633)(D:/Typora/img/image-20220915111726934.png)]
- 添加成功[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HGBtkpk5-1690696066634)(D:/Typora/img/image-20220915165257925.png)]
⼗七、购物⻋—添加购物⻋(未登录状态)
17.1 流程分析
这里我们使用第3种方式:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qAhqAI67-1690696066636)(D:/Typora/img/image-20220912103254234.png)]
17.2 功能实现
17.2.1 定义新的状态码
之前的状态码,只有成功或者不成功,已经不能满足我们的需求,所以我们定义新的状态码。
ResStatus |
---|
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VUlIQ7ZC-1690696075002)(D:/Typora/img/image-20220912103338780.png)] |
登录认证拦截器 |
---|
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-akPZ3Z1h-1690696075003)(D:/Typora/img/image-20220912103402228.png)] |
17.2.2 在详情⻚⾯判断如果⽤户未登录,则跳转到登录⻚⾯
introduction.html |
---|
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Bj2XV9Ld-1690696075004)(D:/Typora/img/image-20220912103448480.png)] |
17.2.3 登录⻚⾯接收回跳信息
login.html |
---|
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TfC0brZt-1690696075004)(D:/Typora/img/image-20220912103516103.png)] |
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vMCmFt8B-1690696075005)(D:/Typora/img/image-20220912103532912.png)] |
17.2.4 回到详情⻚时接收参数
introduction.html |
---|
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0dWzW1DI-1690696075006)(D:/Typora/img/image-20220912103605526.png)] |
17.2.5 使⽤layui添加购物⻋成功/失败进⾏提示
- 引⼊layui layui.com
<!-- 引⼊ layui.css,放到css下面 -->
<link rel="stylesheet"
href="//unpkg.com/layui@2.6.5/dist/css/layui.css">
<!-- 引⼊ layui.js放到jquery和js下面 -->
<script src="//unpkg.com/layui@2.6.5/dist/layui.js">
- 声明弹窗组件
定义到vue上面,且为全局变量,这样就可以供vue使用 |
---|
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eBvGx5Ci-1690696075006)(D:/Typora/img/image-20220912103707757.png)] |
- 当添加购物⻋成功或者失败的时候,进⾏提示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-s84JNoMU-1690696075007)(D:/Typora/img/image-20220912103730178.png)] |
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-60RxtdOH-1690696066637)(D:/Typora/img/image-20220915182012008.png)]
⼗⼋、购物⻋—购物⻋列表
18.1 流程分析
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IY8uMRSV-1690696066638)(D:/Typora/img/image-20220912103804868.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KqBo1pn4-1690696066639)(D:/Typora/img/image-20220916202152382.png)]
18.2 接⼝实现
18.2.1 数据库实现
- SQL
<!--根据用户ID查询用户当前的购物车信息(连接查询,所需的表:shopping_cart、product、product_img,product_sku)-->
SELECT
c.cart_id,
c.product_id,
c.sku_id,
c.user_id,
c.cart_num,
c.cart_time,
c.product_price,
c.sku_props,
p.product_name,
i.url,
s.original_price,
s.sell_price,
s.sku_name
from shopping_cart c
INNER JOIN product p
INNER JOIN product_img i
INNER JOIN product_sku s
on c.product_id=p.product_id
and p.product_id=i.item_id
and c.sku_id=s.sku_id
where user_id=#{userId}
and i.is_main=1;<!--查询商品的主图片,也就是is_main=1的图片-->
- 实体类
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HvV1fk2H-1690696075008)(D:/Typora/img/image-20220912104111903.png)] |
- 在Mapper接⼝定义查询⽅法
@Repository
public interface ShoppingCartMapper extends GeneralDAO<ShoppingCart> {
public List<ShoppingCartVO> selectShopcartByUserId(int userId);
}
- 映射配置
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6Hl3c88u-1690696075008)(D:/Typora/img/image-20220912104156370.png)] |
18.2.2 业务层实现
- Service接⼝
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-F10cxcdQ-1690696075010)(D:/Typora/img/image-20220916164749100.png)] |
- Service实现类
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WSTGjEPF-1690696075014)(D:/Typora/img/image-20220916164935064.png)] |
18.2.3 控制层实现
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uijSkMk8-1690696066652)(D:/Typora/img/image-20220916165054302.png)]
18.3 前端实现
18.3.1 购物车界面(shopcart.html)
- 判断是否登录成功,成功传输用户id,连接后端购物车显示接口,显示购物车列表信息。没有登录(登陆失败)存储回调信息(商品id、回到路径…),跳转到登录界面。
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yB24Q0E7-1690696066654)(D:/Typora/img/image-20220916215206790.png)]
18.3.2 登录界面(login.html)
- 登录界面,接收到传递过来的参数,进行判断,具体跳转页面。
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-o1e8H51B-1690696066655)(D:/Typora/img/image-20220916215420933.png)]
18.3.2 购物车界面动态显示信息
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UiLBXkCa-1690696066656)(D:/Typora/img/image-20220916215611769.png)]
- 显示购物车列表
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dUG6afkN-1690696066657)(D:/Typora/img/image-20220916215934951.png)]
- 显示商品图片
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LqTkoOdB-1690696066658)(D:/Typora/img/image-20220916220019306.png)]
- 显示商品名称,点击名称跳转到对应商品详情页
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lHFOiyOd-1690696066662)(D:/Typora/img/image-20220916220114145.png)]
- 显示商品套餐
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-No4ck1iU-1690696066664)(D:/Typora/img/image-20220916220203589.png)]
- 显示商品价格
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AwCMFtE7-1690696066665)(D:/Typora/img/image-20220916220230138.png)]
- 显示商品数量
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KNDwXeTl-1690696066666)(D:/Typora/img/image-20220916220307182.png)]
- 显示商品总价格
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZrevK5HD-1690696066667)(D:/Typora/img/image-20220916220332581.png)]
⼗九、购物⻋-修改购物⻋数量与删除购物车指定商品
19.1修改购物车数量
19.1.1 流程分析
通过cartId(商品id)修改cartNum(购物车商品)的数量
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uvjZJwSu-1690696066668)(D:/Typora/img/image-20220916180542656.png)]
19.1.2 接⼝实现
19.1.2.1 数据库层实现
- sql
UPDATE shopping_cart SET cart_num=6 WHERE cart_id=7;
- 在Mapper接⼝定义修改⽅法
- 只要数据库操作需要2个及以上的参数,都用@Param修饰
@Repository
public interface ShoppingCartMapper extends GeneralDAO<ShoppingCart> {
public List<ShoppingCartVO> selectShopcartByUserId(int userId);
public int updateCartnumByCartid(@Param("cartId") int cartId,
@Param("cartNum") int cartNum);
}
- 映射配置
<update id="updateCartnumByCartid">
update shopping_cart set cart_num=#{cartNum} where cart_id=#
{cartId}
</update>
19.1.2.2 业务层实现
- Service接⼝
public interface ShoppingCartService {
public ResultVO addShoppingCart(ShoppingCart cart);
public ResultVO listShoppingCartsByUserId(int userId);
public ResultVO updateCartNum(int cartId,int cartNum);
}
- Service实现类
@Service
public class ShoppingCartServiceImpl implements ShoppingCartService
{
@Autowired
private ShoppingCartMapper shoppingCartMapper;
private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd
hh:mm:ss");
@Override
public ResultVO updateCartNum(int cartId, int cartNum) {
int i = shoppingCartMapper.updateCartnumByCartid(cartId,
cartNum);
if(i>0){
return new ResultVO(ResStatus.OK,"update
success",null);
}else{
return new ResultVO(ResStatus.NO,"update fail",null);
}
}
}
19.1.2.3 控制层实现
- ShopCartController接口
- @PathVariable()拿到路径上的值cid与cnum(@PutMapping(“/update/{cid}/{cnum}”))
@PutMapping("/update/{cid}/{cnum}")
public ResultVO updateNum(@PathVariable("cid") Integer cartId,
@PathVariable("cnum") Integer cartNum,
@RequestHeader("token") String token){
ResultVO resultVO = shoppingCartService.updateCartNum(cartId,
cartNum);
return resultVO;
}
19.1.2.4 swagger测试
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-c8gMqV5s-1690696066670)(D:/Typora/img/image-20220917153136457.png)]
19.1.3 前端实现
- 为按钮添加点击事件
:data-id="index"为通过商品下标为按钮绑定id,确定为哪个商品进行加减操作 |
---|
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tziWTqae-1690696075020)(D:/Typora/img/image-20220912104606857.png)] |
- 定义changeNum事件函数
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-a0Dur2MK-1690696075021)(D:/Typora/img/image-20220912104633888.png)] |
19.2 删除购物车指定商品
19.2.1 流程分析
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5D3xruqW-1690696066670)(D:/Typora/img/image-20220917154442407.png)]
19.2.2 接口实现
19.2.2.1 数据库层实现
- sql
DELETE FROM shopping_cart WHERE cart_id=22;
-
在mapper接口中定义删除方法
-
ShoppingCartMapper
-
//删除指定购物车商品 public int deleteShopcartByCartId(int cartId);
-
-
修改相应的mapper.xml映射文件
-
ShoppingCartMapper.xml
-
<!-- 删除指定购物车商品--> <delete id="deleteShopcartByCartId"> DELETE FROM shopping_cart WHERE cart_id=#{cartId}; </delete>
-
19.2.2.2 业务层实现
-
ShoppingCartService接口
-
public ResultVO deleteShopcartByCartid(int cartId)
-
-
ShoppingCartServiceImpl接口实现
-
@Transactional(propagation = Propagation.SUPPORTS) public ResultVO deleteShopcartByCartid(int cartId) { int i = shoppingCartMapper.deleteShopcartByCartId(cartId); if(i>0){//表示删除成功 return new ResultVO(BaseCode.ok,"delete sucess",null); } //否则删除失败 return new ResultVO(BaseCode.no,"delete fasle",null); }
-
19.2.2.3 控制层实现
-
ShopCartController控制接口
- 删除购物车商品实现
@ApiOperation("删除购物车指定商品的接口") @ApiImplicitParams({ @ApiImplicitParam(dataType = "string",name = "token", value = "授权令牌",required =true), @ApiImplicitParam(dataType = "Integer",name = "cid", value = "购物车id",required =true) }) @DeleteMapping("/delete/{cid}") public ResultVO deleteShopCart(@PathVariable("cid") int cartId, @RequestHeader("token")String token){ ResultVO resultVO = shoppingCartService.deleteShopcartByCartid(cartId); return resultVO; }
19.2.2.4 swagger接口测试
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YxcGWEfZ-1690696066671)(D:/Typora/img/image-20220917160509068.png)]
19.2.3 前端实现
- 为删除按钮添加事件:绑定要删除的商品下标
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-raZ73baF-1690696066672)(D:/Typora/img/image-20220917173556813.png)]
- 定义deleteProduct事件函数删除成功页面进行刷新
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QXD6XnR1-1690696066673)(D:/Typora/img/image-20220917173705632.png)]
⼆⼗、购物⻋—结算(提交订单)
在购物⻋列表中选择对应的的商品之后,点击提交⽣成订单的过程
20.1 流程图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tWYOnNit-1690696066676)(D:/Typora/img/image-20220912104711107.png)]
20.2 接⼝实现
20.2.1 收货地址列表接⼝
此操作的数据库实现可以通过tkmapper通⽤⽅法完成
通过用户id 查询收货地址接口
- service接⼝
UserAddrService
public interface UserAddrService {
public ResultVO listAddrsByUid(int userId);
}
- Service实现类
UserAddrServiceImpl
因为使用的tkmapper默认的查询方法,所以想要使用条件查询,就要new Example()。
@Service
public class UserAddrServiceImpl implements UserAddrService {
@Autowired
private UserAddrMapper userAddrMapper;
@Override
@Transactional(propagation = Propagation.SUPPORTS)
public ResultVO listAddrsByUid(int userId) {
Example example = new Example(UserAddr.class);
Example.Criteria criteria = example.createCriteria();
criteria.andEqualTo("userId",userId);
criteria.andEqualTo("status",1);//status=1表示状态为正常显示
List<UserAddr> userAddrs = userAddrMapper.selectByExample(example);
return new ResultVO(BaseCode.ok,"sucess",userAddrs);
}
}
- 控制器实现
@RestController
@CrossOrigin
@RequestMapping("/userAddr")
@Api(value = "提供用户收货地址相关的接口",tags = "收货地址管理")
public class UserAddrController {
@Autowired
private UserAddrService userAddrService;
@ApiOperation("查询用户地址的接口")
@ApiImplicitParams({
@ApiImplicitParam(dataType = "string",name = "token", value = "授权令牌",required =true),
@ApiImplicitParam(dataType = "Integer",name = "userId", value = "用户id",required =true)
})
@GetMapping("/SelectAddrlist")
public ResultVO listUserAddrByUserId(Integer userId,@RequestHeader("token") String token){
ResultVO resultVO = userAddrService.listAddrsByUid(userId);
return resultVO;
}
}
20.2.2 购物⻋记录列表接⼝
根据⼀个ID的集合,查询购物⻋记录,实现⽅式有两种:
- 动态sql
<select id="searchShoppingCartById" resultMap="shopCartMap"> select * from shopping_cart where cart_id in <foreach collection="list" item="cid" separator="," open="(" close=")"> #{cid} </foreach> </select>
tkMapper条件查询:如果我们使用tkMapper自带的查询,那我们只能查询到ShoppingCart中的信息,而没办法查询到扩展的ShoppingCartVO中的信息。
criteria.andIn(“cartId”,ids);
注:ids是一个存储cartId的集合
而我们不只需要查询到ShoppingCart中的信息,我们还需要查询到扩展的商品名称、商品图片、以及用户等信息。所以我们选用第一种方法,自定义的动态sql查询。
- Mapper接⼝定义查询⽅法
数据库中的cid是integer类型 |
---|
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xQ7SNuhk-1690696075022)(D:/Typora/img/image-20220912105020303.png)] |
- 映射配置(动态sql foreach)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-k18YMB2a-1690696075024)(D:/Typora/img/image-20220912105048070.png)] |
---|
collection="cids"是前端传过来的id集合,经过遍历每次拿取到一个cid[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dgmgVAvS-1690696075025)(D:/Typora/img/image-20220912105100851.png)] |
- Service接⼝
而我们service层是传的String类型的cids |
---|
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4y5yzOfO-1690696075026)(D:/Typora/img/image-20220912105123479.png)] |
- Service实现类
service层传输cid到mapper层,因前端传过来的cids是String类型(1,2,3)以逗号分割,所以我们在业务层需要把String类型转化为int类型 |
---|
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7ykBRn7c-1690696075028)(D:/Typora/img/image-20220912105148381.png)] |
- 控制器实现
@GetMapping("/listbycids")
@ApiImplicitParam(dataType = "String",name = "cids", value = "选择的购
物⻋记录id",required = true)
public ResultVO listByCids(String cids,
@RequestHeader("token")String token){
ResultVO resultVO =
shoppingCartService.listShoppingCartsByCids(cids);
return resultVO; }
- swagger接口测试
- cartIds:输入成字符串类型,并且以逗号分割(例如:5,8),因为在业务层中我们,我们所做的操作即使根据逗号分割字符串
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cEyO9kLV-1690696066680)(D:/Typora/img/image-20220917225841098.png)]
20.2.3 保存订单(做完提交订单,跳转到订单界面时,在写此功能)
20.3 前端实现
20.3.1 选择购物⻋记录价格联动
- 列表前的复选框标签
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TmcF98PX-1690696066681)(D:/Typora/img/image-20220918162439806.png)]
绑定复选框的index下标、添加双向绑定 v-model=“opts”(绑定的是一个数组,用来存储被绑定的复选框对应的商品下标) |
---|
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TqU1HLXo-1690696075030)(D:/Typora/img/image-20220912105237847.png)] |
- 渲染商品数量以及总价格
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yBK2dcKF-1690696075035)(D:/Typora/img/image-20220912105259989.png)] |
-
在vue示例的data中声明opts和totalPrice,并且监听opts选项的改变—选项⼀旦改变就
计算总价格
- 这里,当opts所选商品发生变化时,下方选择总数量与价格都要发生变化。
- 还要考虑当选择完商品之后,改变商品数量时,价格也要动态改变,所以在修改数量成功后,我们依然要对价格进行操作
watch{}:监听属性是否改变,如果改变就执行该函数。 |
---|
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JGiN398c-1690696075038)(D:/Typora/img/image-20220912105324777.png)] |
20.3.2 点击“结算”跳转到订单添加⻚⾯
在购物⻋列表⻚⾯,选择购物⻋记录,点击“结算之后”将选择的购物⻋记录ID传递到
order-add.html
- shopcart.html
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OZKAASjn-1690696075039)(D:/Typora/img/image-20220912105412146.png)] |
---|
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JqUAZO7Y-1690696075040)(D:/Typora/img/image-20220912105422695.png)] |
- order-add.html
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MldwkNnJ-1690696075041)(D:/Typora/img/image-20220912105443701.png)] |
20.3.3 显示收货地址及订单商品
-
先在后端配置拦截器,拦截显示收货地址的接口路径(用户的收货地址为受限资源)
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7y5B2in1-1690696066682)(D:/Typora/img/image-20220918173228278.png)]
-
前端数据
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZstfbSZ7-1690696066683)(D:/Typora/img/image-20220918182055912.png)]
20.3.3.1显示收货地址
- 获取数据
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IwPYBNY5-1690696066684)(D:/Typora/img/image-20220918181907437.png)]
-
显示数据
-
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Wz1ky4V0-1690696066684)(D:/Typora/img/image-20220918182921766.png)]
-
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-x1pqbrDg-1690696066685)(D:/Typora/img/image-20220918182854424.png)]
-
20.3.3.2 显示订单商品
- 获取数据
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LDr4jD7w-1690696066686)(D:/Typora/img/image-20220918210430425.png)]
- 显示数据
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gW4vchTS-1690696066687)(D:/Typora/img/image-20220918184530230.png)]
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-THe3GW44-1690696066687)(D:/Typora/img/image-20220918210612495.png)]
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EQum8R5z-1690696066688)(D:/Typora/img/image-20220918184900879.png)]
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IFi47u7H-1690696066689)(D:/Typora/img/image-20220918210548743.png)]
20.3.4 订单确认⻚⾯选择地址
-
给地址绑定点击事件,当选择地址时下方地址发生动态变化。
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KrzmNF5a-1690696066690)(D:/Typora/img/image-20220918205637526.png)]
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sUEg1MdB-1690696066691)(D:/Typora/img/image-20220918210208338.png)]
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aGiyyXjj-1690696066692)(D:/Typora/img/image-20220918210320541.png)]
-
添加选择支付方式(微信或者支付宝)
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Z9jVcJzS-1690696066696)(D:/Typora/img/image-20220921201004288.png)]
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ezEbQHMd-1690696066698)(D:/Typora/img/image-20220921201249878.png)]
-
给买家留言进行双向绑定
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CBkpTgiD-1690696066699)(D:/Typora/img/image-20220921214329476.png)]
⼆⼗⼀、订单提交及⽀付
21.1 流程分析
- 订单提交及支付的基础版本(未完善版本)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nAdNBBH8-1690696066701)(D:/Typora/img/image-20220912105518834.png)]
21.2 订单添加接⼝实现
21.2.1 数据库操作
-
根据收货地址ID,获取收货地址信息(使用tkMapper):收货⼈姓名、电话、地址也可以从前端传递过来
-
根据购物⻋ID,查询购物⻋详情(tkMapper不能满足要求,需要手动操作,自定义关联查询:商品名称、sku名称、 库存 、商品图⽚、商品价格)———》获取生成商品快照的数据
改造: ShoppingCartMapper 中的 selectShopcartByCids,而通过购物车id查询商品信息使用之前的方法即可,因为我们需要商品的库存,所以改造即可。 |
---|
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lvixwVhA-1690696075041)(D:/Typora/img/image-20220912105604621.png)] |
新增ShoppingCartVOMap2[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QFz8kJPe-1690696075042)(D:/Typora/img/image-20220912105622737.png)] |
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-avRrQwuD-1690696075044)(D:/Typora/img/image-20220912105632343.png)] |
- 保存订单(tkMapper)
- 修改库存(tkMapper)
- 保存商品快照(tkMapper)
21.2.2 业务层实现
@Service
public class OrderServiceImpl implements OrderService {
//购物车Mapper
@Autowired
private ShoppingCartMapper shoppingCartMapper;
//订单Mapper
@Autowired
private OrdersMapper ordersMapper;
//订单快照Mapper
@Autowired
private OrderItemMapper orderItemMapper;
//商品套餐表
@Autowired
private ProductSkuMapper productSkuMapper;
/**
* 保存订单业务
*/
@Transactional
//传值String cids,Orders order,因为前端界面传过来的信息为(用户地址、用户信息、购物车商品套餐信息)。而订单里包含用户信息(通过用户id可以查询到用户地址),但不包含商品信息,所以我们需要单独传过来商品信息(肯定不止一个商品,所以我们依然可以通过购物车列表来传值,购物车列表可以存储多商品),cids用来存储购物车列表(每一个cid用来存储一个购物车得id,通过购物车id可以查询购物车商品套餐相关信息)
public ResultVO addOrder(String cids, Orders order) throws SQLException {
Map<String,String> orderInfo = new HashMap<>();//供微信支付使用,支付完成后,响应给前端的信息
//1.校验库存:根据cids查询当前订单中关联的购物⻋记录详情(包括库存)
String[] arr = cids.split(",");
//因为前端传过来的cids字符串类型,而我们数据库中的每一个购物车id都是int类型,所以我们需要进行转换
List<Integer> cidsList = new ArrayList<>();
for (int i = 0; i <arr.length ; i++) {
cidsList.add(Integer.parseInt(arr[i]));
}
List<ShoppingCartVO> list = shoppingCartMapper.selectShopcartByCids(cidsList);//数据库中cid是Int类型
boolean f = true;
String untitled = "";//数据库中表示:产品名称(多个产品用,隔开)
for (ShoppingCartVO sc: list) {//如果下单商品数量>库存的话
if(Integer.parseInt(sc.getCartNum()) > sc.getSkuStock()){
f = false;//下单失败、不能下单,就没必要对订单中的属性,产品名称赋值
}
//当如果下单商品数量<库存的话,可以下单,对产品,名称进行赋值
//获取所有商品名称,以,分割拼接成字符串
untitled = untitled+sc.getProductName()+",";
}
if(f){
//2.保存订单
order.setUntitled(untitled);
order.setCreateTime(new Date());
order.setStatus("1");
//⽣成订单编号,因为数据库中id没有设置自动增长,所以我们需要手动生成
String orderId = UUID.randomUUID().toString().replace("-",
"");//因为uuid生成的32位中,含有"-"字符,我们可以该字符忽略掉
order.setOrderId(orderId);
int i = ordersMapper.insert(order);
//3.⽣成商品快照(保存订单之后才生成)
for (ShoppingCartVO sc: list) {
int cnum = Integer.parseInt(sc.getCartNum());
//自定义订单项id的生成方式:itemId
//“System.currentTimeMillis()用于获取当前系统时间,以毫秒为单位 获取程序开始时间及结束时间,二者之差即为程序运行时间;
String itemId = System.currentTimeMillis()+""+ (new
Random().nextInt(89999)+10000);
OrderItem orderItem = new OrderItem(itemId, orderId,
sc.getProductId(), sc.getProductName(), sc.getProductImg(),
sc.getSkuId(), sc.getSkuName(), new BigDecimal(sc.getSellPrice()),
cnum, new BigDecimal(sc.getSellPrice() * cnum), new Date(), new Date(),
0);
//new BigDecimal(sc.getSellPrice()):因为数据库中product_price和total_amount都是decimal类型的,所以这里需要转换
orderItemMapper.insert(orderItem);
}
//4.扣减库存:根据套餐ID修改套餐库存量
for (ShoppingCartVO sc: list) {
String skuId = sc.getSkuId();
int newStock = sc.getSkuStock()-
Integer.parseInt(sc.getCartNum());
ProductSku productSku = new ProductSku();
productSku.setSkuId(skuId);
productSku.setStock(newStock);
//updateByPrimaryKeySelective():通过主键修改指定数据(tkMapper中的方法,还有很多,可以扩展了解,根据需要合理使用)
productSkuMapper.updateByPrimaryKeySelective(productSku);
}
//5.删除购物⻋:当购物⻋中的记录购买成功之后,购物⻋中对应做删除操作
for (int cid: cidsList) {
shoppingCartMapper.deleteByPrimaryKey(cid);
}
//存放orderId与untitled的原因是,供微信支付使用
orderInfo.put("orderId",orderId);//商品订单
orderInfo.put("productNames",untitled);//商品迷城
//6.微信支付(以上操作成功后,执行微信支付功能)
//设置当前订单信息
HashMap<String, String> data = new HashMap<>();
data.put("body",untitled);//商品描述——商品名称
data.put("out_trade_no",orderId);//使⽤当前⽤户订单的编号作为当前⽀付交易的交易号
data.put("fee_type","CNY");//支付币种——人民币
data.put("total_fee",order.getActualAmount()*100+"");//支付总金额,单位是分(所以*100),集合data存储String类型(所以+“”)、
data.put("trade_type","NATIVE");//交易类型——二维码链接
data.put("notify_url","/pay/callback");//设置⽀付完成时的回调⽅法接⼝——可以是payController接口中的路径
//发送请求,获取响应
//微信⽀付:申请⽀付连接
WXPay wxPay = new WXPay(new MyPayConfig());
try {
Map<String, String> resp = wxPay.unifiedOrder(data);
orderInfo.put("payUrl",resp.get("code_url"));//接收回调支付路径/支付链接
return new ResultVO(BaseCode.ok,"Pay and Add Oredr sucesss!!",orderInfo);
} catch (Exception e) {
e.printStackTrace();
}
}else{
//表示库存不⾜
return new ResultVO(BaseCode.no,"Pay and Add Oredr false!!",null);
}
return new ResultVO(BaseCode.no,"Pay and Add Oredr false!!",null);
}
}
- 进行单元测试
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VJ0fgg3g-1690696066702)(D:/Typora/img/image-20220919212048035.png)]
- 这些信息是必须从页面传值的,swagger测试时只需要添加这些信息即可
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-L6gYEPBB-1690696066703)(D:/Typora/img/image-20220919211848137.png)]
- 生成商品快照时,出现小bug(我们指定了itemId,在没有使用自动增长的情况下,并不能使用insertList方法)
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fJ15OQ4I-1690696066704)(D:/Typora/img/image-20220919212415362.png)]
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FjHFhe5V-1690696066705)(D:/Typora/img/image-20220919212527210.png)]
- 没有使用该注解进行赋值[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WnKeSCwo-1690696066706)(D:/Typora/img/image-20220919212616193.png)]
- 后端有id值,但他却没用该值[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HZ55DjcW-1690696066707)(D:/Typora/img/image-20220919212804288.png)]
- 优化,我们不在使用insertList一次存储多个快照,我们改用insert每次存储一个快照[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iFC5VhoT-1690696066711)(D:/Typora/img/image-20220919213250658.png)]
- 下单成功
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bEMxbxAK-1690696066711)(D:/Typora/img/image-20220919213356700.png)]
- 订单表[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-X1EiYcad-1690696066714)(D:/Typora/img/image-20220919213424464.png)]
- 快照表[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AWBJHeZ4-1690696066718)(D:/Typora/img/image-20220919213506188.png)]
- 套餐表中库存成功发生变化
注:这里有一个问题,快照发生异常时,快照表操作失败,但在异常之前操作的订单表却操作成功,这里出现数据库操作不一致问题,所以我们需要考虑事务的管理
保存订单中的步骤:
- 校验库存(库存满足才执行其他操作)
- 保存订单
- ⽣成商品快照(保存订单之后才生成)
- 扣减库存:根据套餐ID修改套餐库存量
- 删除购物⻋:当购物⻋中的记录购买成功之后,购物⻋中对应做删除操作
- 微信进行支付
无论是保存订单中的哪一步操作出现问题,我们都应该保持数据的一致性,进行事务的回滚操作。因为保存订单是一个整体。
@Transactional:在add订单方法上加上该事务,即可完成相应的操作(保持数据一致性)。
21.2.3 订单添加接⼝实现
- 订单保存
- 申请⽀付链接
- 一般支付类型为支付宝和微信,但支付宝只有商家才能申请支付链接,我们这里采用微信支付
- 具体微信支付详情请参考文档:(锋迷商城)微信支付参考文档
@RestController
@CrossOrigin
@RequestMapping("/order")
@Api(value = "提供订单相关操作的接口",tags = "订单管理")
public class OrderController {
@Autowired
private OrderService orderService;
@ApiOperation("添加订单的接口")
@ApiImplicitParams({
@ApiImplicitParam(dataType = "Orders",name = "orders", value = "订单信息",required =true),
@ApiImplicitParam(dataType = "String",name = "cids", value = "多个购物车id",required =true)
})
@PostMapping("/addOredr")
public ResultVO addOrder(String cids, @RequestBody Orders orders) {
try {
ResultVO resultVO = orderService.addOrder(cids, orders);
return resultVO;
} catch (SQLException e) {
e.printStackTrace();
return new ResultVO(BaseCode.no,"Pay and Add Oredr false!!",null);
}
}
}
21.2.4 swagger接口测试
-
测试接口,得到返回信息
- payUrI就是微信返回的二维码支付链接[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZEoS4EnD-1690696066720)(D:/Typora/img/image-20220920220014073.png)]
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Prf6yGHX-1690696066720)(D:/Typora/img/image-20220920215952306.png)]
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-szraWWDb-1690696066721)(D:/Typora/img/image-20220920220859208.png)]
-
解析二维码
-
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-E3I3zeC8-1690696066722)(D:/Typora/img/image-20220920220117792.png)]
-
扫码测试
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FQQ0yUz0-1690696066722)(D:/Typora/img/image-20220920220209177.png)]
-
21.3 前端提交订单信息
21.3.1 order-add.html 页面提交订单信息
- 点击“提交订单”按钮,触发
doSubmit
order-add.html |
---|
给提交按钮绑定点击事件 |
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LDLlQ1sW-1690696075053)(D:/Typora/img/image-20220921201509602.png)] |
编写点击事件 |
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Dl9KGBUV-1690696075054)(D:/Typora/img/image-20220912135338290.png)] |
21.3.2 order-pay.html接收订单信息,并进行页面显示
-
order-pay.html
-
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8W1I7PBl-1690696066723)(D:/Typora/img/image-20220921204626567.png)]
-
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jqGyitZz-1690696066723)(D:/Typora/img/image-20220921204717796.png)]
-
界面展示,拿到orderInfo信息进行展示
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Wy9FW9O5-1690696066724)(D:/Typora/img/image-20220921214456842.png)]
-
21.3.3 qrcode技术生成支付二维码
- 练习:
- 导入jquery与qrcode的文件[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kLmbnPXP-1690696066726)(D:/Typora/img/image-20220921220959755.png)]
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vHbdKB29-1690696066731)(D:/Typora/img/image-20220921220902476.png)]
- order-pay.html页面,显示支付二维码
- 引入qrcode依赖[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WT6vowPK-1690696066732)(D:/Typora/img/image-20220921221305053.png)]
- 把原先存放图片的位置,换成div用来存放微信支付二维码[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ukFDUZ2j-1690696066734)(D:/Typora/img/image-20220921221654725.png)]
- 显示二维码出现的问题
- 以下是Vue的生命周期[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-htCXFILD-1690696066735)(D:/Typora/img/image-20220921222433560.png)]
- 放在created中,二维码会不显示,原因是先渲染了数据,再加载了模板(存储微信支付二维码的div),导致加载模板时数据为空
- 放在加载模板数据之后,数据成功渲染,二维码成功显示
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CV79pJMw-1690696066736)(D:/Typora/img/image-20220921222918518.png)]
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BczgURE6-1690696066737)(D:/Typora/img/image-20220921223026137.png)]
21.4 ⽀付回调
⽀付回调:当⽤户⽀付成功之后,⽀付平台会向我们指定的服务器接⼝发送请求传递订
单⽀付状态数据
21.4.1 创建⼀个控制器定义回调接⼝(在实现Ngrok内网穿透后编写此接口)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3vsKI4lg-1690696066738)(D:/Typora/img/image-20220922112949460.png)]
@RestController
@RequestMapping("/pay")
public class PayController {
@Autowired
private OrderService orderService;
/**
* 回调接⼝:当⽤户⽀付成功之后,微信⽀付平台就会请求这个接⼝,将⽀付状态的数据
传递过来
*
*/
@RequestMapping("/callback")
public String paySuccess(HttpServletRequest request) throws
Exception {
System.out.println("--------------------callback");
// 1.接收微信⽀付平台传递的数据(使⽤request的输⼊流接收)
ServletInputStream is = request.getInputStream();
byte[] bs = new byte[1024];
int len = -1;
StringBuilder builder = new StringBuilder();
while((len = is.read(bs))!=-1){
builder.append(new String(bs,0,len));
}
String s = builder.toString();
//使⽤帮助类将xml接⼝的字符串装换成map
Map<String, String> map = WXPayUtil.xmlToMap(s);
if(map!=null &&
"success".equalsIgnoreCase(map.get("result_code"))){
//⽀付成功
//2.修改订单状态为“待发货/已⽀付”
String orderId = map.get("out_trade_no");
int i = orderService.updateOrderStatus(orderId, "2");
System.out.println("--orderId:"+orderId);
//3.响应微信⽀付平台
if(i>0){
HashMap<String,String> resp = new HashMap<>();
resp.put("return_code","success");
resp.put("return_msg","OK");
resp.put("appid",map.get("appid"));
resp.put("result_code","success");
return WXPayUtil.mapToXml(resp);
}
}
return null;
}
}
21.4.2 设置回调URL
在订单接⼝中申请⽀付连接的时候将回调接⼝的路径设置给微信⽀付平台
OrderController |
---|
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9fdYeYfX-1690696075055)(D:/Typora/img/image-20220912135631562.png)] |
思考:如果按照上图所示的配置,当⽤户⽀付成功之后,微信⽀付平台会向( http://192.168.55.3:8080/pay/callback )发送请求,因为我们的服务端项⽬是运⾏在本地计算机的(IP为内⽹IP),微信平台没办法访问 —— 内⽹穿透
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JT0rPONk-1690696066739)(D:/Typora/img/image-20220922095313202.png)]
21.5 Ngrok实现内⽹穿透
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CDz9qIF7-1690696066741)(D:/Typora/img/image-20220922101300081.png)]
21.5.1 注册帐号,申请隧道ID
-
注册 www.ngrok.cc
-
开通隧道
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Quz6g9V3-1690696066742)(D:/Typora/img/image-20220922101533742.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1mbv6xen-1690696066747)(D:/Typora/img/image-20220912135746119.png)]
- 获取隧道ID:118c7bfe1ac90369
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WAur2jF8-1690696066748)(D:/Typora/img/image-20220912135801681.png)]
21.5.2 下载ngrok客户端
- https://ngrok.cc/download.html
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MiIMYugO-1690696066749)(D:/Typora/img/image-20220922104938059.png)]
21.5.3 启动客户端
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1vsBSaRv-1690696066750)(D:/Typora/img/image-20220922105402638.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kQ2N3Jhm-1690696066751)(D:/Typora/img/image-20220922105438465.png)]
访问 http://zsh.free.idcfengye.com
就相当于访问本地的8080
测试:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JohcAk6C-1690696066752)(D:/Typora/img/image-20220922105858019.png)]
后端出现:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EcLfEumt-1690696066753)(D:/Typora/img/image-20220922105926955.png)]
21.5.4 订单修改操作(当支付成功后执行修改操作)
-
mapper操作,单表修改使用tkMapper即可
-
OrederService.java 接口
- 修改订单状态的方法[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-deszcbtG-1690696066754)(D:/Typora/img/image-20220922112302857.png)]
-
OrderServiceImpl.java 接口实现类
- 修改orderServiceImpl中的addOrder添加购物车的方法
- 测试支付金额为1分,路径使用代理路径,跳转到PayController支付回调接口[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-J76fSsx1-1690696066757)(D:/Typora/img/image-20220922163238112.png)]
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YQBxkMQb-1690696066759)(D:/Typora/img/image-20220922112520719.png)]
- 修改orderServiceImpl中的addOrder添加购物车的方法
21.5.5 编写支付回调接口(PayController)
详情请查看:21.4.1 创建⼀个控制器定义回调接⼝
21.5.6 测试结果
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-I34U2NgJ-1690696066763)(D:/Typora/img/image-20220922163458450.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6YwGG8Iw-1690696066765)(D:/Typora/img/image-20220922163600159.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pQnSj7fq-1690696066767)(D:/Typora/img/image-20220922163651787.png)]
付款成功修改订单状态为2[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6Hv2EQCU-1690696066768)(D:/Typora/img/image-20220922163711178.png)]
21.6 前端通过轮询访问获取订单⽀付状态
- 流程图:这种方式实现,频繁给后端发送请求,会给后端接口造成极大压力。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tTd5oGG1-1690696066769)(D:/Typora/img/image-20220912135930009.png)]
- 接⼝实现:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2uMR4qcO-1690696066770)(D:/Typora/img/image-20220922174244484.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2QMvKocL-1690696066771)(D:/Typora/img/image-20220922174314219.png)]
- 前端轮询访问接⼝
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UJSTx9Wu-1690696066772)(D:/Typora/img/image-20220922181348676.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qDi1x6gZ-1690696066773)(D:/Typora/img/image-20220922181321199.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-prT593wI-1690696066776)(D:/Typora/img/image-20220922181406511.png)]
21.7 webSocket消息推送
21.7.1 实现流程
前端已经与后端建立了连接,当进入支付界面,就开始监听webSocket请求,当支付成功,后端返回支付结果,通过连接的容器响应给后端。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aFA0u9rf-1690696066778)(D:/Typora/img/image-20220912140014251.png)]
21.7.2 创建webSocket服务器
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iCgv6T0d-1690696066780)(D:/Typora/img/image-20220922205058195.png)]
- 添加依赖,在api层添加
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
- 添加websocket服务节点配置(Java配置⽅式)
@Configuration
public class WebSocketConfig {
@Bean
public ServerEndpointExporter getServerEndpointExporter(){
return new ServerEndpointExporter();
}
}
- 创建websocket服务器
/*@component (把普通pojo实例化到spring容器中,相当于配置文件中的 <bean id="" class=""/>)泛指各种组件,就是说当我们的类不属于各种归类的时候(不属于@Controller、@Services等的时候),我们就可以使用@Component来标注这个类。
@ServerEndpoint主要是将目前的类定义成一个websocket服务器端, 注解的值将被用于监听用户连接的终端访问URL地址,客户端可以通过这个URL来连接到WebSocket服务器端*/
@Component
@ServerEndpoint("/webSocket/{oid}")
public class WebSocketServer {
private static ConcurrentHashMap<String,Session> sessionMap =
new ConcurrentHashMap<>();
/**前端发送请求建⽴websocket连接,就会执⾏@OnOpen⽅法**/
//Session是websocket的Session
@OnOpen
public void open(@PathParam("oid") String orderId, Session
session){
sessionMap.put(orderId,session);
}
/**前端关闭⻚⾯或者主动关闭websocket连接,都会执⾏close**/
@OnClose
public void close(@PathParam("oid") String orderId){
sessionMap.remove(orderId);
}
//封装自定义方法,websocket调用此方法,向前端推送消息
public static void sendMsg(String orderId,String msg){
try {
Session session = sessionMap.get(orderId);
session.getBasicRemote().sendText(msg);
}catch (Exception e){
e.printStackTrace();
}
}
}
21.7.3 在⽀付回调接⼝,通过订单id获取session返回结果
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XQznfB9k-1690696066784)(D:/Typora/img/image-20220922202525906.png)]
@RestController
@RequestMapping("/pay")
public class PayController {
@Autowired
private OrderService orderService;
/**
* 回调接⼝:当⽤户⽀付成功之后,微信⽀付平台就会请求这个接⼝,将⽀付状态的数据
传递过来
*/
@RequestMapping("/callback")
public String paySuccess(HttpServletRequest request) throws
Exception {
System.out.println("--------------------callback");
// 1.接收微信⽀付平台传递的数据(使⽤request的输⼊流接收)
ServletInputStream is = request.getInputStream();
byte[] bs = new byte[1024];
int len = -1;
StringBuilder builder = new StringBuilder();
while((len = is.read(bs))!=-1){
builder.append(new String(bs,0,len));
}
String s = builder.toString();
//使⽤帮助类将xml接⼝的字符串装换成map
Map<String, String> map = WXPayUtil.xmlToMap(s);
if(map!=null &&
"success".equalsIgnoreCase(map.get("result_code"))){
//⽀付成功
//2.修改订单状态为“待发货/已⽀付”
String orderId = map.get("out_trade_no");
int i = orderService.updateOrderStatus(orderId, "2");
System.out.println("--orderId:"+orderId);
//3.通过websocket连接,向前端推送消息
WebSocketServer.sendMsg(orderId,"1");
//4.响应微信⽀付平台
if(i>0){
HashMap<String,String> resp = new HashMap<>();
resp.put("return_code","success");
resp.put("return_msg","OK");
resp.put("appid",map.get("appid"));
resp.put("result_code","success");
return WXPayUtil.mapToXml(resp);
}
}
return null;
}
}
21.7.4 前端进⼊到⽀付⻚⾯时,就建⽴websocket连接
-
修改前端支付界面(order-pay.html),与后端webSocket建立连接
-
删除掉之前的setInterval轮询方式,改用webSocket方式。
-
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8RnGpyOH-1690696066785)(D:/Typora/img/image-20220922201648418.png)]
-
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qM1frjeE-1690696066786)(D:/Typora/img/image-20220922202957442.png)]
-
//前端发送websocket连接请求
var webSocketUrl = webSocketBaseUrl + "webSocket/"+
this.orderInfo.orderId;
var websocket = new WebSocket( webSocketUrl );
//只要后端通过websocket向此连接发消息就会触发onmessage事件
websocket.onmessage = function(event){
var msg = event.data;
if(msg=="1"){
$("#div1").html("<label style='font-size:20px; color:green'>订
单⽀付完成!</label>");
}
}
- 测试结果
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OwKbjwEa-1690696066787)(D:/Typora/img/image-20220922210912150.png)]
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nzIbNgPV-1690696066787)(D:/Typora/img/image-20220922210932309.png)]
⼆⼗⼆、订单超时取消
订单超时取消,指的是当⽤户成功提交订单x之后在规定时间内没有完成⽀付,则将订单x关闭还原库存。
实现订单的超时取消业务通常有两种解决⽅案:
定时任务:(循环扫描:quartz)
延时队列:(MQ)
22.1 实现流程
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QbOUofeZ-1690696075056)(D:/Typora/img/image-20220922211915509.png)] |
---|
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CsbuPGl3-1690696075058)(D:/Typora/img/image-20220912140341464.png)] |
22.2 quartz定时任务框架使⽤(案例)
22.2.1 添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
22.2.2 创建定时任务
定时任务,每隔指定的时间就执⾏⼀次任务
案例:每隔3s就打印⼀次Helloworld
https://cron.qqe2.com :生成的Crons表达式,最后一个*号不加
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pIpOkQZu-1690696066788)(D:/Typora/img/image-20220922213157095.png)]
@Component
public class PrintHelloWorldJob {
//https://cron.qqe2.com
@Scheduled(cron = "0/3 * * * * ?")
public void printHelloWorld(){
System.out.println("----hello world.");
}
}
22.2.3 在启动类开启定时任务
@SpringBootApplication
@EnableScheduling
public class QuartzDemoApplication {
public static void main(String[] args) {
SpringApplication.run(QuartzDemoApplication.class, args);
}
}
22.3 实现订单超时取消(项目)
22.3.1 在service⼦⼯程添加spring-boot-starter-quartz依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
22.3.2 在api⾃动启动类添加@EnableScheduling注解,在启动类开启定时任务
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6RuII4Lh-1690696066789)(D:/Typora/img/image-20220922213651680.png)]
22.3.3 在service子工程中,创建一个新目录,用来存放超时取消业务
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-859ruxpE-1690696066790)(D:/Typora/img/image-20220922213940178.png)]
22.3.3.1 创建OrderTimeoutChecKJob.java
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jdKgIxzA-1690696075059)(D:/Typora/img/image-20220922220037772.png)] |
---|
注:如果只是单纯的把状态为1(未支付)或者超时支付大于规定时间,可能出现以下问题:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zYeD4aZd-1690696075061)(D:/Typora/img/image-20220922221233048.png)] |
优化前:第二步[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pSDSjE6Y-1690696075066)(D:/Typora/img/image-20220922221428922.png)] |
第一次优化:可能出现问题在2.2步,当取消订单时,在准备修改当前订单前,用户却支付了订单,会导致用户钱付了,但订单却取消了[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4p3wL2CH-1690696075069)(D:/Typora/img/image-20220922221625578.png)] |
第二次优化:解决2.2可能出现的问题[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6qxsvvDl-1690696075071)(D:/Typora/img/image-20220922222059342.png)] |
把api子工程pom.xml中的wxpay依赖,移动到service子工程pom.xml文件中[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AvfaZQLS-1690696075072)(D:/Typora/img/image-20220922222527568.png)] |
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XIRkIDLS-1690696075073)(D:/Typora/img/image-20220922223956236.png)] |
具体代码如下:OrderTimeOutCheckJob.java[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hrUpZDtB-1690696075074)(D:/Typora/img/image-20220923160917549.png)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-91o4EvIK-1690696075075)(D:/Typora/img/image-20220923160939997.png)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mULcY4dJ-1690696075076)(D:/Typora/img/image-20220923161034508.png)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4zFYZb8z-1690696075079)(D:/Typora/img/image-20220923161106241.png)] |
OrderService.java:添加关闭订单业务[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0rqVuTnK-1690696075082)(D:/Typora/img/image-20220923161158419.png)] |
OrderServiceImpl.java[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EdPoxV0y-1690696075086)(D:/Typora/img/image-20220923161309875.png)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tBVToam0-1690696075087)(D:/Typora/img/image-20220923161344533.png)] |
⼆⼗三、按类别查询商品
23.1 流程分析
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NgP0gEQ0-1690696066790)(D:/Typora/img/image-20220912140941025.png)]
23.2 接⼝开发
23.2.1 根据类别查询商品接⼝
-
数据库分析及SQL
-
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xguyYSZr-1690696066791)(D:/Typora/img/image-20220923201336896.png)]
-
数据库实现
实体类 |
---|
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-N9LkFdXP-1690696075089)(D:/Typora/img/image-20220912150248318.png)] |
ProductMapper接⼝ |
---|
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RFiu7smP-1690696075090)(D:/Typora/img/image-20220912151301465.png)] |
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-h3QovLCJ-1690696075092)(D:/Typora/img/image-20220912151320924.png)] |
ProductSkuMapper |
---|
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tUsQQahb-1690696075096)(D:/Typora/img/image-20220912151654878.png)] |
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2V6twG3X-1690696075101)(D:/Typora/img/image-20220912151708981.png)] |
- 业务层实现
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fh8LFMtI-1690696075103)(D:/Typora/img/image-20220923195929097.png)] |
---|
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CI9t19U8-1690696075104)(D:/Typora/img/image-20220923200542739.png)] |
- 控制层实现
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OWZqXNSn-1690696066796)(D:/Typora/img/image-20220923201003500.png)]
23.2.2 根据类别ID查询当前类别下所有商品的品牌
- SQL
-- 查询统计某个类别下所有商品的品牌:SQL
select DISTINCT brand from product_params where product_id in
(select product_id from product where category_id=49)
- 数据库实现
ProductMapper接⼝ |
---|
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-h1Mthx74-1690696075105)(D:/Typora/img/image-20220912153034600.png)] |
映射配置 |
---|
明明是俩张表参与了操作,我们为什么只在product中完成即可? |
原因是:下面这个查询属于嵌套查询,先从product表中,根据商品分类id查出所有的商品id,在从这个商品id里(不重复)查询出品牌名称。而原因就在品牌名称这里,因为查询出来的是一个字符串(品牌名称),没有多余的数据,所以不需要做数据库的映射,就没有必要使用resultMap。改用resultSets即可,表示放在List字符串中,resultMap类型为String类型。没有映射配置,那么一个表就够用。 |
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oBZTGpBe-1690696075106)(D:/Typora/img/image-20220912153057097.png)] |
-
Service实现
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-18siH1D7-1690696066798)(D:/Typora/img/image-20220924213425519.png)]
-
Controller实现
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Th3wBsax-1690696066800)(D:/Typora/img/image-20220924213603433.png)]
23.3 前端实现
23.3.1 根据类别查询商品的前端实现
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-et4W3ofM-1690696066801)(D:/Typora/img/image-20220923201057556.png)]
- index.html界面url传值,把商品分类id传输到search.html界面
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZvDarWCE-1690696066802)(D:/Typora/img/image-20220923213435906.png)]
- search.html界面
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1fJoIPIQ-1690696066803)(D:/Typora/img/image-20220923223734705.png)]
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZHIS8SOT-1690696066804)(D:/Typora/img/image-20220923223746212.png)]
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RXj0StQM-1690696066805)(D:/Typora/img/image-20220923223757215.png)]
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AHQlNGTx-1690696066806)(D:/Typora/img/image-20220923223850614.png)]
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-36HCG2SC-1690696066807)(D:/Typora/img/image-20220923223928859.png)]
23.3.2 根据类别查询当前类别下所有商品的品牌的前端实现
- search.html
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OSMgTzS8-1690696066808)(D:/Typora/img/image-20220924222410186.png)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vRS9QpcS-1690696066810)(D:/Typora/img/image-20220924222443196.png)]
⼆⼗四、商品搜索
24.1 流程分析
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FGt5N3Uw-1690696066813)(D:/Typora/img/image-20220912154528718.png)]
24.2 接⼝实现
24.2.1 模糊查询商品信息
- 数据库实现
ProductMapper |
---|
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wu3FbQNJ-1690696075107)(D:/Typora/img/image-20220912161310125.png)] |
映射配置 |
---|
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2ORGpOx9-1690696075128)(D:/Typora/img/image-20220912161332768.png)] |
- Service实现
ProductServiceImpl |
---|
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZjAWkKLC-1690696075133)(D:/Typora/img/image-20220912181012099.png)] |
- Controller实现
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PmGk95aV-1690696066814)(D:/Typora/img/image-20220925173654780.png)]
24.2.2 根据关键字查询对应商品的品牌
- SQL
-- 根据关键字查询对应商品的品牌名称
select DISTINCT brand from product_params where product_id in
(select product_id from product where product_name like '%⼩%')
- 数据库实现
ProductMapper |
---|
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YKZ36id0-1690696075134)(D:/Typora/img/image-20220912181138184.png)] |
映射配置 |
---|
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-W141mqjs-1690696075135)(D:/Typora/img/image-20220912181200221.png)] |
- Service实现
ProductServiceImpl |
---|
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-deijkVLd-1690696075136)(D:/Typora/img/image-20220912181232510.png)] |
- Controller实现
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3XgQsgcx-1690696066815)(D:/Typora/img/image-20220925173711353.png)]
24.3 前端实现
24.3.1 index.html界面
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-a9v0abJe-1690696066816)(D:/Typora/img/image-20220925153300409.png)]
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ksa3QVQQ-1690696066817)(D:/Typora/img/image-20220925153535523.png)]
24.3.2 search.html界面
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CVAF0Qnx-1690696066817)(D:/Typora/img/image-20220925173836534.png)]
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eYZtiaoP-1690696066818)(D:/Typora/img/image-20220925173857886.png)]
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sCBVSemc-1690696066818)(D:/Typora/img/image-20220925173916007.png)]
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DP08j9Bp-1690696066819)(D:/Typora/img/image-20220925173926996.png)]
⼆⼗五、⽤户中⼼
25.1 ⽤户中⼼登录校验
25.1.1 校验token接⼝实现
只用来校验token,不涉及其他操作,把该接口路径交给拦截器拦截即可实现token的校验
- UserController
@ApiOperation("校验token是否过期接⼝")
@GetMapping("/check")
public ResultVO userTokencheck(@RequestHeader("token") String token){
return new ResultVO(ResStatus.OK,"success",null);
}
- token校验拦截器配置
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Autowired
private CheckTokenInterceptor checkTokenInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(checkTokenInterceptor)
.addPathPatterns("/shopcart/**")
.addPathPatterns("/orders/**")
.addPathPatterns("/useraddr/**")
.addPathPatterns("/user/check"); //将接⼝配置到token校验
拦截器
}
}
25.1.2 前端⽤户中⼼⾸⻚校验token
<script type="text/javascript">
var baseUrl = "http://localhost:8080/"
var vm = new Vue({
el:"#div1",
data:{
token:"",
username:"",
userImg:""
},
created:function(){
//从cookie中获取⽤户信息(token、⽤户id、⽤户名、头像)
this.token = getCookieValue("token");
if(this.token == null){
window.location.href = "login.html?returnUrl=usercenter.html";
}else{
//如果登录过期需要重新登录,创建⼀个校验⽤户登录的接⼝,通过请求
这个接⼝进⾏token的检验
//请求user/check接⼝
var url1 = baseUrl+"user/check";
axios({
url:url1,
method:"get",
headers:{
token:this.token
}
}).then((res)=>{
if(res.data.code == 10000){
//token校验通过
this.username = getCookieValue("username");
this.userImg = getCookieValue("userImg");
}else{
window.location.href = "login.html?
returnUrl=user-center.html";
}
});
}
}
});
</script>
25.2 ⽤户中⼼的订单管理
25.2.1 流程分析
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rLcjZcVm-1690696066820)(D:/Typora/img/image-20220912181811477.png)]
25.2.2 接⼝实现
-
数据库实现:(1)使用动态sql,一个查询实现下面俩个功能。(2)或建立俩个查询。
-
根据⽤户的ID分⻚查询当前⽤户的订单信息、关联查询订单中的商品快照
-
根据⽤户的ID和订单状态分⻚查询当前⽤户的订单信息、关联查询订单中的商品快照
-
封装OrdersVO |
---|
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-70Ba09gf-1690696075137)(D:/Typora/img/image-20220912181931725.png)] |
OrderMapper |
---|
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YaUGg9ON-1690696075138)(D:/Typora/img/image-20220912181957967.png)] |
映射配置 |
---|
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wU4qRu84-1690696075139)(D:/Typora/img/image-20220912182018865.png)] |
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qiwvnuO4-1690696075139)(D:/Typora/img/image-20220912182038953.png)] |
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2JXjuUbN-1690696075140)(D:/Typora/img/image-20220912182051287.png)] |
- Service实现
OrderServiceImpl |
---|
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4cGa11CU-1690696075141)(D:/Typora/img/image-20220912182118145.png)] |
-
控制器实现
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8D4E3eWi-1690696066821)(D:/Typora/img/image-20220925231028820.png)]
-
接口修改与优化
因为我们现在是根据订单快照进行分页,导致前端显示订单时,每页显示数量不一致。比如,一个订单有多个快照,就会占用一个分页条数,本来显示5条订单记录,却因该订单下有2个快照,导致西安市3条记录(快照占2条)
-
因为我们使用了连接查询,连接查询的结果做了分页,导致对订单快照信息进行分页而不是订单,数据显示不一致,此操作不合理。
-
修改为子查询操作,即可成功优化
-
映射配置 OrderItemMapper.java [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mOH3Mehz-1690696066822)(D:/Typora/img/image-20220926104340213.png)] OrderItemMapper.xml [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-i2SZVvTM-1690696066822)(D:/Typora/img/image-20220926104523881.png)] OrdersMapper.xml [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KckRpqEv-1690696066829)(D:/Typora/img/image-20220926104735954.png)]
-
25.2.3 前端实现
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3syuS65i-1690696066832)(D:/Typora/img/image-20220926104821851.png)]
user-center.html界面 |
---|
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KDv8z7qu-1690696075141)(D:/Typora/img/image-20220926201525493.png)] |
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TW3W5ZfG-1690696075142)(D:/Typora/img/image-20220926201609271.png)] |
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-73sL60sX-1690696075143)(D:/Typora/img/image-20220926201626572.png)] |
user-orderlist.html |
---|
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yGsgz8qI-1690696075144)(D:/Typora/img/image-20220926201858486.png)] |
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-r1kgVsoB-1690696075146)(D:/Typora/img/image-20220926201951645.png)] |
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-M7qKyAkO-1690696075149)(D:/Typora/img/image-20220926202009477.png)] |
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4cwrsyrj-1690696075153)(D:/Typora/img/image-20220926202052002.png)] |
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zKqlgEgS-1690696075155)(D:/Typora/img/image-20220926202117197.png)] |
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BbVnRW5h-1690696075157)(D:/Typora/img/image-20220926202148208.png)] |
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FwVUXEd4-1690696075158)(D:/Typora/img/image-20220926202219439.png)] |
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EMwPV9Wu-1690696075160)(D:/Typora/img/image-20220926202244119.png)] |
二十六、项目待优化与完成界面
退出登录(未完成)———》(已完成)[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2gXVMGrO-1690696075161)(D:/Typora/img/image-20220926203216068.png)] |
---|
日志管理(未完成)[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-k7aKDYvw-1690696075166)(D:/Typora/img/image-20220926203340234.png)] |
商品评价脱敏实现(让评价人显示为,例如:ans***sf)已实现[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VS6xbImL-1690696075167)(D:/Typora/img/image-20220926203857017.png)] |
收货地址管理(我们只实现了地址的显示与选择)[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OdA6n1VH-1690696075168)(D:/Typora/img/image-20220926204855704.png)] |
提交订单(支付宝支付未完成)[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LUpoo7oL-1690696075169)(D:/Typora/img/image-20220926205453698.png)] |
用户中心(未完成)[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-14723JUx-1690696075170)(D:/Typora/img/image-20220926205737712.png)] |
订单管理(只完成了查询订单操作)[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aAiqPJn2-1690696075170)(D:/Typora/img/image-20220926210030648.png)] |
评价管理(添加商品评价为未完成)[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qCBaTNw3-1690696075171)(D:/Typora/img/image-20220926214235163.png)] |
物流系统(扩展):一个商家的商品一单发货,要填写快递单,拿着快递单(订单)即可通过物流接口查询物流信息。和微信支付一样,调用第三方接口即可[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-elV5j4L0-1690696075172)(D:/Typora/img/image-20220926214251306.png)] |
26.1 订单操作
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xWFzs4QQ-1690696066834)(D:/Typora/img/image-20221002175704271.png)]
26.1.1 逻辑删除订单操作
所谓逻辑删除,并不是要真正的删除,而是让用户前端点击删除订单信息后,不在显示该订单信息。后端只需进行更新操作,把逻辑删除状态改为已删除,从而实现前端不显示该信息的操作。
- Sql语句,数据库测试是否更新成功
- 根据订单id,修改订单逻辑删除状态为已删除(1:已删除。2:未删除)
update orders set delete_status=1 where order_id="2beb74dd518c4d5fb593d55a6e926220";
-
mapper层操作
-
因为只涉及单表操作,所以使用tkMapper即可完成
-
业务层实现
-
OrderService接口
-
//6、逻辑删除订单信息/通过订单id更新订单删除状态为已删除 public ResultVO deleteOrder(String orderId);
-
-
OrderServiceImpl实现类
-
//逻辑删除订单信息/通过订单id更新订单删除状态为已删除 @Override public ResultVO deleteOrder(String orderId) { Orders orders = new Orders(); //根据orderid进行修改 orders.setOrderId(orderId); orders.setDeleteStatus(1); int i = ordersMapper.updateByPrimaryKeySelective(orders); if(i>0){ return new ResultVO(BaseCode.ok,"success","删除成功"); }else { return new ResultVO(BaseCode.no,"false","删除失败"); } }
-
-
-
接口控制层实现
-
//逻辑删除订单信息/通过订单id更新订单删除状态为已删除 @ApiOperation("逻辑删除订单信息的接口") @ApiImplicitParams({ @ApiImplicitParam(dataType = "String",name = "token", value = "令牌",required =true), @ApiImplicitParam(dataType = "String",name = "orderId", value = "订单id",required =true) }) @DeleteMapping("/delOrder/{orderId}") public ResultVO deleteOrder(@PathVariable("orderId") String orderId, @RequestHeader("token") String token){ ResultVO resultVO = orderService.deleteOrder(orderId); return resultVO; }
-
-
前端实现
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jdl8wUiK-1690696066836)(D:/Typora/img/image-20221002174558666.png)]
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BhxxT0Xr-1690696066837)(D:/Typora/img/image-20221002174620587.png)]
26.1.2 取消订单操作
所谓的取消订单操作就是把订单状态改为已关闭,6为已关闭
俩种实现方法:(1)之前已经实现,这里实现(2)
(1)在后端实现订单超时取消功能,当订单超时未支付时,自动更改订单状态为已关闭(2)手动实现订单关闭,当点击取消订单按钮时,更改订单状态为已关闭。
-
sql
-
update orders set delete_status=6 where order_id="2beb74dd518c4d5fb593d55a6e926220";
-
-
mapper层操作
-
因为只涉及单表操作,所以使用tkMapper默认方法即可完成
-
业务层操作
-
//7、取消订单操作,修改订单状态为6,已关闭 public ResultVO updateOrder6(String orderId);
-
@Override public ResultVO updateOrder6(String orderId) { Orders orders = new Orders(); //根据orderid进行修改 orders.setOrderId(orderId); orders.setStatus("6"); int i = ordersMapper.updateByPrimaryKeySelective(orders); if(i>0){ return new ResultVO(BaseCode.ok,"success","取消成功"); }else { return new ResultVO(BaseCode.no,"false","取消失败"); } }
-
-
接口层实现
-
//取消订单操作,修改订单状态为6,已关闭 @ApiOperation("取消订单操作的接口") @ApiImplicitParams({ @ApiImplicitParam(dataType = "String",name = "token", value = "令牌",required =true), @ApiImplicitParam(dataType = "String",name = "orderId", value = "订单id",required =true) }) @PutMapping("/upOrder6/{orderId}") public ResultVO updateOrderStatus6(@PathVariable("orderId") String orderId, @RequestHeader("token") String token){ ResultVO resultVO = orderService.updateOrder6(orderId); return resultVO; }
-
26.1.3 确认收货,订单操作
将订单状态从3待收货改成4待评价
-
sql
-
update orders set delete_status=4 where order_id="2beb74dd518c4d5fb593d55a6e926220";
-
-
mapper层操作
-
因为只涉及单表操作,所以使用tkMapper默认方法即可完成
-
业务层操作
-
//8、确认收货,订单操作,修改订单状态为4,待评价 public ResultVO updateOrder4(String orderId);
-
@Override public ResultVO updateOrder4(String orderId) { Orders orders = new Orders(); //根据orderid进行修改 orders.setOrderId(orderId); orders.setStatus("4"); int i = ordersMapper.updateByPrimaryKeySelective(orders); if(i>0){ return new ResultVO(BaseCode.ok,"success","收货成功,请评价"); }else { return new ResultVO(BaseCode.no,"false","收货失败"); } }
-
-
接口层实现
-
//确认收货,订单操作,修改订单状态为4,待评价 @ApiOperation("确认收货,订单操作的接口") @ApiImplicitParams({ @ApiImplicitParam(dataType = "String",name = "token", value = "令牌",required =true), @ApiImplicitParam(dataType = "String",name = "orderId", value = "订单id",required =true) }) @PutMapping("/upOrder4/{orderId}") public ResultVO updateOrderStatus4(@PathVariable("orderId") String orderId, @RequestHeader("token") String token){ ResultVO resultVO = orderService.updateOrder4(orderId); return resultVO; }
-
26.1.4 商品未付款,去支付操作
当用户下单,却没支付时,可以在个人中心,我的订单中,进行支付
因为支付功能(1、校验库存。2、保存订单。3、⽣成商品快照。4、扣减库存。5、删除购物车商品信息。6、微信支付。7、通过websocket功能,进行微信支付的二次判断,成功就修改订单状态为2代发货)我们已经完成。所以本次操作,并不直接涉及数据库表的操作,只需调用之前的方法即可。
-
注:经过测试发现,在下单那一刻生成的支付url链接,之后无法通过接口拿到,所以我们修改之前的支付功能,在添加订单那一刻,我们把删生成的支付url存储到订单表中即可,供给去支付操作拿取支付url
-
修改数据库订单表,添加pay_url字段
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zBdkUKxh-1690696066838)(D:/Typora/img/image-20221003110524690.png)]
-
修改orders实体类,添加payurl字段
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GxMNPFRi-1690696066840)(D:/Typora/img/image-20221003110650790.png)]
-
修改ordersmapper.xml,添加payUrl字段
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LxwTd8WE-1690696066843)(D:/Typora/img/image-20221003110740746.png)]
-
给业务层添加更新url操作
- ordersService.java[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1YUEZ7sP-1690696066845)(D:/Typora/img/image-20221003111003860.png)]
- ordersServiceImpl.java[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GKzeYVk7-1690696066849)(D:/Typora/img/image-20221003111116253.png)]
-
修改OrderServiceImpl中的addOrder方法,添加更新url操作
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dKfD2Hhy-1690696066852)(D:/Typora/img/image-20221003111222414.png)]
-
去支付的业务层实现
-
ordersService.java[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-diJtriCM-1690696066853)(D:/Typora/img/image-20221003111318894.png)]
-
ordersServiceImpl.java
-
//9、在个人中心界面,对未付款的订单,实现付款操作。 @Override public ResultVO goPay(Orders order) throws SQLException{ Map<String,String> orderInfo = new HashMap<>();//供微信支付使用,支付完成后,响应给前端的信息 //存放orderId与untitled的原因是,供微信支付使用 orderInfo.put("orderId",order.getOrderId());//商品订单 orderInfo.put("productNames",order.getUntitled());//商品名称 orderInfo.put("totalAmount", String.valueOf(order.getTotalAmount())); //6.微信支付(以上操作成功后,执行微信支付功能) //设置当前订单信息 HashMap<String, String> data = new HashMap<>(); data.put("body",order.getUntitled());//商品描述——商品名称 data.put("out_trade_no",order.getOrderId());//使⽤当前⽤户订单的编号作为当前⽀付交易的交易号 data.put("fee_type","CNY");//支付币种——人民币 data.put("total_fee",1+"");//仅供测试时使用,付款1分钱 // data.put("total_fee",order.getActualAmount()*100+"");//支付总金额,单位是分(所以*100),集合data存储String类型(所以+“”)、 data.put("trade_type","NATIVE");//交易类型——二维码链接 //http://zsh.free.idcfengye.com 使用Ngrok实现内⽹穿透,生成的代理路径 data.put("notify_url","http://zsh.free.idcfengye.com/pay/callback");//设置⽀付完成时的回调⽅法接⼝——可以是payController接口中的路径 //发送请求,获取响应 //微信⽀付:申请⽀付连接 try { WXPay wxPay = new WXPay(new MyPayConfig()); String payUrl = order.getPayUrl(); orderInfo.put("payUrl",payUrl);//接收回调支付路径/支付链接 return new ResultVO(BaseCode.ok,"Pay Success!!",orderInfo); } catch (Exception e) { e.printStackTrace(); return new ResultVO(BaseCode.no,"Pay false!!",null); } }
-
-
-
前端实现
- user_orderlist.html
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-t35jkXHo-1690696066854)(D:/Typora/img/image-20221003111613621.png)]
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3ozaiGyW-1690696066855)(D:/Typora/img/image-20221003111644110.png)]
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0HRIuO1R-1690696066855)(D:/Typora/img/image-20221003111702001.png)]
- user_orderlist.html
26.1.5 商品已经确认收货,则可以进行去评价操作
-
数据库操作使用tkMapper默认的方法即可
-
业务层操作
-
ProductCommentsService.java
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xZi6p2uh-1690696066857)(D:/Typora/img/image-20221006213012844.png)]
-
ProductCommentsServiceImpl.java
-
//添加评价功能 @Override public ResultVO insertComments(ProductComments productComments) { productComments.setSepcName(new Date()); int insert = productCommentsMapper.insert(productComments); if(insert>0){ return new ResultVO(BaseCode.ok,"success","添加成功"); }else { return new ResultVO(BaseCode.no,"false","添加失败"); } }
-
-
-
接口层操作
-
UserController.java
-
@ApiOperation("添加评价接⼝") @ApiImplicitParams({ @ApiImplicitParam(dataType = "ProductComments",name = "comments", value = "评价内容",required = true), @ApiImplicitParam(dataType = "string",name = "token", value = "令牌",required = true) }) @PostMapping("/insertComment") public ResultVO insertComments(@RequestBody ProductComments comments,@RequestHeader("token") String token){ return productCommentsService.insertComments(comments); }
-
-
-
前端实现(看项目user-commented.html)
26.2 用户个人信息
26.2.1 查看个人信息
-
数据层操作使用tkMapper默认的方法即可
-
业务层操作
-
UserService接口
-
//查询用户信息 public ResultVO getUserInfo(int userId);
-
-
UserServiceImpl实现类
-
//查询用户信息 @Override public ResultVO getUserInfo(int userId) { Users users = usersMapper.selectByPrimaryKey(userId); return new ResultVO(BaseCode.ok,"success",users); }
-
-
-
接口层操作
-
UserController
-
//查询用户信息 @ApiOperation("查询个人信息接⼝") @ApiImplicitParams({ @ApiImplicitParam(dataType = "int",name = "userId", value = "⽤户id",required = true), @ApiImplicitParam(dataType = "string",name = "token", value = "令牌",required = true) }) @GetMapping("/getUser/{userId}") public ResultVO getUserInfo(@PathVariable("userId") int userId,@RequestHeader("token") String token) { ResultVO userInfo = userService.getUserInfo(userId); return userInfo; }
-
-
-
前端实现
26.2.2 查看我的评价(单表查询)
-
数据层操作使用tkMapper默认的方法即可
-
业务层操作
-
ProductCommentsService.java
-
//通过用户id查询用户评价信息 public ResultVO getUserComments(int userId);
-
-
ProductCommentsServiceImpl实现类
-
//通过用户id查询用户评价信息 @Override public ResultVO getUserComments(int userId) { Example example = new Example(ProductComments.class); Example.Criteria criteria = example.createCriteria(); criteria.andEqualTo("userId",userId); List<ProductComments> productComments = productCommentsMapper.selectByExample(example); return new ResultVO(BaseCode.ok,"success",productComments); }
-
-
-
接口层操作
-
UserController
-
//查询用户评价信息的接口 @ApiOperation("查询用户评价信息的接口") @ApiImplicitParams({ @ApiImplicitParam(dataType = "int",name = "userId", value = "⽤户id",required = true), @ApiImplicitParam(dataType = "string",name = "token", value = "令牌",required = true) }) @GetMapping("/getComment/{userId}") public ResultVO getUserComment(@PathVariable("userId") Integer userId,@RequestHeader("token") String token) { ResultVO userComments = productCommentsService.getUserComments(userId); return userComments; }
-
-
-
前端实现
26.2.3 分页查询用户收货地址
-
sql
-
select addr_id,user_id,receiver_name,receiver_mobile,province,city,area,ad dr,post_code,status,common_addr,create_time,update_time from user_addr where user_id=22 limit 0,1;
-
-
数据库层实现
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-851e8y6o-1690696066861)(D:/Typora/img/image-20221003194845524.png)]
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6ATAZizm-1690696066865)(D:/Typora/img/image-20221003194906165.png)]
-
业务层实现
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vJq59SK0-1690696066866)(D:/Typora/img/image-20221003194956619.png)]
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OcYqsExO-1690696066868)(D:/Typora/img/image-20221003195016473.png)]
-
接口层实现
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PcsZItsE-1690696066869)(D:/Typora/img/image-20221003195044955.png)]
-
前端实现
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Gdau7A5U-1690696066870)(D:/Typora/img/image-20221004224754960.png)]
26.2.4 分页查询我的评价(包含商品图片,涉及多表查询)
-
sql
-
(1)子查询
-
<!--查询用户评价信息--> select product_id, product_name, is_anonymous, comm_type, comm_content, sepc_name from product_comments where user_id=22 limit 0,1 <!--查询商品图片--> select url from product_img where item_id=10 and is_main=1;
-
-
(2)连接查询
-
select c.product_id, c.product_name, c.is_anonymous, c.comm_type, c.comm_content, c.sepc_name, i.url from product_comments c inner join product_img i on c.product_id=i.item_id where c.user_id=22 and i.is_main=1 limit 0,1
-
-
-
数据库层实现:因涉及多表查询,所以需手动编写sql语句,不能使用tkMapper默认的方法
-
先给实体类,添加增加的img属性
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rguWwZgA-1690696066871)(D:/Typora/img/image-20221004202331877.png)]
-
ProductCommentsMapper.java[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uYffAB2y-1690696066872)(D:/Typora/img/image-20221004224401407.png)]
-
ProductCommentsMapper.xml
-
<!-- 查看我的评价功能,通过用户id,查询到对应的评价信息,包含商品与商品图片--> <!--1、连接查询 --> <resultMap id="BaseResultMapVO1" type="com.zsh.fmmall.entity.ProductCommentsVO"> <result column="product_id" jdbcType="VARCHAR" property="productId" /> <result column="product_name" jdbcType="VARCHAR" property="productName" /> <result column="is_anonymous" jdbcType="INTEGER" property="isAnonymous" /> <result column="comm_type" jdbcType="INTEGER" property="commType" /> <result column="comm_content" jdbcType="VARCHAR" property="commContent" /> <result column="sepc_name" jdbcType="TIMESTAMP" property="sepcName" /> <result column="user_id" jdbcType="VARCHAR" property="userId" /> <result column="url" jdbcType="VARCHAR" property="productImg" /> </resultMap> <select id="getCommentsPageByUserId" resultMap="BaseResultMapVO1"> select c.product_id, c.product_name, c.is_anonymous, c.comm_type, c.comm_content, c.sepc_name, i.url from product_comments c inner join product_img i on c.product_id=i.item_id where c.user_id=#{userId} and i.is_main=1 limit #{start},#{limit}; </select>
-
-
业务层实现
-
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xPGbBUEn-1690696066872)(D:/Typora/img/image-20221004224551982.png)]
-
//查看我的评价功能,通过用户id,查询到对应的评价信息,包含商品与商品图片 @Override public ResultVO getCommentsPageByUserId(String userId, int pageNum, int limit) { //1、查询数据 //获取起始数据Start int start=(pageNum-1)*limit; List<ProductCommentsVO> comments = productCommentsMapper.getCommentsPageByUserId(userId, start, limit); //2,获取总记录数 Example example = new Example(ProductComments.class); Example.Criteria criteria = example.createCriteria(); criteria.andEqualTo("userId",userId); int count = productCommentsMapper.selectCountByExample(example); //3、获取总页数 int pageCount=count%limit==0?count/limit:count/limit+1; //4、封装数据 PageHelper<ProductCommentsVO> pageHelper = new PageHelper<>(count, pageCount, comments); return new ResultVO(BaseCode.ok,"success",pageHelper); }
-
-
接口层实现
-
UserController.java
-
//查看我的评价功能,通过用户id,查询到对应的评价信息,包含商品与商品图片 @ApiOperation("分页查询个人评价的接口") @ApiImplicitParams({ @ApiImplicitParam(dataType = "int",name = "userId", value = "⽤户id",required = true), @ApiImplicitParam(dataType = "string",name = "token", value = "令牌",required = true), @ApiImplicitParam(dataType = "int",name = "pageNum", value = "页码",required = true), @ApiImplicitParam(dataType = "int",name = "limit", value = "显示条数",required = true) }) @GetMapping("/getCommentPage/{userId}") public ResultVO getUserCommentPage(@PathVariable("userId") String userId, @RequestHeader("token") String token, int pageNum,int limit) { ResultVO userComments = productCommentsService.getCommentsPageByUserId(userId,pageNum,limit); return userComments; }
-
-
-
前端实现
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LnFoiwuv-1690696066873)(D:/Typora/img/image-20221004224929941.png)]
⼆⼗七、项⽬云部署
27.1 项⽬部署介绍
项⽬部署就是将开发—测试完成的项⽬运⾏在⽣产环境中
多种的部署环境是为了实现数据的隔离、对数据进⾏保护
开发环境:windows (有⾮常便利的可视化操作系统)
⽣产环境:Linux (开源免费、系统开销⼩、安全性⾼)
-
开发环境(windows)
-
应⽤服务器(性能要求低): Tomcat-windows
-
数据库服务器(测试数据不要求很严谨): MySQL-Linux/windows,在企业中数据库可能会使用同一个
-
-
测试环境(模拟的⽣产环境)
-
应⽤服务器 Tomcat-Linux
-
数据库服务器 Tomcat-Linux
-
-
⽣产环境(需要进⾏保护的、不能被破坏的)
-
应⽤服务器 Tomcat-Linux
-
数据库服务器 Tomcat-Linux
-
27.2 linux学习
具体请参考文档[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WUhlFnyn-1690696066873)(D:/Typora/img/image-20220926220539858.png)]
-
在linux中部署完mysql之后,可以使用navicat连接云服务器的mysql(前提是云服务器中的mysql已经开启)
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KAXC2KZ1-1690696066876)(D:/Typora/img/image-20220929205116085.png)]
-
可以通过转储的形式,把本地数据库运行到云服务器数据库
-
先转储本地数据库[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MJuFDa5L-1690696066878)(D:/Typora/img/image-20220929205910449.png)]
-
在云服务器数据库,运行sql文件[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MeHTllGS-1690696066885)(D:/Typora/img/image-20220929210129816.png)]
-
**27.3 **后端项⽬部署
对后端的修改有:数据库连接地址、微信支付的回调接口路径
-
搭建云服务器环境(在linux文件中有操作详情)
-
JDK
-
MySQL
-
-
maven聚合⼯程打包
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HTgX1qEp-1690696066887)(D:/Typora/img/image-20220912182342708.png)]
-
在打包之前,我们要修改对应的服务器路径与数据库密码
-
注:因为我们的云服务器只有一台,数据库、前后端都在同一服务器上,所以我们可以先不修改。(企业中需要修改)
##如果后端项目服务器与数据库服务器不在同一台主机,则需要修改Localhost为云服务器的ip地址
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3bw3UBb6-1690696066890)(D:/Typora/img/image-20221005154520024.png)]
-
-
修改微信支付的回调路径,取消内网穿透:因为云服务器ip为公网ip,我们把项目部署到云服务器上去,就不需要内网穿透了。
- OrderServiceImpl.java
- 内网穿透代理ip[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jdxHRo9o-1690696066892)(D:/Typora/img/image-20221005160940310.png)]
- 改成自己的云服务器ip[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wEO8upxv-1690696066896)(D:/Typora/img/image-20221005161155511.png)]
-
修改完成后,清除缓存,进行打包
-
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RypUxkrP-1690696066901)(D:/Typora/img/image-20221005154812432.png)]
-
打包完成[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-owITMFzk-1690696066902)(D:/Typora/img/image-20221005162406048.png)]
-
-
找到api下jar包的路径,就是后端的jar包
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9IrKrZI2-1690696066903)(D:/Typora/img/image-20221005162310614.png)]
-
上传到云服务器
- 在云服务器上/usr/local/,新建文件夹fmmall。将api的jar包使用xftp传输到该路径
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QC7Y4gPs-1690696066904)(D:/Typora/img/image-20221005163303444.png)]
- 在云服务器上/usr/local/,新建文件夹fmmall。将api的jar包使用xftp传输到该路径
-
使用XShell上连接云服务器,启动项⽬
1、如果只是
java -jar api-2.0.1.jar
则成功启动,但启动后不能进行别的操作,并且Xshell窗口不能关闭。2、所以我们这里使用
java -jar api-2.0.1.jar &
来启动后端,&表示在后台运行,可以进行其他操作,但不能断开连接。3、如果想要项目一直运行,则使用
nohup java -jar api-2.0.1.jar &
命令,可以断开连接,项目会一直运行。[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-46512jUj-1690696066905)(D:/Typora/img/image-20221007221648819.png)]
-
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rjdQaUdc-1690696066905)(D:/Typora/img/image-20221005163804275.png)]
-
nohup java -jar api-2.0.1.jar &
-
启动成功[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-N3cBECy0-1690696066906)(D:/Typora/img/image-20221005223337211.png)]
-
-
访问接口文档进行测试http://47.92.198.197:8080/doc.html
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-N7w4cFIC-1690696066907)(D:/Typora/img/image-20221005223423573.png)]
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9gjqL8ZC-1690696066910)(D:/Typora/img/image-20221005223511785.png)]
27.4 **前端项⽬部署(**tomcat)
前端项⽬也需要部署在服务器上,才能够提供多⽤户访问
- tomcat可以作为前端项⽬部署的服务器使⽤
-
安装Tomcat,配置port 为 9999
-
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZS52PwYg-1690696066917)(D:/Typora/img/image-20221005224609675.png)]
-
改成9999[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NMaDi1ii-1690696066922)(D:/Typora/img/image-20221005224653183.png)]
-
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pylLpUuJ-1690696066927)(D:/Typora/img/image-20221005224822365.png)]
-
-
将前端项⽬上传到tomcat/webapps中
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KnPLfrkj-1690696066934)(D:/Typora/img/image-20221005225001933.png)]
-
记得要在云服务器上放行9999端口
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pLwWZCy1-1690696066935)(D:/Typora/img/image-20221005230123665.png)]
-
启动Tomcat
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fFf1AeQE-1690696066936)(D:/Typora/img/image-20221005230344563.png)]
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WmAuaxae-1690696066937)(D:/Typora/img/image-20221005230326861.png)]
-
访问:http://47.92.198.197:9999/fmall-static/index.html
-
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8BweqDnv-1690696066938)(D:/Typora/img/image-20220912182541835.png)]
-
- 使⽤Tomcat部署前端项⽬存在的问题:
1.前端项⽬的⼀个⻚⾯会包含⼤量的css\js\图⽚,会有⼤量的并发请求,Tomcat难以满
⾜并发的需求
2.Tomcat的核⼼价值在于能够便于执⾏Java程序,⽽不是处理并发,同时前端项⽬中没
有Java程序,从功能上讲前端项⽬的部署也⽤不着Tomcat
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-68geYcPr-1690696066939)(D:/Typora/img/image-20220912182624344.png)]
结论:使⽤Tomcat作为部署前端项⽬的服务器是不合适的。
27.5 前端项⽬部署(Nginx)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OyQtchLV-1690696066939)(D:/Typora/img/image-20221007153008737.png)]
-
在Linux安装Nginx(我们的环境都安装到了/usr/local/目录中)
-
安装步骤可以参考 Linux.pdf
-
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iErzNEcX-1690696066940)(D:/Typora/img/image-20221007150616610.png)]
-
-
修改前端项⽬的baseUrl
-
将前端项⽬上传到nginx⽬录
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VoDxTrLH-1690696066942)(D:/Typora/img/image-20221007153352030.png)]
-
配置nginx的访问路径
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Q93ndBR6-1690696066944)(D:/Typora/img/image-20221007153644229.png)]
具体详情请参考Nginx.pdf
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-r8HV2Ipt-1690696066947)(D:/Typora/img/image-20221007145954784.png)]
- 测试:
- 先启动后端
- 访问47.92.198.197进行测试
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sIoEedtU-1690696066949)(D:/Typora/img/image-20221007162428295.png)]
⼆⼗八、项⽬⽇志管理
28.1 ⽇志框架的概念
在项⽬开发、运维过程中,为了能够清晰的知道项⽬在服务器中的运⾏过程、便于查找服务器运⾏过程的异常原因,我们需要对系统运⾏过程进⾏记录 — 运⾏⽇志
- 我们可以使⽤ 控制台输出 的形式进⾏运⾏过程记录(不便于⽇志信息跟踪和维护、
不能够持久化存储)
- 控制台输出⽇志的诸多弊端 催化了 ⽇志框架的诞⽣
-
⽇志框架 ⽤于帮助我们在应⽤开发中完成⽇志记录的帮助类
-
⽇志框架作⽤
-
1.有结构的记录⽇志信息(结构是为了提升⽇志信息的可读性)
-
2.定义⽇志的输出策略(每个⽇志⽂件最⼤5m、每天⼀个⽇志⽂件)
-
28.2 ⽇志框架规范
⽇志框架规范:⽇志记录实现的规则
⽇志框架:实现⽇志记录
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-e2Pw3Uu5-1690696066950)(D:/Typora/img/image-20220912182938489.png)]
-
⽇志接⼝(⽇志框架规范)
-
JCL(Jakatra Commons Logging)
-
SLF4J(Simple Logging Facade For Java )
-
JBoss Logging
-
-
⽇志实现(⽇志框架)
-
Log4j
-
Logback
-
28.3 SLF4J
-
SLF4J(Simple Logging Facade For Java )简单⽇志⻔⾯,定义了⼀套⽇志规范,并不是⽇志框架解决⽅法。
-
SLF4J的实现框架
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dNmgFYU3-1690696066952)(D:/Typora/img/image-20220912183044050.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bQhnsSII-1690696066953)(D:/Typora/img/image-20221007192732634.png)]
28.4 slf4j-simple
-
创建springBoot应⽤
-
移出springboot应⽤默认logback-classic的⽇志依赖(因为springboot项目自带一个默认依赖,如果不移除,就无法添加新的slf4j依赖,因为俩个依赖项目不知道该用哪一个)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</exclusion>
</exclusions>
</dependency>
- 添加依赖
<!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-simple -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.25</version>
</dependency>
- 在service层打印⽇志
@Service
public class TestServiceImpl implements TestService {
private Logger logger =
LoggerFactory.getLogger(TestServiceImpl.class);
@Override
public void testLog() {
//⽇志记录
//System.out.println("⽇志信息");
logger.info("订单添加完成");
}
}
28.5 log4j使⽤介绍
log4j没有实现slf4j,如果基于slf4j规范使⽤log4j,则需要添加slf4j-log4j12依赖
- 添加依赖
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.25</version>
<scope>test</scope>
</dependency>
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bbrfe8eh-1690696066954)(D:/Typora/img/image-20220912183418087.png)]
- 在resources⽬录下创建log4j.properties⽂件
log4j.rootLogger=DEBUG,stdout
# MyBatis logging configuration...
log4j.logger.org.mybatis.example.BlogMapper=TRACE
# Console output...
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=[%t] %5p - %n%m
28.6 基于SpringBoot应⽤的logback⽇志配置(常用)
SpringBoot默认整合了logback-classic⽇志框架,我们需要对logback⽇志框架进⾏配置
以⾃定义⽇志输出格式、⽇志⽂件配置、⽇志⽂件保存策略等信息
- logback配置
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="stdout"
class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<springProfile name="dev">
<pattern>%d{yyyy-MM-dd-HH:mm:ss E} %level [%thread]-
%class[%line]: %msg%n</pattern>
</springProfile>
<springProfile name="!dev">
<pattern>%d{yyyy-MM-dd-HH:mm:ss E} %level [%thread]-
%class[%line]: %msg%n</pattern>
</springProfile>
<!--⽇志的编码格式-->
<charset>UTF-8</charset>
</encoder>
</appender>
<!--这个就表示的是要把 ⽇志输出到⽂件(FileAppender)-->
<appender name="file" class="ch.qos.logback.core.FileAppender">
<file>D:/log/output.log</file>
<!--设置⽇志是否追加-->
<append>true</append>
<encoder>
<pattern>%d{yyyy-MM-dd-HH:mm:ss.SSS} %level [%thread]-
%class:%line>>%msg%n</pattern>
<charset>UTF-8</charset>
</encoder>
<!--设置⽇志写⼊是否线程安全-->
<prudent>false</prudent>
</appender>
<appender name="timeFile"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<!--TimeBasedRollingPolicy 基于时间的滚动策略-->
<rollingPolicy
class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>d:/log/log-%d{yyyy-MM-dd-HH}.log</fileNamePattern>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd-HH:mm:ss.SSS} %level [%thread]-
%class:%line>>%msg%n</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<appender name="fixedFile"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>d:/log/fixedFile.log</file>
<rollingPolicy
class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
<fileNamePattern>log/fixedFile%i.log</fileNamePattern>
<minIndex>1</minIndex>
<maxIndex>10</maxIndex>
</rollingPolicy>
<!--SizeBasedTriggeringPolicy-->
<triggeringPolicy
class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<maxFileSize>5MB</maxFileSize>
</triggeringPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd-HH:mm:ss.SSS} %level [%thread]-
%class:%line>>%msg%n
</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<root level="info">
<appender-ref ref="stdout" />
<appender-ref ref="timeFile"/>
</root>
</configuration>
28.7 在锋迷商城项⽬进⾏⽇志配置
28.7.1 在api⼦⼯程的resources⽬录添加⽇志配置⽂件
-
创建一个模板,方便使用[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jqOgseyA-1690696066955)(D:/Typora/img/image-20221007200319266.png)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fhy8KRWQ-1690696066955)(D:/Typora/img/image-20221007200522802.png)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8Qgf1E3o-1690696066956)(D:/Typora/img/image-20221007200540098.png)]
-
logback-spring.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="stdout"
class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<springProfile name="dev">
<pattern>%d{yyyy-MM-dd-HH:mm:ss E} %level
[%thread]-%class[%line]: %msg%n</pattern>
</springProfile>
<springProfile name="!dev">
<pattern>%d{yyyy-MM-dd-HH:mm:ss E} %level
[%thread]-%class[%line]: %msg%n</pattern>
</springProfile>
<!--⽇志的编码格式-->
<charset>UTF-8</charset>
</encoder>
</appender>
<appender name="timeFile"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<!--TimeBasedRollingPolicy 基于时间的滚动策略-->
<rollingPolicy
class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--⽇志⽂件的存储路径-->
<fileNamePattern>log/log-%d{yyyy-MM-dd-HH}.log</fileNamePattern>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd-HH:mm:ss.SSS} %level [%thread]-
%class:%line>>%msg%n</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<root level="info">
<appender-ref ref="stdout"/>
<appender-ref ref="timeFile"/>
</root>
</configuration>
28.7.2 在sercie实现类创建Logger对象,输⼊⽇志
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3Ogo4lai-1690696075174)(D:/Typora/img/image-20220912183811639.png)] |
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nuaVvfPH-1690696066957)(D:/Typora/img/image-20221007212523503.png)]
ging)
-
SLF4J(Simple Logging Facade For Java )
-
JBoss Logging
-
⽇志实现(⽇志框架)
-
Log4j
-
Logback
-
28.3 SLF4J
-
SLF4J(Simple Logging Facade For Java )简单⽇志⻔⾯,定义了⼀套⽇志规范,并不是⽇志框架解决⽅法。
-
SLF4J的实现框架
[外链图片转存中…(img-dNmgFYU3-1690696066952)]
[外链图片转存中…(img-bQhnsSII-1690696066953)]
28.4 slf4j-simple
-
创建springBoot应⽤
-
移出springboot应⽤默认logback-classic的⽇志依赖(因为springboot项目自带一个默认依赖,如果不移除,就无法添加新的slf4j依赖,因为俩个依赖项目不知道该用哪一个)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</exclusion>
</exclusions>
</dependency>
- 添加依赖
<!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-simple -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.25</version>
</dependency>
- 在service层打印⽇志
@Service
public class TestServiceImpl implements TestService {
private Logger logger =
LoggerFactory.getLogger(TestServiceImpl.class);
@Override
public void testLog() {
//⽇志记录
//System.out.println("⽇志信息");
logger.info("订单添加完成");
}
}
28.5 log4j使⽤介绍
log4j没有实现slf4j,如果基于slf4j规范使⽤log4j,则需要添加slf4j-log4j12依赖
- 添加依赖
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.25</version>
<scope>test</scope>
</dependency>
[外链图片转存中…(img-bbrfe8eh-1690696066954)]
- 在resources⽬录下创建log4j.properties⽂件
log4j.rootLogger=DEBUG,stdout
# MyBatis logging configuration...
log4j.logger.org.mybatis.example.BlogMapper=TRACE
# Console output...
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=[%t] %5p - %n%m
28.6 基于SpringBoot应⽤的logback⽇志配置(常用)
SpringBoot默认整合了logback-classic⽇志框架,我们需要对logback⽇志框架进⾏配置
以⾃定义⽇志输出格式、⽇志⽂件配置、⽇志⽂件保存策略等信息
- logback配置
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="stdout"
class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<springProfile name="dev">
<pattern>%d{yyyy-MM-dd-HH:mm:ss E} %level [%thread]-
%class[%line]: %msg%n</pattern>
</springProfile>
<springProfile name="!dev">
<pattern>%d{yyyy-MM-dd-HH:mm:ss E} %level [%thread]-
%class[%line]: %msg%n</pattern>
</springProfile>
<!--⽇志的编码格式-->
<charset>UTF-8</charset>
</encoder>
</appender>
<!--这个就表示的是要把 ⽇志输出到⽂件(FileAppender)-->
<appender name="file" class="ch.qos.logback.core.FileAppender">
<file>D:/log/output.log</file>
<!--设置⽇志是否追加-->
<append>true</append>
<encoder>
<pattern>%d{yyyy-MM-dd-HH:mm:ss.SSS} %level [%thread]-
%class:%line>>%msg%n</pattern>
<charset>UTF-8</charset>
</encoder>
<!--设置⽇志写⼊是否线程安全-->
<prudent>false</prudent>
</appender>
<appender name="timeFile"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<!--TimeBasedRollingPolicy 基于时间的滚动策略-->
<rollingPolicy
class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>d:/log/log-%d{yyyy-MM-dd-HH}.log</fileNamePattern>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd-HH:mm:ss.SSS} %level [%thread]-
%class:%line>>%msg%n</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<appender name="fixedFile"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>d:/log/fixedFile.log</file>
<rollingPolicy
class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
<fileNamePattern>log/fixedFile%i.log</fileNamePattern>
<minIndex>1</minIndex>
<maxIndex>10</maxIndex>
</rollingPolicy>
<!--SizeBasedTriggeringPolicy-->
<triggeringPolicy
class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<maxFileSize>5MB</maxFileSize>
</triggeringPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd-HH:mm:ss.SSS} %level [%thread]-
%class:%line>>%msg%n
</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<root level="info">
<appender-ref ref="stdout" />
<appender-ref ref="timeFile"/>
</root>
</configuration>
28.7 在锋迷商城项⽬进⾏⽇志配置
28.7.1 在api⼦⼯程的resources⽬录添加⽇志配置⽂件
-
创建一个模板,方便使用[外链图片转存中…(img-jqOgseyA-1690696066955)][外链图片转存中…(img-fhy8KRWQ-1690696066955)][外链图片转存中…(img-8Qgf1E3o-1690696066956)]
-
logback-spring.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="stdout"
class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<springProfile name="dev">
<pattern>%d{yyyy-MM-dd-HH:mm:ss E} %level
[%thread]-%class[%line]: %msg%n</pattern>
</springProfile>
<springProfile name="!dev">
<pattern>%d{yyyy-MM-dd-HH:mm:ss E} %level
[%thread]-%class[%line]: %msg%n</pattern>
</springProfile>
<!--⽇志的编码格式-->
<charset>UTF-8</charset>
</encoder>
</appender>
<appender name="timeFile"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<!--TimeBasedRollingPolicy 基于时间的滚动策略-->
<rollingPolicy
class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--⽇志⽂件的存储路径-->
<fileNamePattern>log/log-%d{yyyy-MM-dd-HH}.log</fileNamePattern>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd-HH:mm:ss.SSS} %level [%thread]-
%class:%line>>%msg%n</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<root level="info">
<appender-ref ref="stdout"/>
<appender-ref ref="timeFile"/>
</root>
</configuration>
28.7.2 在sercie实现类创建Logger对象,输⼊⽇志
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jJETcyww-1690696075176)(D:/Typora/img/image-20220912183811639.png)] |
[外链图片转存中…(img-nuaVvfPH-1690696066957)]