一、《锋迷商城》项目介绍

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 单体项目

项目的页面和代码都在同一个项目,项目开发完成之后直接部署在一台服务器

image.png

单体项目遇到的问题:用户对页面静态资源以及对Java代码的访问压力都会落在Tomcat服务器上。

1.3.2 技术清单
  • 项目架构:前后端分离
  • 前端技术:vue、axios、妹子UI、layui、bootstrap
  • 后端技术:SpringBoot+MyBatis、RESTful、swagger
  • 服务器搭建:Linux、Nginx

二、项目架构的演进

2.1 单体架构

  • 前后端都部署在同一台服务器上(前后端代码都在同一个应用中)
  • 缺点:对静态资源的访问压力也会落在Tomcat上

2.2 前后端分离

image.png

  • 前后端分离:前端和后端分离开发和部署(前后端部署在不同的服务器)
  • 优点:将对静态资源的访问和对接口的访问进行分离,Tomcat服务器只负责数据服务的访问

2.3 集群与负载均衡

image.png

  • 优点:提供并发能力、可用性

2.4 分布式

image.png

  • 基于redis实现 分布式锁
  • 分布式数据库mycat
  • redis集群
  • 数据库中间件
  • 消息中间件

2.5 微服务架构

  • 微服务架构:将原来在一个应用中开发的多个模块进行拆分,单独开发和部署
  • 保证可用性、性能

三、《锋迷商城》项目搭建

基于Maven的聚合工程完成项目搭建,前端采用vue+axios,后端使用SpringBoot整合SSM

3.1 技术储备

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

3.2 创建Maven聚合工程

image.png

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

    1. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    2. 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

  1. <a name="def700f5"></a>
  2. ###### 3.2.2 创建common工程
  3. - 选择fmmall,右键---New---Module (Maven工程)
  4. - 修改common的pom.xml,设置packing=jar
  5. ```xml
  6. <?xml version="1.0" encoding="UTF-8"?>
  7. <project xmlns="http://maven.apache.org/POM/4.0.0"
  8. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  9. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  10. <parent>
  11. <artifactId>fmmall</artifactId>
  12. <groupId>com.qfedu</groupId>
  13. <version>2.0.1</version>
  14. </parent>
  15. <modelVersion>4.0.0</modelVersion>
  16. <artifactId>common</artifactId>
  17. <packaging>jar</packaging>
  18. </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
    1. <dependency>
    2. <groupId>com.qfedu</groupId>
    3. <artifactId>beans</artifactId>
    4. <version>2.0.1</version>
    5. </dependency>

3.2.5 创建service工程
  • 选择fmmall,右键—-New—-Module (Maven工程)
  • 修改service的pom.xml,设置packing ——- jar
  • 在service的pom.xml,依赖mapper、commom
    1. <dependency>
    2. <groupId>com.qfedu</groupId>
    3. <artifactId>mapper</artifactId>
    4. <version>2.0.1</version>
    5. </dependency>
    6. <dependency>
    7. <groupId>com.qfedu</groupId>
    8. <artifactId>common</artifactId>
    9. <version>2.0.1</version>
    10. </dependency>

3.2.6 创建api工程
  • 选择fmmall,右键—-New—-Module (SpringBoot工程)
  • 修改api的pom.xml,继承fmmall,删除自己的groupId 和 version

    1. <parent>
    2. <groupId>com.qfedu</groupId>
    3. <artifactId>fmmall</artifactId>
    4. <version>2.0.1</version>
    5. </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

    1. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    2. 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
  1. <groupId>com.qfedu</groupId>
  2. <artifactId>fmmall</artifactId>
  3. <version>2.0.1</version>
  4. <modules>
  5. <module>common</module>
  6. <module>beans</module>
  7. <module>mapper</module>
  8. <module>service</module>
  9. <module>api</module>
  10. </modules>
  11. <packaging>pom</packaging>
  12. <properties>
  13. <java.version>1.8</java.version>
  14. </properties>
  15. <dependencies>
  16. <dependency>
  17. <groupId>org.springframework.boot</groupId>
  18. <artifactId>spring-boot-starter-web</artifactId>
  19. </dependency>
  20. <dependency>
  21. <groupId>org.projectlombok</groupId>
  22. <artifactId>lombok</artifactId>
  23. <optional>true</optional>
  24. </dependency>
  25. <dependency>
  26. <groupId>org.springframework.boot</groupId>
  27. <artifactId>spring-boot-starter-test</artifactId>
  28. <scope>test</scope>
  29. </dependency>
  30. </dependencies>
  31. <build>
  32. <plugins>
  33. <plugin>
  34. <groupId>org.springframework.boot</groupId>
  35. <artifactId>spring-boot-maven-plugin</artifactId>
  36. <configuration>
  37. <excludes>
  38. <exclude>
  39. <groupId>org.projectlombok</groupId>
  40. <artifactId>lombok</artifactId>
  41. </exclude>
  42. </excludes>
  43. </configuration>
  44. </plugin>
  45. </plugins>
  46. </build>

  1. - api中,依赖service
  2. ```xml
  3. <dependency>
  4. <groupId>com.qfedu</groupId>
  5. <artifactId>service</artifactId>
  6. <version>2.0.1</version>
  7. </dependency>
  • api的pom.xml继承fmmall

3.3 Maven聚合工程依赖分析

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

  • 好处:当有多个子工程都需要相同的依赖时,无需在子工程中重复添加依赖
  • 缺点:如果某些子工程不需要这个依赖,还是会被强行继承

如果在父工程中没有添加统一依赖,则每个子工程所需的依赖需要在子工程的pom中自行添加

如果存在多个子工程需要添加相同的依赖,则需在父工程pom进行依赖版本的管理

image.png

**依赖配置说明**

  1. 在父工程的pom文件中一次性添加各个子工程所需的所有依赖
  2. 在各个子工程中单独添加当前子工程的依赖

3.4 整合MyBatis

3.4.1 common子工程
  • lombok

3.4.2 beans子工程
  • lombok

3.4.3 MyBatis整合
  • mapper子工程的pom文件,新增mybatis所需的依赖
    ```xml mysql mysql-connector-java 5.1.47
org.springframework.boot spring-boot-starter 2.4.4 org.mybatis.spring.boot mybatis-spring-boot-starter 2.1.4 - 在**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 org.springframework.boot spring-boot-starter-test junit junit test <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-starterxml com.alibaba druid-spring-boot-starter 1.1.22 <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 io.springfox springfox-swagger2 2.9.2 io.swagger swagger-models 1.5.21 io.springfox springfox-swagger-ui 2.9.2 - 在**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 com.github.xiaoymin swagger-bootstrap-ui 1.9.6 - 文档访问<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 实现流程 image.png #### 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 跨域访问概念 - 什么时跨域访问?
image.png > 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实现单体项目用户认证 > 在单体项目中如何保证受限资源在用户未登录的情况下不允许访问? image.png > 在单体项目中,视图资源(页面)和接口(控制器)都在同一台服务器,用户的多次请求都是基于同一个会话(session),因此可以借助session来进行用户认证判断: >
> 1.当用户登录成功之后,将用户信息存放到session >
> 2.当用户再次访问受限资源时,验证session中是否存在用户信息,可以根据session有无用户信息来判断用户是否登录 #### 7.2 基于token实现前后端分离用户认证 > 由于在前后端分离项目开发中,前后端之间是通过异步交互完成数据访问的,请求是无状态的,因此不能基于session实现用户的认证。 image.png #### 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的结构
image.png ###### 7.4.2 生成JWT - 添加依赖放到service层
```xml com.auth0 java-jwt 3.10.3

