一、《锋迷商城》项目介绍
1.1 项目背景
锋迷商城——电商平台
- B2C 商家对客户
- C2B2C 客户对商家对客户
1.1.1 B2C
平台运营方即商品的卖家 小米商城
- 商品
- 用户
1.1.2 C2B2C
平台运营方不卖商品(也可以卖)
卖家是平台的用户
买家也是平台用户
- 用户(店铺)
- 用户(买家)
- 服务
- 商品
1.1.3 Java
Java语言的应用领域很广,但主要应用于web领域的项目开发,web项目类型分为两类:
- 企业级开发 (供企业内部使用的系统:企业内部的管理系统CRM\ERP、学校的教务管理系统)
- 互联网开发(提供给所有互联网用户使用的系统——用户量)—— 电商
1.2 项目功能
https://www.processon.com/view/link/606bde8b1e08534321fd2103
1.3 技术选型
SSM 企业开发框架 基础的开发技术
1.3.1 单体项目
项目的页面和代码都在同一个项目,项目开发完成之后直接部署在一台服务器
单体项目遇到的问题:用户对页面静态资源以及对Java代码的访问压力都会落在Tomcat服务器上。
1.3.2 技术清单
- 项目架构:前后端分离
- 前端技术:vue、axios、妹子UI、layui、bootstrap
- 后端技术:SpringBoot+MyBatis、RESTful、swagger
- 服务器搭建:Linux、Nginx
二、项目架构的演进
2.1 单体架构
- 前后端都部署在同一台服务器上(前后端代码都在同一个应用中)
- 缺点:对静态资源的访问压力也会落在Tomcat上
2.2 前后端分离
- 前后端分离:前端和后端分离开发和部署(前后端部署在不同的服务器)
- 优点:将对静态资源的访问和对接口的访问进行分离,Tomcat服务器只负责数据服务的访问
2.3 集群与负载均衡
- 优点:提供并发能力、可用性
2.4 分布式
- 基于redis实现 分布式锁
- 分布式数据库mycat
- redis集群
- 数据库中间件
- 消息中间件
2.5 微服务架构
- 微服务架构:将原来在一个应用中开发的多个模块进行拆分,单独开发和部署
- 保证可用性、性能
三、《锋迷商城》项目搭建
基于Maven的聚合工程完成项目搭建,前端采用vue+axios,后端使用SpringBoot整合SSM
3.1 技术储备
- (√)SpringBoot: 实现无配置的SSM整合
- (√)Maven聚合工程:实现模块的复用
3.2 创建Maven聚合工程
3.2.1 构建父工程fmmall
- 创建一个maven工程、packing设置为 pom
父工程继承继承
spring-boot-starter-parent
```xml <?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">
4.0.0 org.springframework.boot spring-boot-starter-parent 2.4.4 com.qfedu fmmall 2.0.1 pom
<a name="def700f5"></a>
###### 3.2.2 创建common工程
- 选择fmmall,右键---New---Module (Maven工程)
- 修改common的pom.xml,设置packing=jar
```xml
<?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.qfedu</groupId>
<version>2.0.1</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>common</artifactId>
<packaging>jar</packaging>
</project>
3.2.3 创建beans工程
- 选择fmmall,右键—-New—-Module (Maven工程)
- 修改beans的pom.xml,设置packing ——- jar
3.2.4 创建mapper工程
- 选择fmmall,右键—-New—-Module (Maven工程)
- 修改mapper的pom.xml,设置packing ——- jar
- 在mapper的pom.xml,依赖beans
<dependency>
<groupId>com.qfedu</groupId>
<artifactId>beans</artifactId>
<version>2.0.1</version>
</dependency>
3.2.5 创建service工程
- 选择fmmall,右键—-New—-Module (Maven工程)
- 修改service的pom.xml,设置packing ——- jar
- 在service的pom.xml,依赖mapper、commom
<dependency>
<groupId>com.qfedu</groupId>
<artifactId>mapper</artifactId>
<version>2.0.1</version>
</dependency>
<dependency>
<groupId>com.qfedu</groupId>
<artifactId>common</artifactId>
<version>2.0.1</version>
</dependency>
3.2.6 创建api工程
- 选择fmmall,右键—-New—-Module (SpringBoot工程)
修改api的pom.xml,继承fmmall,删除自己的groupId 和 version
<parent>
<groupId>com.qfedu</groupId>
<artifactId>fmmall</artifactId>
<version>2.0.1</version>
</parent>
将spring boot的依赖配置到父工程fmmall的pom.xml
在父工程fmmall的pom.xml的modules添加api
```xml<?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">
4.0.0 org.springframework.boot spring-boot-starter-parent 2.4.4
<groupId>com.qfedu</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>
<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>
- 在api中,依赖service
```xml
<dependency>
<groupId>com.qfedu</groupId>
<artifactId>service</artifactId>
<version>2.0.1</version>
</dependency>
- api的pom.xml继承fmmall
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所需的依赖
```xmlmysql mysql-connector-java 5.1.47
- 在**mapper子工程**的`resources`目录创建`application.yml`
yml
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 包的路径java
@SpringBootApplication
@MapperScan("com.qfedu.fmmall.dao")
public class ApiApplication {
public static void main(String[] args) {
SpringApplication.run(ApiApplication.class, args);
}
}
#### 3.5 基于SpringBoot的单元测试
###### 3.5.1 添加依赖
```xml
<a name="f22b87de"></a>
###### 3.5.2 测试类
java
@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);
}
}
<a name="e18bd7e7"></a>
#### 3.6 整合Druid
<a name="add16078"></a>
###### 3.6.1 添加依赖
- 在**mapper子工程**添加druid-starter
xml
<a name="b631061c"></a>
###### 3.6.2 修改数据源配置
- 修改**mapper子工程**application.yml文件
yaml
spring:
datasource:
druid:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/db2010_mybatis?characterEncoding=utf-8
username: root
password: admin123
mybatis:
mapper-locations: classpath:mappers/Mapper.xml
type-aliases-package: com.qfedu.fmmall.entity
<a name="8076bfe8"></a>
## 四、《锋迷商城》数据库设计
<a name="1419f72a"></a>
#### 4.1 软件开发步骤
- 问题定义/提出问题
- 可行性分析(技术、成本、法律法规)
- 需求分析(需求采集、需求分析)---->甲方
- 概要设计
- 架构设计(技术选型、架构模式、项目搭建)
- 数据库设计
- UI设计
- 业务流程设计
- 详细设计
- 实现步骤(业务流程的实现细节)
- 编码
- 根据设计好的实现步骤进行代码实现
- 开发过程中开发者要进行单元测试
- 测试
- 集成测试
- 功能测试(黑盒)
- 性能测试(白盒)
- 交付/部署实施
<a name="6f370ff1"></a>
#### 4.2 数据库设计流程
- 根据项目功能分析数据实体(数据实体,就是应用系统中要存储的数据对象)
- 商品、订单、购物车、用户、评价、地址...
- 提取数据实体的数据项(数据对象的属性)
- 商品(商品id、商品名称、商品描述,特征)
- 地址(姓名、地址、电话...)
- 使用数据库设计三范式检查数据项是否合理
- 分析实体关系:E-R图
- 数据库建模(三线图)、建模工具
- 建库建表-SQL
<a name="c901c475"></a>
#### 4.3 数据库设计分析
<a name="952265a6"></a>
###### 4.3.1 PDMan建模工具使用
- 可视化创建数据表(数据表)
- 视图显示数据表之间的关系(关系图)
- 导出SQL指令(模型--导出DDL脚本)
- 记录数据设计的版本-数据库模型版本的管理(模型版本)
- 同步数据模型到数据库(开始-数据库连接)<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/23162011/1644073758532-af935a2d-b14b-405a-9311-8952112a8ff5.png#clientId=uc179a9d3-9ffa-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=276&id=u5916aa68&margin=%5Bobject%20Object%5D&name=image.png&originHeight=401&originWidth=1531&originalType=binary&ratio=1&rotation=0&showTitle=false&size=40632&status=done&style=none&taskId=u3a2be08e-2ea2-4730-a9c4-21582466a52&title=&width=1054.9526582419683)
<a name="d4a71d63"></a>
###### 4.3.2 分析《锋迷商城》的数据库模型
- 用户
- 首页
- 商品
- 购物车
- 订单 和 订单项
- 评论
<a name="7b97b954"></a>
#### 4.4 SPU 和 SKU
<a name="81c32735"></a>
###### 4.4.1 SPU
> SPU(Standard Product Unit):标准化产品单元。是商品信息聚合的最小单位,是一组可复用、易检索的标准化信息的集合,该集合描述了一个产品的特性。通俗点讲,属性值、特性相同的商品就可以称为一个SPU。
1 荣耀8
2 小米10
<a name="7caf41fd"></a>
###### 4.4.2 SKU
> [SKU](https://baike.baidu.com/item/SKU/5016808)(中文译为[最小存货单位](https://baike.baidu.com/item/%E6%9C%80%E5%B0%8F%E5%AD%98%E8%B4%A7%E5%8D%95%E4%BD%8D/892217),英文全称为Stock Keeping Unit,简称SKU,定义为保存库存控制的最小可用单位)
>
> `特征或者特性`
101 8G / 128G 10 1800 1
102 4G / 128G 20 1500 1
103 8G / 128G 12 2999 2
104 12G / 256G 11 3999 2
<a name="76d5a3d6"></a>
#### 4.5 建库建表
<a name="7126918e"></a>
###### 4.5.1 创建数据表
- 从PDMan导出sql,导入到mysql
<a name="c8909d75"></a>
###### 4.5.2 准备测试数据
- 首页轮播图 index_img<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/23162011/1644073775983-1551003a-45d2-443e-866f-49df608a00e7.png#clientId=uc179a9d3-9ffa-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=89&id=u86af9b2e&margin=%5Bobject%20Object%5D&name=image.png&originHeight=129&originWidth=1048&originalType=binary&ratio=1&rotation=0&showTitle=false&size=13570&status=done&style=none&taskId=ua1b7ae75-52b0-46c5-a71c-3186af6ff52&title=&width=722.1361109324512)
- 首页类别信息 category<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/23162011/1644073779976-da9d60ec-e903-497e-b779-ca7e7c1f92b5.png#clientId=uc179a9d3-9ffa-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=309&id=u76d8749d&margin=%5Bobject%20Object%5D&name=image.png&originHeight=449&originWidth=1065&originalType=binary&ratio=1&rotation=0&showTitle=false&size=42632&status=done&style=none&taskId=u425c95b9-2694-4f57-a6ab-1abc8bd7feb&title=&width=733.8501508998669)
- 商品信息
- sku
<a name="440ff86d"></a>
## 五、《锋迷商城》业务流程设计-接口规范
> 在企业项目开发中,当完成项目的需求分析、功能分析、数据库分析与设计之后,项目组就会按照项目中的功能进行开发任务的分配
![image.png](https://cdn.nlark.com/yuque/0/2022/png/23162011/1644073786844-c8b8add2-5447-4be0-be34-2b38308764fd.png#clientId=uc179a9d3-9ffa-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=453&id=u1acbcadb&margin=%5Bobject%20Object%5D&name=image.png&originHeight=657&originWidth=1148&originalType=binary&ratio=1&rotation=0&showTitle=false&size=29626&status=done&style=none&taskId=u1437ad59-7e03-46d8-9220-860b8e18293&title=&width=791.0422283878378)
<a name="2ac6a4a3"></a>
#### 5.1 前后端分离与单体架构流程实现的区别
> 单体架构:页面和控制之间可以进行跳转,同步请求控制器,流程控制由控制器来完成
>
> 前后端分离架构:前端和后端分离开发和部署,前端只能通过异步向后端发送请求,后端只负责接收请求及参数、处理请求、返回处理结果,但是后端并不负责流程控制,流程控制是由前端完成
<a name="f43f7c7a"></a>
###### 5.1.1 单体架构
![image.png](https://cdn.nlark.com/yuque/0/2022/png/23162011/1644073793754-ac653807-a576-443b-8b2f-b47e73a74e44.png#clientId=uc179a9d3-9ffa-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=220&id=u48aed380&margin=%5Bobject%20Object%5D&name=image.png&originHeight=319&originWidth=889&originalType=binary&ratio=1&rotation=0&showTitle=false&size=16119&status=done&style=none&taskId=ua018614e-53a2-4549-912d-2d7fbf7aebe&title=&width=612.5753841783866)
<a name="7cefcd12"></a>
###### 5.1.2 前后端分离架构
![image.png](https://cdn.nlark.com/yuque/0/2022/png/23162011/1644073798657-abf225bb-4890-452e-a3f9-17d8d93f22c5.png#clientId=uc179a9d3-9ffa-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=231&id=ue8ccfbf0&margin=%5Bobject%20Object%5D&name=image.png&originHeight=335&originWidth=885&originalType=binary&ratio=1&rotation=0&showTitle=false&size=24728&status=done&style=none&taskId=ubfb9be0d-30df-419e-9bf1-09b1eedae93&title=&width=609.8191394801711)
<a name="f1cb8b64"></a>
#### 5.2 接口介绍
<a name="7df19f83"></a>
###### 5.2.1 接口概念
> 狭义的理解:就是控制器中可以接受用户请求的某个方法
>
> 应用程序编程接口,简称API(Application Programming Interface),就是软件系统不同组成部分衔接的约定
<a name="9e861a45"></a>
###### 5.2.2 接口规范
> 作为一个后端开发者,我们不仅要完成接口程序的开发,还要编写接口的说明文档——接口规范
**接口规范示例**:
参考:[《锋迷商城》后端接口说明](%E3%80%8A%E9%94%8B%E8%BF%B7%E5%95%86%E5%9F%8E%E3%80%8B%E5%90%8E%E7%AB%AF%E6%8E%A5%E5%8F%A3%E8%AF%B4%E6%98%8E.docx)
**Example:**
java
@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);
}
```
@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); }
接口说明:接收帐号和密码进行校验,返回校验结果
请求URL:http://192.168.55.11:8080/fmmall/user/login
请求方式:GET
请求参数:
| key | 类型 | 是否必须 | 说明 |
| —- | —- | —- | —- |
| username | string | 是 | 用户登录的帐号 |
| password | string | 否 | 用户登录的密码,默认值为 111111 |
响应结果:
code 响应状态码 10000表示成功,10001表示失败
msg 响应提示 提示信息
data 响应数据 如果登录成功则响应user信息,失败响应null
#### 5.3 Swagger
> 前后端分离开发,后端需要编写接口说明文档,会耗费比较多的时间
>> swagger是一个用于生成服务器接口的规范性文档、并且能够对接口进行测试的工具 ###### 5.3.1 作用 - 生成接口说明文档 - 对接口进行测试 ###### 5.3.2 Swagger整合 - 在api子工程添加依赖(Swagger2 \ Swagger UI) - Swagger2放在beans与common中,SwaggerUI放在api中
```xml
- 在**api子工程**创建swagger的配置(Java配置方式)
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.liangge.com”,”liangge@wang.com”) );
ApiInfo apiInfo = apiInfoBuilder.build();
Docket docket = new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo) //指定生成的文档中的封面信息:文档标题、版本、作者
.select()
.apis(RequestHandlerSelectors.basePackage(“com.qfedu.fmmall.controller”))
.paths(PathSelectors.any())
.build();
return docket;
}
}
- 修改application.yml<br />**修改原因是Springfox使用的路径匹配是基于AntPathMatcher的,而Spring Boot 2.6.X使用的是PathPatternMatcher,修改application.yaml spring: mvc: pathmatch: matching-strategy: ANT_PATH_MATCHER**
yml
spring:
datasource:
druid:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/fmwy?characterEncoding=utf-8&useSSL=false
username: root
password: mysql
mvc:
pathmatch:
matching-strategy: ant_path_matcher
mybatis:
mapper-locations: classpath:mappers/*Mapper.xml
type-aliases-package: com.chang.ygmall.entity
- 测试:
- 启动SpringBoot应用,访问:[http://localhost:8080/swagger-ui.html](http://localhost:8080/swagger-ui.html)
<a name="d39778a6"></a>
###### 5.3.3 Swagger注解说明
> swagger提供了一套注解,可以对每个接口进行详细说明
`@Api` 类注解,在控制器类添加此注解,可以对控制器类进行功能说明
java
@Api(value = “提供商品添加、修改、删除及查询的相关接口”,tags = “商品管理”)
`@ApiOperation`方法注解:说明接口方法的作用
`@ApiImplicitParams`和`@ApiImplicitParam` 方法注解,说名接口方法的参数
java
@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` 当接口参数和返回值为对象类型时,在实体类中添加注解说明
java
@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`接口方法注解,添加此注解的方法将不会生成到接口文档中
<a name="5e424dd1"></a>
###### 5.3.4 Swagger-ui 插件
- 导入插件的依赖
xml
- 文档访问<br />[http://ip](http://ip):port/doc.html
<a name="98d43eac"></a>
#### 5.4 RESTful
> 前后端分离开发的项目中,前后端之间是接口进行请求和响应,后端向前端提供请求时就要对外暴露一个URL;URL的设计不能是随意的,需要遵从一定的设计规范——RESTful
RESTful 是一种Web api的标准,也就是一种url设计风格/规范
- `每个URL请求路径代表服务器上的唯一资源`
传统的URL设计:
http://localhost:8080/goods/delete?goodsId=1 商品1
http://localhost:8080/goods/delete?goodsId=2 商品2
RESTful设计:
http://localhost:8080/goods/delete/1 商品1
http://localhost:8080/goods/delete/2 商品2
```java
@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 (预检)
java
根据ID删除一个商品:
//http://localhost:8080/goods/1 [delete]
@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]
@RequestMapping(value = "/{id}",method = RequestMethod.GET)
public ResultVO getGoods(@PathVariable("id") int goodsId){
return null;
}
- 接口响应的资源的表现形式采用JSON(或者XML)
- 在控制类或者每个接口方法添加@ResponseBody
注解将返回的对象格式为json
- 或者直接在控制器类使用@RestController
注解声明控制器
- 前端(Android\ios\pc)通过无状态的HTTP协议与后端接口进行交互
## 六、《锋迷商城》设计及实现—用户管理
#### 6.1 实现流程
#### 6.2 后端接口开发
###### 6.2.1 完成DAO操作
1. 创建实体类java
@Data
@NoArgsConstructor
@AllArgsConstructor
@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接口、定义操作方法java
public interface UserDAO {
//用户注册
public int insertUser(User user);
//根据用户名查询用户信息
public User queryUserByName(String name);
}
3. 创建DAO接口的mapper文件并完成配置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.UserDAO">
<insert id="insertUser">
insert into users(username,password,user_regtime,user_modtime)
values(#{username},#{password},#{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 users
where username=#{name}
</select>
</mapper>
###### 6.2.2 完成Service业务
1. 创建service接口java
public interface UserService {
//用户注册
public ResultVO userResgit(String name, String pwd);
//用户登录
public ResultVO checkLogin(String name, String pwd);
}
2. 创建service接口实现类,完成业务实现java
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserDAO userDAO;
@Transactional
public ResultVO userResgit(String name, String pwd) {
synchronized (this) {
//1.根据用户查询,这个用户是否已经被注册
User user = userDAO.queryUserByName(name);
//2.如果没有被注册则进行保存操作
if (user == null) {
String md5Pwd = MD5Utils.md5(pwd);
user = new User();
user.setUsername(name);
user.setPassword(md5Pwd);
user.setUserImg("img/default.png");
user.setUserRegtime(new Date());
user.setUserModtime(new Date());
int i = userDAO.insertUser(user);
if (i > 0) {
return new ResultVO(10000, "注册成功!", null);
} else {
return new ResultVO(10002, "注册失败!", null);
}
} else {
return new ResultVO(10001, "用户名已经被注册!", null);
}
}
}
@Override
public ResultVO checkLogin(String name, String pwd) {
User user = userDAO.queryUserByName(name);
if(user == null){
return new ResultVO(10001,"登录失败,用户名不存在!",null);
}else{
String md5Pwd = MD5Utils.md5(pwd);
if(md5Pwd.equals(user.getPassword())){
return new ResultVO(10000,"登录成功!",user);
}else{
return new ResultVO(10001,"登录失败,密码错误!",null);
}
}
}
}
###### 6.2.3 完成Controller提供接口
1. 创建controller,调用service
1. 添加接口注解java
@RestController
@RequestMapping("/user")
@Api(value = "提供用户的登录和注册接口",tags = "用户管理")
public class UserController {
@Resource
private UserService userService;
@ApiOperation("用户登录接口")
@ApiImplicitParams({
@ApiImplicitParam(dataType = "string",name = "username", value = "用户登录账号",required = true),
@ApiImplicitParam(dataType = "string",name = "password", value = "用户登录密码",required = true)
})
@GetMapping("/login")
public ResultVO login(@RequestParam("username") String name,
@RequestParam(value = "password") String pwd){
ResultVO resultVO = userService.checkLogin(name, pwd);
return resultVO;
}
@ApiOperation("用户注册接口")
@ApiImplicitParams({
@ApiImplicitParam(dataType = "string",name = "username", value = "用户注册账号",required = true),
@ApiImplicitParam(dataType = "string",name = "password", value = "用户注册密码",required = true)
})
@PostMapping("/regist")
public ResultVO regist(String username,String password){
ResultVO resultVO = userService.userRegist(username, password);
return resultVO;
}
}
###### 6.2.4 接口测试
- 基于swagger进行测试
#### 6.3 前端跨域访问
###### 6.3.1 跨域访问概念
- 什么时跨域访问? > AJAX 跨域访问是用户访问A网站时所产生的对B网站的跨域访问请求均提交到A网站的指定页面 ###### 6.3.2 如何解决跨域访问? - 前端使用JSONP设置 - 后端使用
@CrossOrigin
—- 就是设置响应头允许跨域
#### 6.4 前端页面之间的传值
- cookie是浏览器端的缓存,容量大小受浏览器产品的限制,cookie可将数据传递给后端
- localStorage/sessionStorage 为了存储更大容量的数据,localStorage不能将数据传递给后端,只能在前端传递数据,生命周期比较长久
###### 6.4.1 cookie
- 工具方法封装为cookie_utils.js:javascript
var operator = "=";
function getCookieValue(keyStr){
var value = null;
var s = window.document.cookie;
var arr = s.split("; ");
for(var i=0; i<arr.length; i++){
var str = arr[i];
var k = str.split(operator)[0];
var v = str.split(operator)[1];
if(k == keyStr){
value = v;
break;
}
}
return value;
}
function setCookieValue(key,value){
document.cookie = key+operator+value;
}
- A页面java
setCookieValue("username",userInfo.username);
setCookieValue("userimg",userInfo.userImg);
- B页面javascript
var name = getCookieValue("username");
var img = getCookieValue("userimg");
###### 6.4.2 localStorage
- A页面javascript
localStorage.setItem("user",JSON.stringify(userInfo));
- B页面javascript
var jsonStr = localStorage.getItem("user");
var userInfo = eval("("+jsonStr+")");
//取到值以后 移出localStorage键值对
localStorage.removeItem("user");
## 七、前后端分离用户认证-JWT
#### 7.1 基于session实现单体项目用户认证
> 在单体项目中如何保证受限资源在用户未登录的情况下不允许访问?
> 在单体项目中,视图资源(页面)和接口(控制器)都在同一台服务器,用户的多次请求都是基于同一个会话(session),因此可以借助session来进行用户认证判断:
>> 1.当用户登录成功之后,将用户信息存放到session >
> 2.当用户再次访问受限资源时,验证session中是否存在用户信息,可以根据session有无用户信息来判断用户是否登录 #### 7.2 基于token实现前后端分离用户认证 > 由于在前后端分离项目开发中,前后端之间是通过异步交互完成数据访问的,请求是无状态的,因此不能基于session实现用户的认证。 #### 7.3 基于token的用户认证的实现 ###### 7.3.1 登录认证接口生成token
java
// UserController
@GetMapping("/login")
public ResultVO login(@RequestParam("username") String name,
@RequestParam(value = "password") String pwd){
ResultVO resultVO = userService.checkLogin(name, pwd);
return resultVO;
}
java
// UserServiceImpl
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(ResStatus.NO,"登录失败,用户名不存在!",null);
}else{
String md5Pwd = MD5Utils.md5(pwd);
if(md5Pwd.equals(users.get(0).getPassword())){
//如果登录验证成功,则需要生成令牌token(token就是按照特定规则生成的字符串)
String token = Base64Utils.encode(name+"QIANfeng6666");
return new ResultVO(ResStatus.OK,token,users.get(0));
}else{
return new ResultVO(ResStatus.NO,"登录失败,密码错误!",null);
}
}
}
###### 7.3.2 登录页面接收到token存储到cookie
javascript
// 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访问接口
html
<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
java
@GetMapping("/list")
@ApiImplicitParam(dataType = "string",name = "token", value = "授权令牌",required = true)
public ResultVO listCarts(String token){
//1.获取token
//2.校验token
if(token == null){
return new ResultVO(ResStatus.NO,"请先登录",null);
}else{
String decode = Base64Utils.decode(token);
if(decode.endsWith("QIANfeng6666")){
//token校验成功
return new ResultVO(ResStatus.OK,"success",null);
}else{
return new ResultVO(ResStatus.NO,"登录过期,请重新登录!",null);
}
}
}
#### 7.4 JWT
> 如果按照上述规则生成token:
>> 1.简易的token生成规则安全性较差,如果要生成安全性很高的token对加密算法要求较高; >
> 2.无法完成时效性的校验(登录过期) ###### 7.4.1 JWT简介 - JWT: Json Web Token - 官网:https://jwt.io - jwt的结构
###### 7.4.2 生成JWT - 添加依赖放到service层
```xml
- 生成token
```java
zhoJwtBuilder 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)) //设置过期时间
.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
创建拦截器(在api中创建interceptor文件夹)
```java @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();
}
}
- 配置拦截器
```java
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Autowired
private CheckTokenInterceptor checkTokenInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(checkTokenInterceptor)
.addPathPatterns("/**") //拦截所有
.excludePathPatterns("/user/**"); //除了user开头
}
}
7.5 请求头传递token
前端但凡访问受限资源,都必须携带token发送请求;token可以通过请求行(params)、请求头(header)以及请求体(data)传递,但是习惯性使用header传递
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请求
@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(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();
}
}
八、首页—轮播图
8.1 实现流程分析
- 流程图
- 接口
- 查询轮播图信息返回
8.2 完成后台接口开发
8.2.1 数据库操作实现
- 分析数据表结构
- 添加测试数据
- 编写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.2.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.2.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 完成前端功能
当进入到index.html,在进行页面初始化之后,就需要请求轮播图数据进行轮播图的显示
index.html |
---|
九、首页-分类列表
9.1 实现流程分析
- 方案一:一次性查询三级分类
- 优点:只需要一次查询,根据一级分类显示二级分类时响应速度较快
- 缺点:数据库查询效率较低,页面首次加载的速度也相对较慢
- 方案二:先只查询一级分类,用户点击/鼠标移动到一级分类,动态加载二级分类
- 优点:数据库查询效率提高,页面首次加载速度提高
- 缺点:需要多次连接数据库
9.2 接口开发
9.2.1 数据库操作实现
- 数据表结构
- 添加测试数据
编写接口实现所需的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
子查询
-- 根据父级分类的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;
}
}
在CategoryMapper定义操作方法
```java @Repository public interface CategoryMapper extends GeneralDAO{ //1.连接查询 public List
selectAllCategories(); //2.子查询:根据parentId查询子分类 public List
selectAllCategories2(int parentId);
}
- 映射配置
```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.CategoryMapper">
<resultMap id="BaseResultMap" type="com.qfedu.fmmall.entity.Category">
<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>
<resultMap id="categoryVOMap" type="com.qfedu.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.qfedu.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.qfedu.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>
<!---------------------------------------------------------------------------->
<resultMap id="categoryVOMap2" type="com.qfedu.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.qfedu.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.2 业务层实现
CategoryService接口
```java public interface CategoryService {public ResultVO listCategories();
}
- CategoryServiceImpl
```java
@Service
public class CategoryServiceImpl implements CategoryService {
@Autowired
private CategoryMapper categoryMapper;
public ResultVO listCategories() {
List<CategoryVO> categoryVOS = categoryMapper.selectAllCategories();
ResultVO resultVO = new ResultVO(ResStatus.OK, "success", categoryVOS);
return resultVO;
}
}
9.2.3 控制层实现
- IndexController
```java @Autowired private CategoryService categoryService;
@GetMapping(“/category-list”) @ApiOperation(“商品分类查询接口”) public ResultVO listCatetory(){ return categoryService.listCategories(); }
<a name="619b66e2"></a>
#### 9.3 前端功能实现
<a name="6f39a371"></a>
## 十、首页-商品推荐
<a name="9b003689"></a>
#### 10.1 流程分析
![image.png](https://cdn.nlark.com/yuque/0/2022/png/23162011/1644073991011-59df53e2-9369-4fdc-b1b5-5c41fa830173.png#clientId=uc179a9d3-9ffa-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=94&id=u0e5d185b&margin=%5Bobject%20Object%5D&name=image.png&originHeight=136&originWidth=1026&originalType=binary&ratio=1&rotation=0&showTitle=false&size=31168&status=done&style=none&taskId=udb87e3d1-94ee-4ac6-8213-ecd5572935a&title=&width=706.9767650922662)
<a name="265accfc"></a>
#### 10.2 接口开发
<a name="8fb8738a"></a>
###### 10.2.1 数据库实现
> 商品推荐算法:推荐最新上架的商品
>
> 说明:商品推荐算法是根据多个维度进行权重计算,计算出一个匹配值
- 数据表分析及数据准备
- sql
```sql
-- 商品推荐:查询最新上架的商品
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 ```java public interface ProductMapper extends GeneralDAO
{ public List
selectRecommendProducts();
}
- ProductImgMapper
```java
public interface ProductImgMapper extends GeneralDAO<ProductImg> {
//根据商品id查询当前商品的图片信息
public List<ProductImg> selectProductImgByProductId(int productId);
}
- 配置映射文件
- ProductMapper.xml
```xml
- ProductMapper.xml
```xml
- ProductImgMapper.xml
```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接口
```java public interface ProductService {public ResultVO listRecommendProducts();
}
- ProductServiceImpl实现类
```java
@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
```java @Autowired private ProductService productService;
@GetMapping(“/list-recommends”) @ApiOperation(“查询推荐商品接口”) public ResultVO listRecommendProducts() { return productService.listRecommendProducts(); }
<a name="2ab1a403"></a>
#### 10.3 前端实现
<a name="6b1b6a68"></a>
## 十一、首页-分类商品推荐
> 按照商品的分类(一级分类)推荐销量最高的6个商品
<a name="55a50547"></a>
#### 11.1 流程分析
> 加载分类商品推荐有两种实现方案:
>
> 方案一:当加载首页面时不加载分类的推荐商品,监听进度条滚动事件,当进度条触底(滚动指定的距离)就触发分类推荐商品的加载,每次只加载一个分类的商品。
![image.png](https://cdn.nlark.com/yuque/0/2022/png/23162011/1644074005971-0eeda008-c179-4543-80b3-62c8c0a43ba5.png#clientId=uc179a9d3-9ffa-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=72&id=u09b44f1d&margin=%5Bobject%20Object%5D&name=image.png&originHeight=105&originWidth=1009&originalType=binary&ratio=1&rotation=0&showTitle=false&size=23153&status=done&style=none&taskId=ua2d2af85-e497-40fb-9779-7f6c68e14d3&title=&width=695.2627251248505)
> 方案二:一次性加载所有分类的推荐商品,整体进行初始化。
![image.png](https://cdn.nlark.com/yuque/0/2022/png/23162011/1644074010577-ccceeaf6-0340-4797-97c8-07bc1e961547.png#clientId=uc179a9d3-9ffa-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=76&id=u34561006&margin=%5Bobject%20Object%5D&name=image.png&originHeight=110&originWidth=1043&originalType=binary&ratio=1&rotation=0&showTitle=false&size=21655&status=done&style=none&taskId=u19b6f240-807a-479e-b4c5-68da88da84d&title=&width=718.6908050596819)
<a name="a22c3d19"></a>
#### 11.2 接口实现
<a name="06fa60ba"></a>
###### 11.2.1 数据库实现
- 数据准备
```sql
-- 添加商品
-- 添加十个分类下的商品:
insert into product(product_id,product_name,category_id,root_category_id,sold_num,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_num,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_num,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_num,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_num,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_num,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_num,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_num,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_num,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_num,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_num,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_num,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_num,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_num,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;
实体类:
```java @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
categories; //实现首页分类商品推荐 private List products;
}
- 在Mapper接口中定义查询方法
| CategoryMapper |
| --- |
| ![image.png](https://cdn.nlark.com/yuque/0/2022/png/23162011/1644074033713-d0894c70-aa93-4cf6-b623-f82d60c65bb6.png#clientId=uc179a9d3-9ffa-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=254&id=u7fd6a17d&margin=%5Bobject%20Object%5D&name=image.png&originHeight=368&originWidth=1121&originalType=binary&ratio=1&rotation=0&showTitle=false&size=31532&status=done&style=none&taskId=uaae6ecd6-8a8e-4151-8370-7a7548d6630&title=&width=772.4375766748834) |
| ProductMapper |
| --- |
| ![image.png](https://cdn.nlark.com/yuque/0/2022/png/23162011/1644074029253-9fa31a06-c3c2-423d-9b48-2c9139dfea4b.png#clientId=uc179a9d3-9ffa-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=241&id=u48579179&margin=%5Bobject%20Object%5D&name=image.png&originHeight=350&originWidth=1038&originalType=binary&ratio=1&rotation=0&showTitle=false&size=28892&status=done&style=none&taskId=u3c9cc78f-53aa-4304-b346-e5726aac5b4&title=&width=715.2454991869125) |
- 映射配置
| ProductMapper.xml |
| --- |
| ![image.png](https://cdn.nlark.com/yuque/0/2022/png/23162011/1644074128996-fa8461d0-8110-4412-8672-4b46c3fde3a4.png#clientId=uc179a9d3-9ffa-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=575&id=uc9f896d3&margin=%5Bobject%20Object%5D&name=image.png&originHeight=834&originWidth=1387&originalType=binary&ratio=1&rotation=0&showTitle=false&size=113051&status=done&style=none&taskId=u5399b560-22c5-444c-9580-ccbbad8256b&title=&width=955.7278491062117) |
| CategoryMapper.xml |
| --- |
| ![image.png](https://cdn.nlark.com/yuque/0/2022/png/23162011/1644074134474-2b500ca0-0b2c-4264-8548-ae02e0991321.png#clientId=uc179a9d3-9ffa-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=457&id=u3cdb596a&margin=%5Bobject%20Object%5D&name=image.png&originHeight=663&originWidth=1361&originalType=binary&ratio=1&rotation=0&showTitle=false&size=93620&status=done&style=none&taskId=u5d992f62-c7c1-43fa-844d-c5082c742aa&title=&width=937.8122585678111) |
<a name="ec704cc9"></a>
###### 11.2.2 业务层实现
<a name="5c8a3db0"></a>
###### 11.2.3 控制层实现
<a name="69f32560"></a>
#### 11.3 前端实现
<a name="270aea62"></a>
## 十二、商品详情展示—显示商品基本信息
> 点击首页推荐的商品、轮播图商品广告、商品列表页面点击商品,就会进入到商品的详情页面
<a name="a22d397c"></a>
#### 12.1 流程分析
![image.png](https://cdn.nlark.com/yuque/0/2022/png/23162011/1644074142784-6b5a83f1-2d50-4012-9867-dbb69e0ddc9a.png#clientId=uc179a9d3-9ffa-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=244&id=u89b529e1&margin=%5Bobject%20Object%5D&name=image.png&originHeight=354&originWidth=1235&originalType=binary&ratio=1&rotation=0&showTitle=false&size=85265&status=done&style=none&taskId=uf6dc2f8e-8514-4878-ba3d-a03df8a110a&title=&width=850.990550574024)
<a name="8a84c800"></a>
#### 12.2 商品基础信息-接口实现
> 商品基本信息、商品套餐、商品图片
- SQL
```sql
-- 根据id查询商品基本信息
select * from product where product_id=3;
-- 根据商品id查询当前商品的图片(√)
select * from product_img where item_id=3;
-- 根据商品id查询当前商品的套餐
select * from product_sku where product_id=3;
- 因为上述的三个查询都是单表查询,可以通过tkmapper完成,无需在Mapper接口定义新的方法
- 业务层实现
| ProductService接口 | | —- | | |
ProductServiceImpl类实现 |
---|
- 控制层实现
| ProductController类 | | —- | | |
12.3 商品基础信息-前端显示
十三、商品详情展示—显示商品参数信息
13.1 接口实现
根据商品id查询商品参数信息
- 数据库操作直接只用tkMapper的默认方法实现
业务层实现
| | | —- | | |控制层实现
13.2 前端显示商品参数
13.3 前端显示商品细节
前端页面间URL传值
utils.js
function getUrlParam(key){
var url = decodeURI( window.location.toString() );
var arr = url.split("?");
if(arr.length>1){
var params = arr[1].split("&");
for(var i=0; i<params.length; i++){
var param = params[i]; //"pid=101"
if(param.split("=")[0] == key ){
return param.split("=")[1];
}
}
}
return null;
}
a.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<a href="b.html?pid=101&pname=咪咪虾条">跳转到B页面</a>
</body>
</html>
b.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
This is Page B...
<hr/>
<script type="text/javascript" src="js/utils.js" ></script>
<script type="text/javascript">
var pid = getUrlParam("pid");
</script>
</body>
</html>
十四、商品详情展示—显示商品评论信息
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
@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);
}
- 映射配置:
14.1.2 业务层实现
- 创建
ProductCommontsService
接口定义方法
public interface ProductCommontsService {
public ResultVO listCommontsByProductId(String productId);
}
- 创建实现类
ProductCommontsServiceImpl
实现查询操作
@Service
public class ProductCommontsServiceImpl implements ProductCommontsService {
@Autowired
private ProductCommentsMapper productCommentsMapper;
@Override
public ResultVO listCommontsByProductId(String productId) {
List<ProductCommentsVO> productCommentsVOS = productCommentsMapper.selectCommontsByProductId(productId);
ResultVO resultVO = new ResultVO(ResStatus.OK, "success", productCommentsVOS);
return resultVO;
}
}
14.1.3 控制层实现
- ProductController
@ApiOperation("商品评论信息查询接口")
@GetMapping("/detail-commonts/{pid}")
public ResultVO getProductCommonts(@PathVariable("pid") String pid){
return productCommontsService.listCommontsByProductId(pid);
}
14.2 前端评论内容显示
十五、商品详情展示—商品评论分页及统计信息
15.1 流程分析
15.2 接口开发
15.2.1 改造商品评论列表接口
分页查询
定义PageHelper
```java @Data @NoArgsConstructor @AllArgsConstructor public class PageHelper{ //总记录数 private int count;
//总页数 private int pageCount;
//分页数据 private List
list;
}
- 改造数据库操作
| ProductCommentsMapper 接口 |
| --- |
| ![image.png](https://cdn.nlark.com/yuque/0/2022/png/23162011/1644074222438-ec3cc1e7-7c83-4da2-8d02-25a88e1206c3.png#clientId=uc179a9d3-9ffa-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=280&id=u4276b3e3&margin=%5Bobject%20Object%5D&name=image.png&originHeight=407&originWidth=1124&originalType=binary&ratio=1&rotation=0&showTitle=false&size=39768&status=done&style=none&taskId=u2118e6af-d9c0-4632-9d85-e71bfdcb031&title=&width=774.504760198545) |
| ProductCommentsMapper.xml映射配置 |
| --- |
| ![image.png](https://cdn.nlark.com/yuque/0/2022/png/23162011/1644074227311-9abc8558-6762-46b3-9805-709b05813560.png#clientId=uc179a9d3-9ffa-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=256&id=u8071ea1e&margin=%5Bobject%20Object%5D&name=image.png&originHeight=372&originWidth=1110&originalType=binary&ratio=1&rotation=0&showTitle=false&size=35164&status=done&style=none&taskId=u55d9fda8-6ec4-4208-97b5-7e5e8bf17bc&title=&width=764.8579037547909) |
- 改造业务逻辑层
| ProductCommontsService接口 |
| --- |
| ![image.png](https://cdn.nlark.com/yuque/0/2022/png/23162011/1644074232312-4e6c2872-2f86-4ef0-adef-645a142a8e54.png#clientId=uc179a9d3-9ffa-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=241&id=uced38017&margin=%5Bobject%20Object%5D&name=image.png&originHeight=350&originWidth=1087&originalType=binary&ratio=1&rotation=0&showTitle=false&size=32414&status=done&style=none&taskId=ub3b85715-0fbc-4357-a547-baac74a3eca&title=&width=749.009496740052) |
| ProductCommontsServiceImpl |
| --- |
| ![image.png](https://cdn.nlark.com/yuque/0/2022/png/23162011/1644074237388-268cf522-68d7-473e-a368-d0521e9b018c.png#clientId=uc179a9d3-9ffa-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=517&id=u3192b23d&margin=%5Bobject%20Object%5D&name=image.png&originHeight=750&originWidth=1434&originalType=binary&ratio=1&rotation=0&showTitle=false&size=98355&status=done&style=none&taskId=ud24107ec-b734-4229-8af0-ad9769d524a&title=&width=988.1137243102434) |
- 改造控制层
| ProductController |
| --- |
| ![image.png](https://cdn.nlark.com/yuque/0/2022/png/23162011/1644074244382-8c177ea9-c105-48c1-bed9-76e62d416af6.png#clientId=uc179a9d3-9ffa-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=188&id=u3ec4308b&margin=%5Bobject%20Object%5D&name=image.png&originHeight=273&originWidth=1098&originalType=binary&ratio=1&rotation=0&showTitle=false&size=41377&status=done&style=none&taskId=ubb30f5ef-e09e-4b04-993a-985bf04ff79&title=&width=756.5891696601444) |
<a name="1eb38d7c"></a>
###### 15.2.2 评价统计接口实现
- 数据库实现
- 统计当前商品的总记录数
- 统计当前商品的好评/中评/差评
- 业务层实现: `ProductCommontsServiceImpl`
```java
@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);
//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.查询c评评价数
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.计算好评率
double percent = (Double.parseDouble(goodTotal+"") / Double.parseDouble(total+"") )*100;
String percentValue = (percent+"").substring(0,(percent+"").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);
ResultVO success = new ResultVO(ResStatus.OK, "success", map);
return success;
}
15.3 前端实现
15.3.1 商品评论的分页
引用elementUI分页组件
<!-- 引入样式 -->
<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>
引用分页组件
<!--分页 -->
<el-pagination background layout="prev, pager, next"
:current-page="pageNum"
:page-size="limit"
:total="count"
@current-change="pager"> </el-pagination>
监听分页组件的
页码改变
事件(点击上一页、下一页、页码都会导致页码改变
)分页组件的事件函数默认传递当前页码参数
pager:function(currentPage){
this.pageNum = currentPage;
//请求下一页数据
var url3 = baseUrl+"product/detail-commonts/"+this.productId;
axios.get(url3,{
params:{
pageNum:this.pageNum,
limit:this.limit
}
}).then((res)=>{
//获取到评论分页数据
var pageHelper = res.data.data;
//当前页的评论列表
this.productCommonts = pageHelper.list;
//总页数
this.pageCount = pageHelper.pageCount;
//总记录数
this.count = pageHelper.count;
});
}
15.3.2 商品评价统计
十六、购物车—添加购物车(登陆状态)
16.1 流程分析
16.2 接口实现
16.2.1 修改购物车数据表结构
shopping_cart |
---|
- 数据表修改完成之后,对此表重新进行逆向工程
16.2.2 数据库实现
- 单表添加操作,可以直接使用tkMapper完成
16.2.3 业务层实现
ShoppingCartService
接口
```java public interface ShoppingCartService {public ResultVO addShoppingCart(ShoppingCart cart);
}
- 实现类
<a name="ee21ebc0"></a>
#### 16.3 前端实现
<a name="71e62ad4"></a>
###### 16.3.1 记录选择的套餐属性
- 在vue的data中定义 `chooseSkuProps`
| |
| --- |
| ![image.png](https://cdn.nlark.com/yuque/0/2022/png/23162011/1644074267818-2d37a2eb-1811-4126-b6f1-bebd3cd8a48b.png#clientId=uc179a9d3-9ffa-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=302&id=uf8086afd&margin=%5Bobject%20Object%5D&name=image.png&originHeight=439&originWidth=1052&originalType=binary&ratio=1&rotation=0&showTitle=false&size=30916&status=done&style=none&taskId=u90df4b62-4da1-4051-99d9-ed102f6a90c&title=&width=724.8923556306667) |
- 为sku的属性添加点击事件
| |
| --- |
| ![image.png](https://cdn.nlark.com/yuque/0/2022/png/23162011/1644074273142-8f389cee-1c0f-4f77-8164-d6bd5b72411c.png#clientId=uc179a9d3-9ffa-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=159&id=ufdf705d3&margin=%5Bobject%20Object%5D&name=image.png&originHeight=231&originWidth=1389&originalType=binary&ratio=1&rotation=0&showTitle=false&size=30613&status=done&style=none&taskId=u57bfe714-4fb4-4cfd-a910-afa126b9f57&title=&width=957.1059714553194) |
- 在methods中定义事件函数`changeProp`
| |
| --- |
| ![image.png](https://cdn.nlark.com/yuque/0/2022/png/23162011/1644074278915-fb9131e2-4945-4be3-b89c-47099994a417.png#clientId=uc179a9d3-9ffa-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=174&id=ucff7c661&margin=%5Bobject%20Object%5D&name=image.png&originHeight=253&originWidth=973&originalType=binary&ratio=1&rotation=0&showTitle=false&size=23503&status=done&style=none&taskId=u407db9d5-aa3f-413b-b13b-032d12f44e2&title=&width=670.4565228409112) |
- 添加套餐切换的监听事件:
| |
| --- |
| ![image.png](https://cdn.nlark.com/yuque/0/2022/png/23162011/1644074288537-2b5a4a5a-b140-48b2-9f56-4db65a81d091.png#clientId=uc179a9d3-9ffa-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=135&id=ub37070ba&margin=%5Bobject%20Object%5D&name=image.png&originHeight=196&originWidth=967&originalType=binary&ratio=1&rotation=0&showTitle=false&size=16313&status=done&style=none&taskId=ua9bca3ed-f724-4d28-ab1a-3eae41a3e75&title=&width=666.3221557935881) |
<a name="a3399e92"></a>
###### 16.3.2 套餐属性选中效果
- 在套餐属性标签上添加name属性
| |
| --- |
| ![image.png](https://cdn.nlark.com/yuque/0/2022/png/23162011/1644074293533-d19dccc4-6873-400f-81ef-66a75b348579.png#clientId=uc179a9d3-9ffa-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=162&id=ucefb3717&margin=%5Bobject%20Object%5D&name=image.png&originHeight=235&originWidth=1122&originalType=binary&ratio=1&rotation=0&showTitle=false&size=26824&status=done&style=none&taskId=u26d12a68-7096-4f10-95d4-50f8dff2090&title=&width=773.1266378494372) |
- 在属性的点击事件函数实现选中效果
| |
| --- |
| ![image.png](https://cdn.nlark.com/yuque/0/2022/png/23162011/1644074298444-986fc35b-48de-4613-b96e-1590f087aa71.png#clientId=uc179a9d3-9ffa-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=249&id=u750b5ff6&margin=%5Bobject%20Object%5D&name=image.png&originHeight=362&originWidth=1014&originalType=binary&ratio=1&rotation=0&showTitle=false&size=33823&status=done&style=none&taskId=ue0fc1b0f-f91a-4e11-bf87-7c883f2e053&title=&width=698.7080309976197) |
<a name="7cefa183"></a>
###### 16.3.3 修改商品数量
- 在vue的data中定义`num`存储商品数量(默认值为1)
- 为+,-添加点击事件监听
| |
| --- |
| ![image.png](https://cdn.nlark.com/yuque/0/2022/png/23162011/1644074304809-ef58162a-c632-4416-9539-90b572607f7e.png#clientId=uc179a9d3-9ffa-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=49&id=u730440b4&margin=%5Bobject%20Object%5D&name=image.png&originHeight=71&originWidth=1097&originalType=binary&ratio=1&rotation=0&showTitle=false&size=17228&status=done&style=none&taskId=u10669d09-e95f-45ac-a575-4f88e2a567c&title=&width=755.9001084855906) |
- 定义点击事件函数
| |
| --- |
| ![image.png](https://cdn.nlark.com/yuque/0/2022/png/23162011/1644074311745-f7a0b7a6-aa68-42d4-985f-60d210d83efa.png#clientId=uc179a9d3-9ffa-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=232&id=uf0d42dec&margin=%5Bobject%20Object%5D&name=image.png&originHeight=336&originWidth=1094&originalType=binary&ratio=1&rotation=0&showTitle=false&size=25484&status=done&style=none&taskId=u7d6fb199-9004-413b-b239-9fea181d12a&title=&width=753.832924961929) |
<a name="d8b587b8"></a>
###### 16.3.4 提交购物车
| |
| --- |
| ![image.png](https://cdn.nlark.com/yuque/0/2022/png/23162011/1644074318131-91d86c8f-b644-4a48-bb4d-d01ab96360c7.png#clientId=uc179a9d3-9ffa-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=472&id=u50156daa&margin=%5Bobject%20Object%5D&name=image.png&originHeight=685&originWidth=1108&originalType=binary&ratio=1&rotation=0&showTitle=false&size=56874&status=done&style=none&taskId=u3c45d2b0-6ee4-49c3-861a-f3174c783d4&title=&width=763.4797814056831) |
<a name="ca0da361"></a>
## 十七、购物车—添加购物车(未登录状态)
<a name="1f4aab5e"></a>
#### 17.1 流程分析
![image.png](https://cdn.nlark.com/yuque/0/2022/png/23162011/1644074325251-cc3fae69-1918-43db-8914-bf815d8c45bc.png#clientId=uc179a9d3-9ffa-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=453&id=ub0e2da32&margin=%5Bobject%20Object%5D&name=image.png&originHeight=658&originWidth=1141&originalType=binary&ratio=1&rotation=0&showTitle=false&size=114353&status=done&style=none&taskId=u1701b748-669e-483e-a9a7-8136a435a52&title=&width=786.2188001659607)
<a name="76ba79af"></a>
#### 17.2 功能实现
<a name="5419de60"></a>
###### 17.2.1 定义新的状态码
| ResStatus |
| --- |
| ![image.png](https://cdn.nlark.com/yuque/0/2022/png/23162011/1644074331004-d27e9f08-654d-49d7-bfa6-38bc4c5ff44d.png#clientId=uc179a9d3-9ffa-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=207&id=u72bf43b6&margin=%5Bobject%20Object%5D&name=image.png&originHeight=301&originWidth=1090&originalType=binary&ratio=1&rotation=0&showTitle=false&size=27586&status=done&style=none&taskId=udd0a4ccd-e716-46f5-b45f-4f908eccae4&title=&width=751.0766802637136) |
| 登录认证拦截器 |
| --- |
| ![image.png](https://cdn.nlark.com/yuque/0/2022/png/23162011/1644074335419-4c7755d8-e266-41cf-aa4e-22904a34b7c6.png#clientId=uc179a9d3-9ffa-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=578&id=u57974304&margin=%5Bobject%20Object%5D&name=image.png&originHeight=839&originWidth=1393&originalType=binary&ratio=1&rotation=0&showTitle=false&size=108785&status=done&style=none&taskId=u5c8452d9-0a1b-4227-91dc-538b0cf241d&title=&width=959.8622161535349) |
<a name="621d4683"></a>
###### 17.2.2 在详情页面判断如果用户未登录,则跳转到登录页面
| introduction.html |
| --- |
| ![image.png](https://cdn.nlark.com/yuque/0/2022/png/23162011/1644074347163-c6d92394-96d5-4e3d-abf1-87acd6afabfa.png#clientId=uc179a9d3-9ffa-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=495&id=uf94f2ff0&margin=%5Bobject%20Object%5D&name=image.png&originHeight=718&originWidth=1222&originalType=binary&ratio=1&rotation=0&showTitle=false&size=50735&status=done&style=none&taskId=u330a5331-d8e9-4dd0-8faa-bdc4fd04e26&title=&width=842.0327553048238) |
<a name="543b4273"></a>
###### 17.2.3 登录页面接收回跳信息
| login.html |
| --- |
| ![image.png](https://cdn.nlark.com/yuque/0/2022/png/23162011/1644074356835-94a0ac19-e030-4f65-8e72-02662bd7273e.png#clientId=uc179a9d3-9ffa-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=306&id=u5875e050&margin=%5Bobject%20Object%5D&name=image.png&originHeight=444&originWidth=1117&originalType=binary&ratio=1&rotation=0&showTitle=false&size=34110&status=done&style=none&taskId=u5e4a8e07-719c-4726-8d57-6279513f66e&title=&width=769.6813319766679) |
| ![image.png](https://cdn.nlark.com/yuque/0/2022/png/23162011/1644074362114-17f82b82-db56-43a1-bc96-9ce526c0e9c3.png#clientId=uc179a9d3-9ffa-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=409&id=ud9da686c&margin=%5Bobject%20Object%5D&name=image.png&originHeight=593&originWidth=1186&originalType=binary&ratio=1&rotation=0&showTitle=false&size=52420&status=done&style=none&taskId=u857a773b-828f-42ea-a65a-0cccfaffda9&title=&width=817.2265530208847) |
<a name="db15e15f"></a>
###### 17.2.4 回到详情页时接收参数
| introduction.html |
| --- |
| ![image.png](https://cdn.nlark.com/yuque/0/2022/png/23162011/1644074372294-a7075f13-59a6-45c3-b025-921f0b8e9fa6.png#clientId=uc179a9d3-9ffa-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=515&id=u04a0eed2&margin=%5Bobject%20Object%5D&name=image.png&originHeight=748&originWidth=1158&originalType=binary&ratio=1&rotation=0&showTitle=false&size=63963&status=done&style=none&taskId=u19363dc4-4a96-4840-8c3b-14cd7a7ab41&title=&width=797.9328401333764) |
<a name="16c1372c"></a>
###### 17.2.5 使用layui添加购物车成功/失败进行提示
- 引入layui layui.com
```html
<!-- 引入 layui.css -->
<link rel="stylesheet" href="//unpkg.com/layui@2.6.5/dist/css/layui.css">
<!-- 引入 layui.js -->
<script src="//unpkg.com/layui@2.6.5/dist/layui.js">
声明弹窗组件
| | | —- | | |当添加购物车成功或者失败的时候,进行提示:
| | | —- | | |
十八、购物车—购物车列表
18.1 流程分析
18.2 接口实现
18.2.1 数据库实现
SQL
-- 根据用户ID查询当前用户的购物车信息
select c.*, p.product_name,i.url
from shopping_cart c
INNER JOIN product p
INNER JOIN product_img i
ON c.product_id = p.product_id and i.item_id=p.product_id
where user_id=6 and i.is_main=1;
实体类
| | | —- | | |在Mapper接口定义查询方法
```java @Repository public interface ShoppingCartMapper extends GeneralDAO{ public List
selectShopcartByUserId(int userId);
}
- 映射配置
| |
| --- |
| ![image.png](https://cdn.nlark.com/yuque/0/2022/png/23162011/1644074404777-2ea8f476-d209-4dd5-b765-9a48aa022873.png#clientId=uc179a9d3-9ffa-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=576&id=ubd159c55&margin=%5Bobject%20Object%5D&name=image.png&originHeight=836&originWidth=1088&originalType=binary&ratio=1&rotation=0&showTitle=false&size=111843&status=done&style=none&taskId=ufacbe9ae-ae81-4c97-9c29-97b93f388e9&title=&width=749.6985579146058) |
<a name="a54874fd"></a>
###### 18.2.2 业务层实现
- Service接口
| |
| --- |
| ![image.png](https://cdn.nlark.com/yuque/0/2022/png/23162011/1644074412175-e02ed736-7a30-4209-842d-d2229fe99bc8.png#clientId=uc179a9d3-9ffa-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=140&id=u8e8e10cc&margin=%5Bobject%20Object%5D&name=image.png&originHeight=203&originWidth=970&originalType=binary&ratio=1&rotation=0&showTitle=false&size=13546&status=done&style=none&taskId=u7ab0e87a-08cd-41aa-905c-5209ee0575d&title=&width=668.3893393172497) |
- Service实现类
| |
| --- |
| ![image.png](https://cdn.nlark.com/yuque/0/2022/png/23162011/1644074417283-2d064015-9ce1-4673-8b7c-558baeed592f.png#clientId=uc179a9d3-9ffa-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=338&id=u1850f7dd&margin=%5Bobject%20Object%5D&name=image.png&originHeight=490&originWidth=1130&originalType=binary&ratio=1&rotation=0&showTitle=false&size=49184&status=done&style=none&taskId=u557dec28-1113-4d29-9003-85de7285268&title=&width=778.6391272458682) |
<a name="4fbc7df7"></a>
###### 18.2.3 控制层实现
<a name="9cc613d7"></a>
#### 18.3 前端实现
<a name="1aed0391"></a>
###### 18.3.1 显示购物车列表
<a name="28736881"></a>
###### 18.3.2 显示购物车中商品价格
<a name="f68ba5a1"></a>
## 十九、购物车-修改购物车数量
<a name="3d1f3f1f"></a>
#### 19.1 流程分析
<a name="1d49a9ae"></a>
#### 19.2 接口实现
- 在Mapper接口定义修改方法
```java
@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>
Service接口
```java public interface ShoppingCartService {public ResultVO addShoppingCart(ShoppingCart cart);
public ResultVO listShoppingCartsByUserId(int userId);
public ResultVO updateCartNum(int cartId,int cartNum);
}
- Service实现类
```java
@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);
}
}
}
- 控制层实现
@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.3 前端实现
为按钮添加点击事件
| | | —- | | |定义changeNum事件函数
| | | —- | | |
二十、购物车—结算、提交订单
在购物车列表中选择对应的的商品之后,点击提交生成订单的过程
20.1 流程图
20.2 接口实现
20.2.1 收货地址列表接口
此操作的数据库实现可以通过tkmapper通用方法完成
service接口
UserAddrService
```java public interface UserAddrService {public ResultVO listAddrsByUid(int userId);
}
- Service实现类 `UserAddrServiceImpl`
```java
@Service
public class UserAddrServiceImpl implements UserAddrService {
@Autowired
private UserAddrMapper userAddrMapper;
@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);
List<UserAddr> userAddrs = userAddrMapper.selectByExample(example);
ResultVO resultVO = new ResultVO(ResStatus.OK, "success", userAddrs);
return resultVO;
}
}
控制器实现
```java @RestController @CrossOrigin @Api(value = “提供收货地址相关接口”,tags = “收货地址管理”) @RequestMapping(“/useraddr”) public class UserAddrController {@Autowired private UserAddrService userAddrService;
@GetMapping(“/list”) @ApiImplicitParam(dataType = “int”,name = “userId”, value = “用户ID”,required = true) public ResultVO listAddr(Integer userId, @RequestHeader(“token”) String token){
ResultVO resultVO = userAddrService.listAddrsByUid(userId);
return resultVO;
}
}
<a name="1ad87bd5"></a>
###### 20.2.2 购物车记录列表接口
> 根据一个ID的集合,查询购物车记录,实现方式有两种:
>
> - 动态sql
> - tkMapper条件查询<br />criteria.andIn("cartId",ids);
>
```xml
<select id="searchShoppingCartById" resultMap="shopCartMap">
select * from shopping_cart where cart_id in
<foreach collection="list" item="cid" separator="," open="(" close=")">
#{cid}
</foreach>
</select>
Mapper接口定义查询方法
| | | —- | | |映射配置(动态sql foreach)
| | | —- | | |Service接口
| | | —- | | |Service实现类
| | | —- | | |控制器实现
@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;
}
20.2.3 保存订单
20.3 前端实现
20.3.1 选择购物车记录价格联动
列表前的复选框标签
| | | —- | | |渲染商品数量以及总价格
| | | —- | | |在vue示例的data中声明opts和totalPrice,并且监听opts选项的改变—选项一旦改变就计算总价格
| | | —- | | |
20.3.2 点击“结算”跳转到订单添加页面
在购物车列表页面,选择购物车记录,点击“结算之后”将选择的购物车记录ID传递到order-add.html
shopcart.html
| | | —- | | |order-add.html
| | | —- | | |
20.3.3 显示收货地址及订单商品
20.3.4 订单确认页面选择地址
二十一、订单提交及支付
21.1 流程分析
21.2 订单添加接口实现
21.2.1 数据库操作
- 根据收货地址ID,获取收货地址信息(tkMapper)
根据购物车ID,查询购物车详情(需要关联查询商品名称、sku名称、
库存
、商品图片、商品价格)
| 改造:ShoppingCartMapper
中的selectShopcartByCids
| | —- | | | | | | |保存订单(tkMapper)
- 修改库存(tkMapper)
- 保存商品快照(tkMapper)