io.jsonwebtoken jjwt 0.9.1

  1. - 生成token
  2. ```java
  3. zhoJwtBuilder builder= Jwts.builder();
  4. HashMap<String,Object> map=new HashMap<>();
  5. String token = builder.setSubject(name) //主题,就是token中携带的数据
  6. .setIssuedAt(new Date()) //设置token的生成时间
  7. .setId(users.get(0).getUserId() + "") //设置用户id为token id
  8. .setClaims(map) //map中可以存放用户的角色权限信息
  9. .setExpiration(new Date(System.currentTimeMillis() + 24 * 60 * 60 * 1000)) //设置过期时间
  10. .signWith(SignatureAlgorithm.HS256, "QIANfeng6666") //设置加密方式和加密密码
  11. .compact();

7.4.3 JWT校验
  • 如果token正确则正常解析,如果token不正确或者过期,则通过抛出的异常进行识别

    1. try {
    2. //验证token
    3. JwtParser parser = Jwts.parser();
    4. parser.setSigningKey("QIANfeng6666"); //解析token的SigningKey必须和生成token时设置密码一致
    5. //如果token正确(密码正确,有效期内)则正常执行,否则抛出异常
    6. Jws<Claims> claimsJws = parser.parseClaimsJws(token);
    7. Claims body = claimsJws.getBody(); //获取token中用户数据
    8. String subject = body.getSubject(); //获取生成token设置的subject
    9. String v1 = body.get("key1", String.class); //获取生成token时存储的Claims的map中的值
    10. return new ResultVO(ResStatus.OK,"success",null);
    11. }catch (ExpiredJwtException e){
    12. return new ResultVO(ResStatus.NO,"登录过期,请重新登录!",null);
    13. }catch (UnsupportedJwtException e){
    14. return new ResultVO(ResStatus.NO,"Tonken不合法,请自重!",null);
    15. }catch (Exception e){
    16. return new ResultVO(ResStatus.NO,"请重新登录!",null);
    17. }

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 {

    1. String token = request.getParameter("token");
    2. if(token == null){
    3. ResultVO resultVO = new ResultVO(ResStatus.NO, "请先登录!", null);
    4. //提示请先登录
    5. doResponse(response,resultVO);
    6. }else{
    7. try {
    8. //验证token
    9. JwtParser parser = Jwts.parser();
    10. //解析token的SigningKey必须和生成token时设置密码一致
    11. parser.setSigningKey("QIANfeng6666");
    12. //如果token正确(密码正确,有效期内)则正常执行,否则抛出异常
    13. Jws<Claims> claimsJws = parser.parseClaimsJws(token);
    14. return true;
    15. }catch (ExpiredJwtException e){
    16. ResultVO resultVO = new ResultVO(ResStatus.NO, "登录过期,请重新登录!", null);
    17. doResponse(response,resultVO);
    18. }catch (UnsupportedJwtException e){
    19. ResultVO resultVO = new ResultVO(ResStatus.NO, "Token不合法,请自重!", null);
    20. doResponse(response,resultVO);
    21. }catch (Exception e){
    22. ResultVO resultVO = new ResultVO(ResStatus.NO, "请先登录!", null);
    23. doResponse(response,resultVO);
    24. }
    25. }
    26. return false;

    }

    private void doResponse(HttpServletResponse response,ResultVO resultVO) throws IOException {

    1. response.setContentType("application/json");
    2. response.setCharacterEncoding("utf-8");
    3. PrintWriter out = response.getWriter();
    4. String s = new ObjectMapper().writeValueAsString(resultVO);
    5. out.print(s);
    6. out.flush();
    7. out.close();

    }

}

  1. - 配置拦截器
  2. ```java
  3. @Configuration
  4. public class InterceptorConfig implements WebMvcConfigurer {
  5. @Autowired
  6. private CheckTokenInterceptor checkTokenInterceptor;
  7. @Override
  8. public void addInterceptors(InterceptorRegistry registry) {
  9. registry.addInterceptor(checkTokenInterceptor)
  10. .addPathPatterns("/**") //拦截所有
  11. .excludePathPatterns("/user/**"); //除了user开头
  12. }
  13. }

7.5 请求头传递token

前端但凡访问受限资源,都必须携带token发送请求;token可以通过请求行(params)、请求头(header)以及请求体(data)传递,但是习惯性使用header传递

7.5.1 axios通过请求头传值
  1. axios({
  2. method:"get",
  3. url:baseUrl+"shopcart/list",
  4. headers:{
  5. token:this.token
  6. }
  7. }).then(function(res){
  8. console.log(res);
  9. });

7.5.2 在拦截器中放行options请求
  1. @Component
  2. public class CheckTokenInterceptor implements HandlerInterceptor {
  3. @Override
  4. public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
  5. //放行options请求
  6. String method = request.getMethod();
  7. if("OPTIONS".equalsIgnoreCase(method)){
  8. return true;
  9. }
  10. String token = request.getHeader("token");
  11. System.out.println("-------------"+token);
  12. if(token == null){
  13. ResultVO resultVO = new ResultVO(ResStatus.NO, "请先登录!", null);
  14. //提示请先登录
  15. doResponse(response,resultVO);
  16. }else{
  17. try {
  18. //验证token
  19. JwtParser parser = Jwts.parser();
  20. //解析token的SigningKey必须和生成token时设置密码一致
  21. parser.setSigningKey("QIANfeng6666");
  22. //如果token正确(密码正确,有效期内)则正常执行,否则抛出异常
  23. Jws<Claims> claimsJws = parser.parseClaimsJws(token);
  24. return true;
  25. }catch (ExpiredJwtException e){
  26. ResultVO resultVO = new ResultVO(ResStatus.NO, "登录过期,请重新登录!", null);
  27. doResponse(response,resultVO);
  28. }catch (UnsupportedJwtException e){
  29. ResultVO resultVO = new ResultVO(ResStatus.NO, "Token不合法,请自重!", null);
  30. doResponse(response,resultVO);
  31. }catch (Exception e){
  32. ResultVO resultVO = new ResultVO(ResStatus.NO, "请先登录!", null);
  33. doResponse(response,resultVO);
  34. }
  35. }
  36. return false;
  37. }
  38. private void doResponse(HttpServletResponse response,ResultVO resultVO) throws IOException {
  39. response.setContentType("application/json");
  40. response.setCharacterEncoding("utf-8");
  41. PrintWriter out = response.getWriter();
  42. String s = new ObjectMapper().writeValueAsString(resultVO);
  43. out.print(s);
  44. out.flush();
  45. out.close();
  46. }
  47. }

八、首页—轮播图

8.1 实现流程分析

  • 流程图
    image.png
  • 接口
    • 查询轮播图信息返回

8.2 完成后台接口开发

8.2.1 数据库操作实现
  • 分析数据表结构

image.png

  • 添加测试数据

image.png

  • 编写sql语句
  1. select img_id,
  2. img_url,
  3. img_bg_color,
  4. prod_id,
  5. category_id,
  6. index_type,
  7. seq,
  8. status,
  9. create_time,
  10. update_time
  11. from index_img
  12. where status=1
  13. order by seq
  • 在Mapper接口(DAO)中定义操作方法
  1. @Repository
  2. public interface IndexImgMapper extends GeneralDAO<IndexImg> {
  3. //1.查询轮播图信息: 查询status=1 且 按照seq进行排序
  4. public List<IndexImg> listIndexImgs();
  5. }
  • 配置映射文件
  1. <!--BaseResultMap是由逆向工程生成的-->
  2. <select id="listIndexImgs" resultMap="BaseResultMap">
  3. select img_id,
  4. img_url,
  5. img_bg_color,
  6. prod_id,
  7. category_id,
  8. index_type,
  9. seq,
  10. status,
  11. create_time,
  12. update_time
  13. from index_img
  14. where status=1
  15. order by seq
  16. </select>

8.2.2 业务层实现
  • IndexImgService接口
  1. public interface IndexImgService {
  2. public ResultVO listIndexImgs();
  3. }
  • IndexImgServiceImpl实现类
  1. @Service
  2. public class IndexImgServiceImpl implements IndexImgService {
  3. @Autowired
  4. private IndexImgMapper indexImgMapper;
  5. public ResultVO listIndexImgs() {
  6. List<IndexImg> indexImgs = indexImgMapper.listIndexImgs();
  7. if(indexImgs.size()==0){
  8. return new ResultVO(ResStatus.NO,"fail",null);
  9. }else{
  10. return new ResultVO(ResStatus.OK,"success",indexImgs);
  11. }
  12. }
  13. }

8.2.3 控制层实现
  • IndexController类
  1. @RestController
  2. @CrossOrigin
  3. @RequestMapping("/index")
  4. @Api(value = "提供首页数据显示所需的接口",tags = "首页管理")
  5. public class IndexController {
  6. @Autowired
  7. private IndexImgService indexImgService;
  8. @GetMapping("/indeximg")
  9. @ApiOperation("首页轮播图接口")
  10. public ResultVO listIndexImgs(){
  11. return indexImgService.listIndexImgs();
  12. }
  13. }

8.3 完成前端功能

当进入到index.html,在进行页面初始化之后,就需要请求轮播图数据进行轮播图的显示

index.html
image.png
image.png

九、首页-分类列表

9.1 实现流程分析

image.png

  • 方案一:一次性查询三级分类
    • 优点:只需要一次查询,根据一级分类显示二级分类时响应速度较快
    • 缺点:数据库查询效率较低,页面首次加载的速度也相对较慢
  • 方案二:先只查询一级分类,用户点击/鼠标移动到一级分类,动态加载二级分类
    • 优点:数据库查询效率提高,页面首次加载速度提高
    • 缺点:需要多次连接数据库

9.2 接口开发

9.2.1 数据库操作实现
  • 数据表结构

image.png

  • 添加测试数据
  • 编写接口实现所需的SQL

    • 连接查询

      1. select
      2. c1.category_id 'category_id1',
      3. c1.category_name 'category_name1',
      4. c1.category_level 'category_level1',
      5. c1.parent_id 'parent_id1',
      6. c1.category_icon 'category_icon1',
      7. c1.category_slogan 'category_slogan1',
      8. c1.category_pic 'category_pic1',
      9. c1.category_bg_color 'category_bg_color1',
      10. c2.category_id 'category_id2',
      11. c2.category_name 'category_name2',
      12. c2.category_level 'category_level2',
      13. c2.parent_id 'parent_id2',
      14. c3.category_id 'category_id3',
      15. c3.category_name 'category_name3',
      16. c3.category_level 'category_level3',
      17. c3.parent_id 'parent_id3'
      18. from category c1
      19. inner join category c2
      20. on c2.parent_id=c1.category_id
      21. left join category c3
      22. on c3.parent_id=c2.category_id
      23. where c1.category_level=1
    • 子查询

      1. -- 根据父级分类的id查询类别信息
      2. select * from category where parent_id=3;
  • 创建用于封装查询的类别信息的CategoryVO
    在beans子工程的entity包新建一个CategoryVO用于封装查询到类别信息,相对于Category来说,新增了如下属性:

    1. public class CategoryVO {
    2. //用于存放当前分类的子分类
    3. private List<CategoryVO> categories;
    4. public List<CategoryVO> getCategories() {
    5. return categories;
    6. }
    7. }
  • 在CategoryMapper定义操作方法
    ```java @Repository public interface CategoryMapper extends GeneralDAO {

    //1.连接查询 public List selectAllCategories();

    //2.子查询:根据parentId查询子分类 public List selectAllCategories2(int parentId);

}

  1. - 映射配置
  2. ```xml
  3. <?xml version="1.0" encoding="UTF-8"?>
  4. <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
  5. <mapper namespace="com.qfedu.fmmall.dao.CategoryMapper">
  6. <resultMap id="BaseResultMap" type="com.qfedu.fmmall.entity.Category">
  7. <id column="category_id" jdbcType="INTEGER" property="categoryId" />
  8. <result column="category_name" jdbcType="VARCHAR" property="categoryName" />
  9. <result column="category_level" jdbcType="INTEGER" property="categoryLevel" />
  10. <result column="parent_id" jdbcType="INTEGER" property="parentId" />
  11. <result column="category_icon" jdbcType="VARCHAR" property="categoryIcon" />
  12. <result column="category_slogan" jdbcType="VARCHAR" property="categorySlogan" />
  13. <result column="category_pic" jdbcType="VARCHAR" property="categoryPic" />
  14. <result column="category_bg_color" jdbcType="VARCHAR" property="categoryBgColor" />
  15. </resultMap>
  16. <resultMap id="categoryVOMap" type="com.qfedu.fmmall.entity.CategoryVO">
  17. <id column="category_id1" jdbcType="INTEGER" property="categoryId" />
  18. <result column="category_name1" jdbcType="VARCHAR" property="categoryName" />
  19. <result column="category_level1" jdbcType="INTEGER" property="categoryLevel" />
  20. <result column="parent_id1" jdbcType="INTEGER" property="parentId" />
  21. <result column="category_icon1" jdbcType="VARCHAR" property="categoryIcon" />
  22. <result column="category_slogan1" jdbcType="VARCHAR" property="categorySlogan" />
  23. <result column="category_pic1" jdbcType="VARCHAR" property="categoryPic" />
  24. <result column="category_bg_color1" jdbcType="VARCHAR" property="categoryBgColor" />
  25. <collection property="categories" ofType="com.qfedu.fmmall.entity.CategoryVO">
  26. <id column="category_id2" jdbcType="INTEGER" property="categoryId" />
  27. <result column="category_name2" jdbcType="VARCHAR" property="categoryName" />
  28. <result column="category_level2" jdbcType="INTEGER" property="categoryLevel" />
  29. <result column="parent_id2" jdbcType="INTEGER" property="parentId" />
  30. <collection property="categories" ofType="com.qfedu.fmmall.entity.CategoryVO">
  31. <id column="category_id3" jdbcType="INTEGER" property="categoryId" />
  32. <result column="category_name3" jdbcType="VARCHAR" property="categoryName" />
  33. <result column="category_level3" jdbcType="INTEGER" property="categoryLevel" />
  34. <result column="parent_id3" jdbcType="INTEGER" property="parentId" />
  35. </collection>
  36. </collection>
  37. </resultMap>
  38. <select id="selectAllCategories" resultMap="categoryVOMap">
  39. select
  40. c1.category_id 'category_id1',
  41. c1.category_name 'category_name1',
  42. c1.category_level 'category_level1',
  43. c1.parent_id 'parent_id1',
  44. c1.category_icon 'category_icon1',
  45. c1.category_slogan 'category_slogan1',
  46. c1.category_pic 'category_pic1',
  47. c1.category_bg_color 'category_bg_color1',
  48. c2.category_id 'category_id2',
  49. c2.category_name 'category_name2',
  50. c2.category_level 'category_level2',
  51. c2.parent_id 'parent_id2',
  52. c3.category_id 'category_id3',
  53. c3.category_name 'category_name3',
  54. c3.category_level 'category_level3',
  55. c3.parent_id 'parent_id3'
  56. from category c1
  57. inner join category c2
  58. on c2.parent_id=c1.category_id
  59. left join category c3
  60. on c3.parent_id=c2.category_id
  61. where c1.category_level=1
  62. </select>
  63. <!---------------------------------------------------------------------------->
  64. <resultMap id="categoryVOMap2" type="com.qfedu.fmmall.entity.CategoryVO">
  65. <id column="category_id" jdbcType="INTEGER" property="categoryId" />
  66. <result column="category_name" jdbcType="VARCHAR" property="categoryName" />
  67. <result column="category_level" jdbcType="INTEGER" property="categoryLevel" />
  68. <result column="parent_id" jdbcType="INTEGER" property="parentId" />
  69. <result column="category_icon" jdbcType="VARCHAR" property="categoryIcon" />
  70. <result column="category_slogan" jdbcType="VARCHAR" property="categorySlogan" />
  71. <result column="category_pic" jdbcType="VARCHAR" property="categoryPic" />
  72. <result column="category_bg_color" jdbcType="VARCHAR" property="categoryBgColor" />
  73. <collection property="categories" column="category_id" select="com.qfedu.fmmall.dao.CategoryMapper.selectAllCategories2"/>
  74. </resultMap>
  75. <!-- 根据父级分类的id查询子级分类 -->
  76. <select id="selectAllCategories2" resultMap="categoryVOMap2">
  77. select
  78. category_id,
  79. category_name,
  80. category_level,
  81. parent_id,
  82. category_icon,
  83. category_slogan,
  84. category_pic,
  85. category_bg_color
  86. from category
  87. where parent_id=#{parentId}
  88. </select>
  89. </mapper>

9.2.2 业务层实现
  • CategoryService接口
    ```java public interface CategoryService {

    public ResultVO listCategories();

}

  1. - CategoryServiceImpl
  2. ```java
  3. @Service
  4. public class CategoryServiceImpl implements CategoryService {
  5. @Autowired
  6. private CategoryMapper categoryMapper;
  7. public ResultVO listCategories() {
  8. List<CategoryVO> categoryVOS = categoryMapper.selectAllCategories();
  9. ResultVO resultVO = new ResultVO(ResStatus.OK, "success", categoryVOS);
  10. return resultVO;
  11. }
  12. }

9.2.3 控制层实现
  • IndexController
    ```java @Autowired private CategoryService categoryService;

@GetMapping(“/category-list”) @ApiOperation(“商品分类查询接口”) public ResultVO listCatetory(){ return categoryService.listCategories(); }

  1. <a name="619b66e2"></a>
  2. #### 9.3 前端功能实现
  3. <a name="6f39a371"></a>
  4. ## 十、首页-商品推荐
  5. <a name="9b003689"></a>
  6. #### 10.1 流程分析
  7. ![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)
  8. <a name="265accfc"></a>
  9. #### 10.2 接口开发
  10. <a name="8fb8738a"></a>
  11. ###### 10.2.1 数据库实现
  12. > 商品推荐算法:推荐最新上架的商品
  13. >
  14. > 说明:商品推荐算法是根据多个维度进行权重计算,计算出一个匹配值
  15. - 数据表分析及数据准备
  16. - sql
  17. ```sql
  18. -- 商品推荐:查询最新上架的商品
  19. select * from product order by create_time desc limit 0,3;
  20. -- 子查询:根据商品id查询商品图片
  21. select * from product_img where item_id=2;
  • 在beans子工程entity包创建ProductVO,相比较Product新增了List imgs用于存储商品的图片

    1. public class ProductVO{
    2. private List<ProductImg> imgs;
    3. public List<ProductImg> getImgs() {
    4. return imgs;
    5. }
    6. public void setImgs(List<ProductImg> imgs) {
    7. this.imgs = imgs;
    8. }
    9. }
  • Mapper接口定义操作方法:

    • ProductMapper ```java public interface ProductMapper extends GeneralDAO {

      public List selectRecommendProducts();

}

  1. - ProductImgMapper
  2. ```java
  3. public interface ProductImgMapper extends GeneralDAO<ProductImg> {
  4. //根据商品id查询当前商品的图片信息
  5. public List<ProductImg> selectProductImgByProductId(int productId);
  6. }
  • 配置映射文件
    • ProductMapper.xml ```xml

  1. - ProductImgMapper.xml
  2. ```xml
  3. <?xml version="1.0" encoding="UTF-8"?>
  4. <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
  5. <mapper namespace="com.qfedu.fmmall.dao.ProductImgMapper">
  6. <resultMap id="BaseResultMap" type="com.qfedu.fmmall.entity.ProductImg">
  7. <id column="id" jdbcType="VARCHAR" property="id" />
  8. <result column="item_id" jdbcType="VARCHAR" property="itemId" />
  9. <result column="url" jdbcType="VARCHAR" property="url" />
  10. <result column="sort" jdbcType="INTEGER" property="sort" />
  11. <result column="is_main" jdbcType="INTEGER" property="isMain" />
  12. <result column="created_time" jdbcType="TIMESTAMP" property="createdTime" />
  13. <result column="updated_time" jdbcType="TIMESTAMP" property="updatedTime" />
  14. </resultMap>
  15. <select id="selectProductImgByProductId" resultMap="BaseResultMap">
  16. select
  17. id,
  18. item_id,
  19. url,
  20. sort,
  21. is_main,
  22. created_time,
  23. updated_time
  24. from product_img
  25. where item_id=#{productId}
  26. </select>
  27. </mapper>

10.2.2 业务层实现
  • ProductService接口
    ```java public interface ProductService {

    public ResultVO listRecommendProducts();

}

  1. - ProductServiceImpl实现类
  2. ```java
  3. @Service
  4. public class ProductServiceImpl implements ProductService {
  5. @Autowired
  6. private ProductMapper productMapper;
  7. public ResultVO listRecommendProducts() {
  8. List<ProductVO> productVOS = productMapper.selectRecommendProducts();
  9. ResultVO resultVO = new ResultVO(ResStatus.OK, "success", productVOS);
  10. return resultVO;
  11. }
  12. }

10.2.3 控制层实现
  • IndexController
    ```java @Autowired private ProductService productService;

@GetMapping(“/list-recommends”) @ApiOperation(“查询推荐商品接口”) public ResultVO listRecommendProducts() { return productService.listRecommendProducts(); }

  1. <a name="2ab1a403"></a>
  2. #### 10.3 前端实现
  3. <a name="6b1b6a68"></a>
  4. ## 十一、首页-分类商品推荐
  5. > 按照商品的分类(一级分类)推荐销量最高的6个商品
  6. <a name="55a50547"></a>
  7. #### 11.1 流程分析
  8. > 加载分类商品推荐有两种实现方案:
  9. >
  10. > 方案一:当加载首页面时不加载分类的推荐商品,监听进度条滚动事件,当进度条触底(滚动指定的距离)就触发分类推荐商品的加载,每次只加载一个分类的商品。
  11. ![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)
  12. > 方案二:一次性加载所有分类的推荐商品,整体进行初始化。
  13. ![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)
  14. <a name="a22c3d19"></a>
  15. #### 11.2 接口实现
  16. <a name="06fa60ba"></a>
  17. ###### 11.2.1 数据库实现
  18. - 数据准备
  19. ```sql
  20. -- 添加商品
  21. -- 添加十个分类下的商品:
  22. insert into product(product_id,product_name,category_id,root_category_id,sold_num,product_status,content,create_time,updated_time)
  23. values('5','商品5',10,1,122,1,'商品说明','2021-04-26 11:11:11','2021-04-26 11:11:11');
  24. insert into product(product_id,product_name,category_id,root_category_id,sold_num,product_status,content,create_time,updated_time)
  25. values('6','商品6',10,1,123,1,'商品说明','2021-04-26 11:11:11','2021-04-26 11:11:11');
  26. insert into product(product_id,product_name,category_id,root_category_id,sold_num,product_status,content,create_time,updated_time)
  27. values('7','商品7',10,1,124,1,'商品说明','2021-04-26 11:11:11','2021-04-26 11:11:11');
  28. insert into product(product_id,product_name,category_id,root_category_id,sold_num,product_status,content,create_time,updated_time)
  29. values('8','商品8',10,1,125,1,'商品说明','2021-04-26 11:11:11','2021-04-26 11:11:11');
  30. insert into product(product_id,product_name,category_id,root_category_id,sold_num,product_status,content,create_time,updated_time)
  31. values('9','商品9',10,1,126,1,'商品说明','2021-04-26 11:11:11','2021-04-26 11:11:11');
  32. insert into product(product_id,product_name,category_id,root_category_id,sold_num,product_status,content,create_time,updated_time)
  33. values('10','商品10',10,1,127,1,'商品说明','2021-04-26 11:11:11','2021-04-26 11:11:11');
  34. insert into product(product_id,product_name,category_id,root_category_id,sold_num,product_status,content,create_time,updated_time)
  35. values('11','商品11',10,1,128,1,'商品说明','2021-04-26 11:11:11','2021-04-26 11:11:11');
  36. insert into product(product_id,product_name,category_id,root_category_id,sold_num,product_status,content,create_time,updated_time)
  37. values('12','商品12',46,2,122,1,'商品说明','2021-04-26 11:11:11','2021-04-26 11:11:11');
  38. insert into product(product_id,product_name,category_id,root_category_id,sold_num,product_status,content,create_time,updated_time)
  39. values('13','商品13',46,2,123,1,'商品说明','2021-04-26 11:11:11','2021-04-26 11:11:11');
  40. insert into product(product_id,product_name,category_id,root_category_id,sold_num,product_status,content,create_time,updated_time)
  41. values('14','商品14',46,2,124,1,'商品说明','2021-04-26 11:11:11','2021-04-26 11:11:11');
  42. insert into product(product_id,product_name,category_id,root_category_id,sold_num,product_status,content,create_time,updated_time)
  43. values('15','商品15',46,2,125,1,'商品说明','2021-04-26 11:11:11','2021-04-26 11:11:11');
  44. insert into product(product_id,product_name,category_id,root_category_id,sold_num,product_status,content,create_time,updated_time)
  45. values('16','商品16',46,2,126,1,'商品说明','2021-04-26 11:11:11','2021-04-26 11:11:11');
  46. insert into product(product_id,product_name,category_id,root_category_id,sold_num,product_status,content,create_time,updated_time)
  47. values('17','商品17',46,2,127,1,'商品说明','2021-04-26 11:11:11','2021-04-26 11:11:11');
  48. insert into product(product_id,product_name,category_id,root_category_id,sold_num,product_status,content,create_time,updated_time)
  49. values('18','商品18',46,2,128,1,'商品说明','2021-04-26 11:11:11','2021-04-26 11:11:11');
  50. -- 添加商品图片
  51. insert into product_img(id,item_id,url,sort,is_main,created_time,updated_time)
  52. values('9','5','wwxb_1.png',1,1,'2021-04-26 11:11:11','2021-04-26 11:11:11');
  53. insert into product_img(id,item_id,url,sort,is_main,created_time,updated_time)
  54. values('10','6','wwxb_1.png',1,1,'2021-04-26 11:11:11','2021-04-26 11:11:11');
  55. insert into product_img(id,item_id,url,sort,is_main,created_time,updated_time)
  56. values('11','7','wwxb_1.png',1,1,'2021-04-26 11:11:11','2021-04-26 11:11:11');
  57. insert into product_img(id,item_id,url,sort,is_main,created_time,updated_time)
  58. values('12','8','wwxb_1.png',1,1,'2021-04-26 11:11:11','2021-04-26 11:11:11');
  59. insert into product_img(id,item_id,url,sort,is_main,created_time,updated_time)
  60. values('13','9','wwxb_1.png',1,1,'2021-04-26 11:11:11','2021-04-26 11:11:11');
  61. insert into product_img(id,item_id,url,sort,is_main,created_time,updated_time)
  62. values('14','10','wwxb_1.png',1,1,'2021-04-26 11:11:11','2021-04-26 11:11:11');
  63. insert into product_img(id,item_id,url,sort,is_main,created_time,updated_time)
  64. values('15','11','wwxb_1.png',1,1,'2021-04-26 11:11:11','2021-04-26 11:11:11');
  65. insert into product_img(id,item_id,url,sort,is_main,created_time,updated_time)
  66. values('16','12','wwxb_1.png',1,1,'2021-04-26 11:11:11','2021-04-26 11:11:11');
  67. insert into product_img(id,item_id,url,sort,is_main,created_time,updated_time)
  68. values('17','13','wwxb_1.png',1,1,'2021-04-26 11:11:11','2021-04-26 11:11:11');
  69. insert into product_img(id,item_id,url,sort,is_main,created_time,updated_time)
  70. values('18','14','wwxb_1.png',1,1,'2021-04-26 11:11:11','2021-04-26 11:11:11');
  71. insert into product_img(id,item_id,url,sort,is_main,created_time,updated_time)
  72. values('19','15','wwxb_1.png',1,1,'2021-04-26 11:11:11','2021-04-26 11:11:11');
  73. insert into product_img(id,item_id,url,sort,is_main,created_time,updated_time)
  74. values('20','16','wwxb_1.png',1,1,'2021-04-26 11:11:11','2021-04-26 11:11:11');
  75. insert into product_img(id,item_id,url,sort,is_main,created_time,updated_time)
  76. values('21','17','wwxb_1.png',1,1,'2021-04-26 11:11:11','2021-04-26 11:11:11');
  77. insert into product_img(id,item_id,url,sort,is_main,created_time,updated_time)
  78. values('22','18','wwxb_1.png',1,1,'2021-04-26 11:11:11','2021-04-26 11:11:11');
  • 查询SQL

    1. -- 查询所有的一级分类
    2. select * from category where category_level=1;
    3. -- 查询每个分类下销量前6的商品
    4. select * from product where root_category_id=2 order by sold_num desc limit 0,6;
    5. -- 查询每个商品的图片
    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;

}

  1. - Mapper接口中定义查询方法
  2. | CategoryMapper |
  3. | --- |
  4. | ![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) |
  5. | ProductMapper |
  6. | --- |
  7. | ![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) |
  8. - 映射配置
  9. | ProductMapper.xml |
  10. | --- |
  11. | ![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) |
  12. | CategoryMapper.xml |
  13. | --- |
  14. | ![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) |
  15. <a name="ec704cc9"></a>
  16. ###### 11.2.2 业务层实现
  17. <a name="5c8a3db0"></a>
  18. ###### 11.2.3 控制层实现
  19. <a name="69f32560"></a>
  20. #### 11.3 前端实现
  21. <a name="270aea62"></a>
  22. ## 十二、商品详情展示—显示商品基本信息
  23. > 点击首页推荐的商品、轮播图商品广告、商品列表页面点击商品,就会进入到商品的详情页面
  24. <a name="a22d397c"></a>
  25. #### 12.1 流程分析
  26. ![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)
  27. <a name="8a84c800"></a>
  28. #### 12.2 商品基础信息-接口实现
  29. > 商品基本信息、商品套餐、商品图片
  30. - SQL
  31. ```sql
  32. -- 根据id查询商品基本信息
  33. select * from product where product_id=3;
  34. -- 根据商品id查询当前商品的图片(√)
  35. select * from product_img where item_id=3;
  36. -- 根据商品id查询当前商品的套餐
  37. select * from product_sku where product_id=3;
  • 因为上述的三个查询都是单表查询,可以通过tkmapper完成,无需在Mapper接口定义新的方法
  • 业务层实现
    | ProductService接口 | | —- | | image.png |
ProductServiceImpl类实现
image.png
  • 控制层实现
    | ProductController类 | | —- | | image.png |

12.3 商品基础信息-前端显示

十三、商品详情展示—显示商品参数信息

13.1 接口实现

根据商品id查询商品参数信息

  • 数据库操作直接只用tkMapper的默认方法实现
  • 业务层实现
    | | | —- | | image.png |

  • 控制层实现

13.2 前端显示商品参数

13.3 前端显示商品细节

前端页面间URL传值
  • utils.js

    1. function getUrlParam(key){
    2. var url = decodeURI( window.location.toString() );
    3. var arr = url.split("?");
    4. if(arr.length>1){
    5. var params = arr[1].split("&");
    6. for(var i=0; i<params.length; i++){
    7. var param = params[i]; //"pid=101"
    8. if(param.split("=")[0] == key ){
    9. return param.split("=")[1];
    10. }
    11. }
    12. }
    13. return null;
    14. }
  • a.html

    1. <!DOCTYPE html>
    2. <html>
    3. <head>
    4. <meta charset="UTF-8">
    5. <title></title>
    6. </head>
    7. <body>
    8. <a href="b.html?pid=101&pname=咪咪虾条">跳转到B页面</a>
    9. </body>
    10. </html>
  • b.html

    1. <!DOCTYPE html>
    2. <html>
    3. <head>
    4. <meta charset="UTF-8">
    5. <title></title>
    6. </head>
    7. <body>
    8. This is Page B...
    9. <hr/>
    10. <script type="text/javascript" src="js/utils.js" ></script>
    11. <script type="text/javascript">
    12. var pid = getUrlParam("pid");
    13. </script>
    14. </body>
    15. </html>

十四、商品详情展示—显示商品评论信息

14.1 接口实现

14.1.1 数据库实现
  • 数据表分析及数据准备
  • SQL
  1. -- 根据ID查询商品的评价信息,关联查询评价用户的信息
  2. select u.username,u.nickname,u.user_img,c.*
  3. from product_comments c
  4. INNER JOIN users u
  5. ON u.user_id = c.user_id
  6. WHERE c.product_id =3;
  • 实体类封装ProductCommentsVO
  1. @Data
  2. @AllArgsConstructor
  3. @NoArgsConstructor
  4. public class ProductCommentsVO {
  5. private String commId;
  6. private String productId;
  7. private String productName;
  8. private String orderItemId;
  9. private Integer isAnonymous;
  10. private Integer commType;
  11. private Integer commLevel;
  12. private String commContent;
  13. private String commImgs;
  14. private Date sepcName;
  15. private Integer replyStatus;
  16. private String replyContent;
  17. private Date replyTime;
  18. private Integer isShow;
  19. //封装评论对应的用户数据
  20. private String userId;
  21. private String username;
  22. private String nickname;
  23. private String userImg;
  24. }
  • 在Mapper接口定义查询方法
  1. @Repository
  2. public interface ProductCommentsMapper extends GeneralDAO<ProductComments> {
  3. public List<ProductCommentsVO> selectCommontsByProductId(String productId);
  4. }
  • 映射配置:

14.1.2 业务层实现
  • 创建ProductCommontsService接口定义方法
  1. public interface ProductCommontsService {
  2. public ResultVO listCommontsByProductId(String productId);
  3. }
  • 创建实现类ProductCommontsServiceImpl实现查询操作
  1. @Service
  2. public class ProductCommontsServiceImpl implements ProductCommontsService {
  3. @Autowired
  4. private ProductCommentsMapper productCommentsMapper;
  5. @Override
  6. public ResultVO listCommontsByProductId(String productId) {
  7. List<ProductCommentsVO> productCommentsVOS = productCommentsMapper.selectCommontsByProductId(productId);
  8. ResultVO resultVO = new ResultVO(ResStatus.OK, "success", productCommentsVOS);
  9. return resultVO;
  10. }
  11. }

14.1.3 控制层实现
  • ProductController
  1. @ApiOperation("商品评论信息查询接口")
  2. @GetMapping("/detail-commonts/{pid}")
  3. public ResultVO getProductCommonts(@PathVariable("pid") String pid){
  4. return productCommontsService.listCommontsByProductId(pid);
  5. }

14.2 前端评论内容显示

十五、商品详情展示—商品评论分页及统计信息

15.1 流程分析

image.png

15.2 接口开发

15.2.1 改造商品评论列表接口

分页查询

  • 定义PageHelper
    ```java @Data @NoArgsConstructor @AllArgsConstructor public class PageHelper {

    //总记录数 private int count;

    //总页数 private int pageCount;

    //分页数据 private List list;

}

  1. - 改造数据库操作
  2. | ProductCommentsMapper 接口 |
  3. | --- |
  4. | ![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) |
  5. | ProductCommentsMapper.xml映射配置 |
  6. | --- |
  7. | ![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) |
  8. - 改造业务逻辑层
  9. | ProductCommontsService接口 |
  10. | --- |
  11. | ![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) |
  12. | ProductCommontsServiceImpl |
  13. | --- |
  14. | ![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) |
  15. - 改造控制层
  16. | ProductController |
  17. | --- |
  18. | ![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) |
  19. <a name="1eb38d7c"></a>
  20. ###### 15.2.2 评价统计接口实现
  21. - 数据库实现
  22. - 统计当前商品的总记录数
  23. - 统计当前商品的好评/中评/差评
  24. - 业务层实现: `ProductCommontsServiceImpl`
  25. ```java
  26. @Override
  27. public ResultVO getCommentsCountByProductId(String productId) {
  28. //1.查询当前商品评价的总数
  29. Example example = new Example(ProductComments.class);
  30. Example.Criteria criteria = example.createCriteria();
  31. criteria.andEqualTo("productId",productId);
  32. int total = productCommentsMapper.selectCountByExample(example);
  33. //2.查询好评评价数
  34. criteria.andEqualTo("commType",1);
  35. int goodTotal = productCommentsMapper.selectCountByExample(example);
  36. //3.查询中评评价数
  37. Example example1 = new Example(ProductComments.class);
  38. Example.Criteria criteria1 = example1.createCriteria();
  39. criteria1.andEqualTo("productId",productId);
  40. criteria1.andEqualTo("commType",0);
  41. int midTotal = productCommentsMapper.selectCountByExample(example1);
  42. //4.查询c评评价数
  43. Example example2 = new Example(ProductComments.class);
  44. Example.Criteria criteria2 = example2.createCriteria();
  45. criteria2.andEqualTo("productId",productId);
  46. criteria2.andEqualTo("commType",-1);
  47. int badTotal = productCommentsMapper.selectCountByExample(example2);
  48. //5.计算好评率
  49. double percent = (Double.parseDouble(goodTotal+"") / Double.parseDouble(total+"") )*100;
  50. String percentValue = (percent+"").substring(0,(percent+"").lastIndexOf(".")+3);
  51. HashMap<String,Object> map = new HashMap<>();
  52. map.put("total",total);
  53. map.put("goodTotal",goodTotal);
  54. map.put("midTotal",midTotal);
  55. map.put("badTotal",badTotal);
  56. map.put("percent",percentValue);
  57. ResultVO success = new ResultVO(ResStatus.OK, "success", map);
  58. return success;
  59. }

15.3 前端实现

15.3.1 商品评论的分页
  • 引用elementUI分页组件

    1. <!-- 引入样式 -->
    2. <link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
    3. <!-- vue的引入必须在elementUI组件库引入之前 -->
    4. <script type="text/javascript" src="js/vue.js"></script>
    5. <!-- 引入组件库 -->
    6. <script src="https://unpkg.com/element-ui/lib/index.js"></script>
  • 引用分页组件

    1. <!--分页 -->
    2. <el-pagination background layout="prev, pager, next"
    3. :current-page="pageNum"
    4. :page-size="limit"
    5. :total="count"
    6. @current-change="pager"> </el-pagination>
  • 监听分页组件的页码改变事件(点击上一页、下一页、页码都会导致页码改变
    分页组件的事件函数默认传递当前页码参数

    1. pager:function(currentPage){
    2. this.pageNum = currentPage;
    3. //请求下一页数据
    4. var url3 = baseUrl+"product/detail-commonts/"+this.productId;
    5. axios.get(url3,{
    6. params:{
    7. pageNum:this.pageNum,
    8. limit:this.limit
    9. }
    10. }).then((res)=>{
    11. //获取到评论分页数据
    12. var pageHelper = res.data.data;
    13. //当前页的评论列表
    14. this.productCommonts = pageHelper.list;
    15. //总页数
    16. this.pageCount = pageHelper.pageCount;
    17. //总记录数
    18. this.count = pageHelper.count;
    19. });
    20. }

15.3.2 商品评价统计

十六、购物车—添加购物车(登陆状态)

16.1 流程分析

image.png

16.2 接口实现

16.2.1 修改购物车数据表结构
shopping_cart
image.png
  • 数据表修改完成之后,对此表重新进行逆向工程

16.2.2 数据库实现
  • 单表添加操作,可以直接使用tkMapper完成

16.2.3 业务层实现
  • ShoppingCartService接口
    ```java public interface ShoppingCartService {

    public ResultVO addShoppingCart(ShoppingCart cart);

}

  1. - 实现类
  2. <a name="ee21ebc0"></a>
  3. #### 16.3 前端实现
  4. <a name="71e62ad4"></a>
  5. ###### 16.3.1 记录选择的套餐属性
  6. - vuedata中定义 `chooseSkuProps`
  7. | |
  8. | --- |
  9. | ![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) |
  10. - sku的属性添加点击事件
  11. | |
  12. | --- |
  13. | ![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) |
  14. - methods中定义事件函数`changeProp`
  15. | |
  16. | --- |
  17. | ![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) |
  18. - 添加套餐切换的监听事件:
  19. | |
  20. | --- |
  21. | ![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) |
  22. <a name="a3399e92"></a>
  23. ###### 16.3.2 套餐属性选中效果
  24. - 在套餐属性标签上添加name属性
  25. | |
  26. | --- |
  27. | ![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) |
  28. - 在属性的点击事件函数实现选中效果
  29. | |
  30. | --- |
  31. | ![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) |
  32. <a name="7cefa183"></a>
  33. ###### 16.3.3 修改商品数量
  34. - vuedata中定义`num`存储商品数量(默认值为1
  35. - 为+,-添加点击事件监听
  36. | |
  37. | --- |
  38. | ![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) |
  39. - 定义点击事件函数
  40. | |
  41. | --- |
  42. | ![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) |
  43. <a name="d8b587b8"></a>
  44. ###### 16.3.4 提交购物车
  45. | |
  46. | --- |
  47. | ![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) |
  48. <a name="ca0da361"></a>
  49. ## 十七、购物车—添加购物车(未登录状态)
  50. <a name="1f4aab5e"></a>
  51. #### 17.1 流程分析
  52. ![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)
  53. <a name="76ba79af"></a>
  54. #### 17.2 功能实现
  55. <a name="5419de60"></a>
  56. ###### 17.2.1 定义新的状态码
  57. | ResStatus |
  58. | --- |
  59. | ![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) |
  60. | 登录认证拦截器 |
  61. | --- |
  62. | ![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) |
  63. <a name="621d4683"></a>
  64. ###### 17.2.2 在详情页面判断如果用户未登录,则跳转到登录页面
  65. | introduction.html |
  66. | --- |
  67. | ![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) |
  68. <a name="543b4273"></a>
  69. ###### 17.2.3 登录页面接收回跳信息
  70. | login.html |
  71. | --- |
  72. | ![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) |
  73. | ![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) |
  74. <a name="db15e15f"></a>
  75. ###### 17.2.4 回到详情页时接收参数
  76. | introduction.html |
  77. | --- |
  78. | ![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) |
  79. <a name="16c1372c"></a>
  80. ###### 17.2.5 使用layui添加购物车成功/失败进行提示
  81. - 引入layui layui.com
  82. ```html
  83. <!-- 引入 layui.css -->
  84. <link rel="stylesheet" href="//unpkg.com/layui@2.6.5/dist/css/layui.css">
  85. <!-- 引入 layui.js -->
  86. <script src="//unpkg.com/layui@2.6.5/dist/layui.js">
  • 声明弹窗组件
    | | | —- | | image.png |

  • 当添加购物车成功或者失败的时候,进行提示:
    | | | —- | | image.png |

十八、购物车—购物车列表

18.1 流程分析

image.png

18.2 接口实现

18.2.1 数据库实现
  • SQL

    1. -- 根据用户ID查询当前用户的购物车信息
    2. select c.*, p.product_name,i.url
    3. from shopping_cart c
    4. INNER JOIN product p
    5. INNER JOIN product_img i
    6. ON c.product_id = p.product_id and i.item_id=p.product_id
    7. where user_id=6 and i.is_main=1;
  • 实体类
    | | | —- | | image.png |

  • 在Mapper接口定义查询方法
    ```java @Repository public interface ShoppingCartMapper extends GeneralDAO {

    public List selectShopcartByUserId(int userId);

}

  1. - 映射配置
  2. | |
  3. | --- |
  4. | ![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) |
  5. <a name="a54874fd"></a>
  6. ###### 18.2.2 业务层实现
  7. - Service接口
  8. | |
  9. | --- |
  10. | ![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) |
  11. - Service实现类
  12. | |
  13. | --- |
  14. | ![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) |
  15. <a name="4fbc7df7"></a>
  16. ###### 18.2.3 控制层实现
  17. <a name="9cc613d7"></a>
  18. #### 18.3 前端实现
  19. <a name="1aed0391"></a>
  20. ###### 18.3.1 显示购物车列表
  21. <a name="28736881"></a>
  22. ###### 18.3.2 显示购物车中商品价格
  23. <a name="f68ba5a1"></a>
  24. ## 十九、购物车-修改购物车数量
  25. <a name="3d1f3f1f"></a>
  26. #### 19.1 流程分析
  27. <a name="1d49a9ae"></a>
  28. #### 19.2 接口实现
  29. - Mapper接口定义修改方法
  30. ```java
  31. @Repository
  32. public interface ShoppingCartMapper extends GeneralDAO<ShoppingCart> {
  33. public List<ShoppingCartVO> selectShopcartByUserId(int userId);
  34. public int updateCartnumByCartid(@Param("cartId") int cartId,
  35. @Param("cartNum") int cartNum);
  36. }
  • 映射配置

    1. <update id="updateCartnumByCartid">
    2. update shopping_cart set cart_num=#{cartNum} where cart_id=#{cartId}
    3. </update>
  • Service接口
    ```java public interface ShoppingCartService {

    public ResultVO addShoppingCart(ShoppingCart cart);

    public ResultVO listShoppingCartsByUserId(int userId);

    public ResultVO updateCartNum(int cartId,int cartNum);

}

  1. - Service实现类
  2. ```java
  3. @Service
  4. public class ShoppingCartServiceImpl implements ShoppingCartService {
  5. @Autowired
  6. private ShoppingCartMapper shoppingCartMapper;
  7. private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
  8. @Override
  9. public ResultVO updateCartNum(int cartId, int cartNum) {
  10. int i = shoppingCartMapper.updateCartnumByCartid(cartId, cartNum);
  11. if(i>0){
  12. return new ResultVO(ResStatus.OK,"update success",null);
  13. }else{
  14. return new ResultVO(ResStatus.NO,"update fail",null);
  15. }
  16. }
  17. }
  • 控制层实现
    1. @PutMapping("/update/{cid}/{cnum}")
    2. public ResultVO updateNum(@PathVariable("cid") Integer cartId,
    3. @PathVariable("cnum") Integer cartNum,
    4. @RequestHeader("token") String token){
    5. ResultVO resultVO = shoppingCartService.updateCartNum(cartId, cartNum);
    6. return resultVO;
    7. }

19.3 前端实现

  • 为按钮添加点击事件
    | | | —- | | image.png |

  • 定义changeNum事件函数
    | | | —- | | image.png |

二十、购物车—结算、提交订单

在购物车列表中选择对应的的商品之后,点击提交生成订单的过程

20.1 流程图

image.png

20.2 接口实现

20.2.1 收货地址列表接口

此操作的数据库实现可以通过tkmapper通用方法完成

  • service接口UserAddrService
    ```java public interface UserAddrService {

    public ResultVO listAddrsByUid(int userId);

}

  1. - Service实现类 `UserAddrServiceImpl`
  2. ```java
  3. @Service
  4. public class UserAddrServiceImpl implements UserAddrService {
  5. @Autowired
  6. private UserAddrMapper userAddrMapper;
  7. @Transactional(propagation = Propagation.SUPPORTS)
  8. public ResultVO listAddrsByUid(int userId) {
  9. Example example = new Example(UserAddr.class);
  10. Example.Criteria criteria = example.createCriteria();
  11. criteria.andEqualTo("userId",userId);
  12. criteria.andEqualTo("status",1);
  13. List<UserAddr> userAddrs = userAddrMapper.selectByExample(example);
  14. ResultVO resultVO = new ResultVO(ResStatus.OK, "success", userAddrs);
  15. return resultVO;
  16. }
  17. }
  • 控制器实现
    ```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){

    1. ResultVO resultVO = userAddrService.listAddrsByUid(userId);
    2. return resultVO;

    }

}

  1. <a name="1ad87bd5"></a>
  2. ###### 20.2.2 购物车记录列表接口
  3. > 根据一个ID的集合,查询购物车记录,实现方式有两种:
  4. >
  5. > - 动态sql
  6. > - tkMapper条件查询<br />criteria.andIn("cartId",ids);
  7. >
  8. ```xml
  9. <select id="searchShoppingCartById" resultMap="shopCartMap">
  10. select * from shopping_cart where cart_id in
  11. <foreach collection="list" item="cid" separator="," open="(" close=")">
  12. #{cid}
  13. </foreach>
  14. </select>
  • Mapper接口定义查询方法
    | | | —- | | image.png |

  • 映射配置(动态sql foreach)
    | image.png | | —- | | image.png |

  • Service接口
    | | | —- | | image.png |

  • Service实现类
    | | | —- | | image.png |

  • 控制器实现

    1. @GetMapping("/listbycids")
    2. @ApiImplicitParam(dataType = "String",name = "cids", value = "选择的购物车记录id",required = true)
    3. public ResultVO listByCids(String cids, @RequestHeader("token")String token){
    4. ResultVO resultVO = shoppingCartService.listShoppingCartsByCids(cids);
    5. return resultVO;
    6. }

20.2.3 保存订单

20.3 前端实现

20.3.1 选择购物车记录价格联动
  • 列表前的复选框标签
    | | | —- | | image.png |

  • 渲染商品数量以及总价格
    | | | —- | | image.png |

  • 在vue示例的data中声明opts和totalPrice,并且监听opts选项的改变—选项一旦改变就计算总价格
    | | | —- | | image.png |

20.3.2 点击“结算”跳转到订单添加页面

在购物车列表页面,选择购物车记录,点击“结算之后”将选择的购物车记录ID传递到order-add.html

  • shopcart.html
    | image.png | | —- | | image.png |

  • order-add.html
    | | | —- | | image.png |

20.3.3 显示收货地址及订单商品

20.3.4 订单确认页面选择地址

二十一、订单提交及支付

21.1 流程分析

image.png

21.2 订单添加接口实现

21.2.1 数据库操作
  • 根据收货地址ID,获取收货地址信息(tkMapper)
  • 根据购物车ID,查询购物车详情(需要关联查询商品名称、sku名称、库存、商品图片、商品价格)
    | 改造:ShoppingCartMapper
    中的selectShopcartByCids | | —- | | image.png | | image.png | | image.png |

  • 保存订单(tkMapper)

  • 修改库存(tkMapper)
  • 保存商品快照(tkMapper)

21.2.2 业务层实现

开发任务

1、首页轮播图

2、首页分类列表

3、首页商品推荐

4、首页类别推荐商品

5、商品详情

6、商品评价显示

7、实现评价统计及分页

8、购物车