锋迷商城学习

news/2024/12/23 6:41:03/

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

1.1 项目背景

​ 锋迷商城——电商平台

  • B2C商家对客户
  • C2B2C 客户对商家对客户

1.1.1 B2C(一家超市)

平台运营方即商品的卖家,例如:小米商城

  • 商品
  • 用户

1.1.2 C2B2C(一条街)

平台运营方不卖商品(也可以卖)

卖家是平台的用户

买家也是平台的用户

  • 用户(店铺)
  • 用户(买家)
  • 服务
  • 商品

1.1.3 Java

Java语言的应用领域很广,但主要应用于web领域的项目开发,web项目类型分为两类:

  • 企业级开发(供企业内部使用的系统:企业内部的管理系统CRM\ERP,学校的教务管理系统)
  • 互联网开发(提供给所有互联网用户使用的系统——用户量)——电商,扩展知识(BAT:百度、阿里、腾讯;TMD:头条、美团、滴滴)

1.2 项目功能

https://www.processon.com/mindmap/60fbfb63e0b34d49622edc3a

1.3 技术选型

SSM 企业开发框架 基础的开发技术

1.3.1 单体项目

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

在这里插入图片描述

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

1.3.2 技术清单

  • 项目架构:前后端分离
  • 前端技术:vue、axios 、Amaze UI、layui、bootstrap(jQuery已成过去式(页面动态刷新+异步通信 ==> 笨重))
  • 后端技术:SpringBoot(包含Spring、SpringMVC)+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 构建父工程

  • 创建一个Maven工程、packaging设置为 pom

  • 父工程继承spring-boot-starter-parent

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><!-- spring-boot-starter-parent -->  <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.5.3</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>com.lzp</groupId><artifactId>fmmall</artifactId><version>2.0.1</version><packaging>pom</packaging></project>
    

3.2.2 创建common工程

  • 选择fmmall,右键——New——Module(Maven工程)

  • 修改common的pom.xml,设置packaging=jar

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><parent><groupId>com.lzp</groupId><artifactId>fmmall</artifactId><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,设置packaging=jar

3.2.4 创建mapper工程

  • 选择fmmall,右键——New——Module(Maven工程)

  • 修改mapper的pom.xml,设置packaging=jar

  • 在mapper的pom.xml,依赖beans

    <dependency><groupId>com.lzp</groupId><artifactId>beans</artifactId><version>2.0.1</version>
    </dependency>
    

3.2.5 创建service工程

  • 选择fmmall,右键——New——Module(Maven工程)

  • 修改service的pom.xml,设置packaging=jar

  • 在service的pom.xml,依赖mapper,common

    <dependency><groupId>com.lzp</groupId><artifactId>mapper</artifactId><version>2.0.1</version>
    </dependency>
    <dependency><groupId>com.lzp</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.lzp</groupId><artifactId>fmmall</artifactId><version>2.0.1</version>
    </parent>
    
  • 将springboot的依赖配置到父工程fmmall的pom.xml

  • 在父工程fmmall的pom.xml的modules添加api

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.5.3</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>com.lzp</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></project>
    
  • 在api的pom.xml中,依赖service

    <!-- 依赖了service,等于依赖了所有 -->
    <dependency><groupId>com.lzp</groupId><artifactId>service</artifactId><version>2.0.1</version>
    </dependency>
    

3.3 Maven聚合工程依赖分析

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

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

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

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Q6gEP39o-1646129708196)(C:\Users\14715\AppData\Roaming\Typora\typora-user-images\image-20210727145559634.png)]

说明

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

3.4 整合MyBatis

3.4.1 common子工程

  • lombok

3.4.2 beans子工程

  • lombok

3.4.3 MyBatis整合

  • 在mapper子工程的pom文件,新增mybatis所需的依赖

    <!--mysql-->
    <dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.47</version>
    </dependency>
    <!--spring-boot-starter-->
    <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId><version>2.4.4</version>
    </dependency>
    <!--mybatis starter-->
    <dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.1.4</version>
    </dependency>
    
  • 在mapper子工程的resources目录创建application.yml

    spring:datasource:driver-class-name: com.mysql.jdbc.Driverurl: jdbc:mysql://localhost:3306/db_2010_mybatis?characterEncoding=utf-8username: rootpassword: 123
    mybatis:mapper-locations: classpath:mappers/*Mapper.xmltype-aliases-package: com.lzp.fmmall.entity
    
  • 在**api子工程*的启动类通过 @MapperScan 声明dao包的路径

    @SpringBootApplication
    @MapperScan("com.lzp.fmmall.dao")
    public class ApiApplication {public static void main(String[] args) {SpringApplication.run(ApiApplication.class, args);}}
    

3.5 基于SpringBoot的单元测试

3.5.1 添加依赖

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

3.5.2 测试类

package com.lzp.fmmall.dao;import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;@SpringBootTest
public class UserDAOTest {@Testpublic void queryUserByName() {System.out.println("测试成功");}
}

3.6 整合Druid

3.6.1 添加依赖

  • mapper子工程添加druid-starter

    <!--druid starter-->
    <dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>1.1.22</version>
    </dependency>
    

3.6.2 修改数据源配置

  • 修改mapper子工程application.yml文件
spring:datasource:druid:driver-class-name: com.mysql.jdbc.Driverurl: jdbc:mysql://localhost:3306/db_2010_mybatis?characterEncoding=utf-8username: rootpassword: 123
mybatis:mapper-locations: classpath:mappers/*Mapper.xmltype-aliases-package: com.lzp.fmmall.entity

四、《锋迷商城》数据库设计

4.1 软件开发步骤

  • 问题定义/提出问题
  • 可行性分析(技术、成本、法律法规)
  • 需求分析(需求采集、需求分析下)——> 甲方
  • 概要设计
    • 系统设计(技术选型、架构模式、项目搭建)
    • 数据库设计
    • UI设计
    • 业务流程设计
  • 详细设计
    • 实现步骤(业务流程的实现细节)
  • 编码
    • 根据设计好的实现步骤进行代码实现
    • 开发过程中开发者要进行单元测试
  • 测试
    • 集成测试
    • 功能测试(黑盒测试)
    • 性能测试(白盒测试)
  • 交付/部署实施

4.2 数据库设计流程

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

4.3 《锋迷商城》数据库设计分析

4.3.1 PDMan建模工具使用

  • 可视化创建数据表(数据表)

  • 视图显示数据表之间的关系(关系图)

  • 导出SQL指令(模型-导出DDL脚本)

  • 记录数据设计的版本-数据库模型版本的管理(模型版本)

  • 同步数据模型到数据库(开始-数据库连接)

4.3.2 分析《锋迷商城》的数据库模型

  • 用户
  • 首页
    • 轮播图
    • 类别
  • 商品

4.4 SPU 和 SKU

4.4.1 SPU

SPU(Standard Product Unit):标准化产品单元。是商品信息聚合的最小单位,是一组可复用、易检索的标准化信息的集合,该集合描述了一个产品的特性。通俗点讲,属性值、特性相同的商品就可以称为一个SPU。

产品:

1 荣耀8

2 小米10

4.4.2 SKU(理解为套餐)

SKU(中文译为最小存货单位,英文全称为Stock Keeping Unit,简称SKU,定义为保存库存控制的最小可用单位

101 8G / 128G 10 2999 1

102 4G / 64G 20 1999 1

103 8G / 128G 12 2999 1

104 12G / 256G 11 3999 1

4.5 建库建表

4.5.1 创建数据表

  • 从PDMan导出sql,导入到mysql

4.5.2 准备测试数据

  • 首页轮播图 index_img

  • 首页类别信息 category
    在这里插入图片描述

  • 商品信息

  • sku

五、《锋迷商城》业务流程设计

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

在这里插入图片描述

5.1 用户管理业务流程分析

单体架构:页面和控制之间可以进行跳转,同步请求控制器,流程控制由后端来完成

前后端分离架构:前端和后端分离开发和部署,前端只能通过异步向后端发送请求,后端只负责接收请求及参数、处理请求、返回处理结果,但是后端并不负责流程控制,流程控制是由前端完成

5.1.1 单体架构

在这里插入图片描述

5.1.2 前后端分离架构

在这里插入图片描述

5.2 接口介绍

5.2.1 接口

狭义的理解:就是控制器中可以接收用户请求的某个方法

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

5.5.2 接口规范

作为一个后端开发者,我们不仅要完成接口程序的开发,还要编写接口的说明文档——接口规范

接口规范示例

参考:《锋迷商城》后端接口说明

5.3 Swagger

前后端分离开发,后端需要编写接口说明文档,会耗费比较多的时间

swagger是一个用于生成服务器接口的规范性文档,并且能够对接口进行测试的工具

5.3.1 作用

  • 生成接口说明文档
  • 对接口进行测试

5.3.2 整合

  • api子工程添加依赖(Swagger2 \ Swagger UI)

    <!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger2 -->
    <dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger2</artifactId><version>2.9.2</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger-ui -->
    <dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger-ui</artifactId><version>2.9.2</version>
    </dependency>
    
  • zapi子工程创建swagger的配置(基于Java的配置方式)

    package com.lzp.fmmall.config;import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import springfox.documentation.builders.ApiInfoBuilder;
    import springfox.documentation.builders.PathSelectors;
    import springfox.documentation.builders.RequestHandlerSelectors;
    import springfox.documentation.service.ApiInfo;
    import springfox.documentation.service.Contact;
    import springfox.documentation.spi.DocumentationType;
    import springfox.documentation.spring.web.plugins.Docket;
    import springfox.documentation.swagger2.annotations.EnableSwagger2;/*** @Author LZP* @Date 2021/7/29 9:57* @Version 1.0*/
    @Configuration
    @EnableSwagger2
    public class SwaggerConfig {/*** 返回一个封装接口文档信息的Docket对象*/@Beanpublic Docket getDocket() {// 创建Docket实例Docket docket = new Docket(DocumentationType.SWAGGER_2);// 创建封面信息对象,通过ApiInfoBuilder来创建ApiInfoBuilder builder = new ApiInfoBuilder();// 进行相应的配置builder.title("《锋迷商城》后端接口说明文档").description("此文档详细说明了锋迷商城项目后端接口规范...").version("v 2.0.1").contact(new Contact("LZP", "https://www.cnblogs.com/pengsay", "1471591945@qq.com"));ApiInfo apiInfo = builder.build();docket.apiInfo(apiInfo).select().apis(RequestHandlerSelectors.basePackage("com.lzp.fmmall.controller")).paths(PathSelectors.any()).build();return docket;}
    }
    
  • 测试:

    • 启动SpringBoot应用,访问:http://localhost:8080/swagger-ui.html

5.3.3 Swagger注解说明

swagger提供了一套注解,可以对每个接口进行详细说明

@Api类注解,在控制器类上添加此注解,可以对控制器类进行功能说明

@Api(value = "提供商品添加、删除、修改及查询的相关接口", tags = "商品管理")

@ApiOperation 方法注解,说明接口方法的作用

@ApiImplicitParams@ApiImplicitParam 方法注解,说明接口方法的参数

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

@ApiModel@ApiModelProperty当接口参数和返回值为对象类型时,在实体类中添加注解说明

package com.lzp.fmmall.entity;import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;/*** @Author LZP* @Date 2021/7/27 15:52* @Version 1.0*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@ApiModel(value = "User对象", description = "用户/买家信息")
public class User {@ApiModelProperty(dataType = "int", required = false, value = "用户id")private int userId;@ApiModelProperty(dataType = "String", required = true, value = "用户账号")private String userName;@ApiModelProperty(dataType = "String", required = true, value = "用户密码")private String userPwd;@ApiModelProperty(dataType = "String", required = true, value = "用户真实姓名")private String userRealname;@ApiModelProperty(dataType = "String", required = true, value = "用户头像url")private String userImg;}

@ApiIgnore 接口方法注解,添加此注解的方法将不会生成到接口文档中

5.3.4 swagger-bootstrap-ui插件

  • 导入插件的依赖

    <!-- https://mvnrepository.com/artifact/com.github.xiaoymin/swagger-bootstrap-ui -->
    <dependency><groupId>com.github.xiaoymin</groupId><artifactId>swagger-bootstrap-ui</artifactId><version>1.9.6</version>
    </dependency>
    
  • 文档访问

    http://ip:port/doc.html

5.4 RESTful

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

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

  • 每个URL请求路径代表服务器上的唯一资源

    传统的URL设计:http://localhost:8080/goods/delete?goodsId=1	商品1http://localhost:8080/goods/delete?goodsId=2	商品2RESTful设计:http://localhost:8080/goods/delete/1			商品1http://localhost:8080/goods/delete/2			商品2
    
    @RequestMapping("/delete/{gid}")
    public ResultVO deleteGoods(@PathVariable("gid") int goodsId) {System.out.println("--goodsId-->" + goodsId);return new ResultVO(10000, "SUCCESS", null);
    }
    
  • 使用不同的请求方式表示不同的操作

    SpringMVC对RESTful风格提供了很好的支持,在我么定义一个接口的URL时,可以通过@RequestMapping(value="/{id}", method=RequestMapping.GET),形式指定请求方式,也可使用特定请求方式的注解设定URL

    @PostMapping("/add")

    @DeleteMapping("/{id}")

    @PutMapping("/{id}")

    @GetMapping("/{id}")

    • post 添加
    • get 查询
    • put 修改
    • delete 删除
    • option(预检)
    根据ID删除一个商品
    // localhost:8080/goods/1   [delete]	商品1
    @RequestMapping(value = "/{gid}", method = RequestMethod.DELETE)
    public ResultVO deleteGoods(@PathVariable("gid") int goodsId) {System.out.println("--goodsId-->" + goodsId);return new ResultVO(10000, "SUCCESS", null);
    }根据ID查询一个商品
    // localhost:8080/goods/1    [get]		商品1
    @RequestMapping(value = "/{gid}", method = RequestMethod.GET)
    public ResultVO getGoods(@PathVariable("gid") int goodsId) {System.out.println("--goodsId-->" + goodsId);return new ResultVO(10000, "SUCCESS", null);
    }
    
  • 接口响应的资源的表现形式采用JSON(或者XML)

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

六、《锋迷商城》设计及实现—用户管理

6.1 实现流程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sADil8TS-1646129708206)(C:\Users\14715\AppData\Roaming\Typora\typora-user-images\image-20210729152133861.png)]

6.2 后端接口开发

6.2.1 完成DAO操作

  1. 创建实体类

    @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接口,定义操作方法

    package com.lzp.fmmall.dao;import com.lzp.fmmall.entity.User;public interface UserDAO {// 用户注册public int insertUser(User user);// 根据用户名查询用户信息public User queryUserByName(String name);
    }
  3. 创建DAO接口的mapper文件并完成配置

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.lzp.fmmall.dao.UserDAO"><insert id="insertUser">insert intousers (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" resultType="userMap">selectuser_id,username,password,nickname,realname,user_img,user_mobile,user_email,user_sex,user_birth,user_regtime.user_modtimefrom userswhere username = #{name}</select>
    </mapper>
    

6.2.2 完成Service业务

  1. 创建service接口

    public interface UserService {// 用户注册public ResultVO userRegister(String name, String pwd);// 用户登录public ResultVO checkLogin(String name, String pwd);
    }
    
  2. 创建service接口实现类,完成业务实现

    @Service
    public class UserServiceImpl implements UserService {@Autowiredprivate UserDAO userDAO;@Transactionalpublic ResultVO userRegister(String name, String pwd) {// 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.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);}}@Overridepublic 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

  2. 添加接口注解

    package com.lzp.fmmall.controller;import com.lzp.fmmall.service.UserService;
    import com.lzp.fmmall.vo.ResultVO;
    import io.swagger.annotations.Api;
    import io.swagger.annotations.ApiImplicitParam;
    import io.swagger.annotations.ApiImplicitParams;
    import io.swagger.annotations.ApiOperation;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.*;/*** @Author LZP* @Date 2021/7/27 19:06* @Version 1.0*/
    @RestController
    @RequestMapping("/user")
    @Api(value = "提供用户的登录与注册接口", tags = "用户管理")
    public class UserController {@Autowiredprivate 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("password") String pwd) {return userService.checkLogin(name, pwd);}@ApiOperation("用户注册接口")@ApiImplicitParams({@ApiImplicitParam(dataType = "string", name = "username", value = "用户注册账号", required = true),@ApiImplicitParam(dataType = "string", name = "password", value = "用户注册密码", required = true)})@PostMapping("/register")public ResultVO register(String username, String password) {return userService.userRegister(username, password);}
    }
    

6.2.4 接口测试

  • 基于swagger2进行测试
  • 测试地址:http://localhost:8080/doc.html

6.3 前端功能实现

6.3.1 跨域访问概念

  • 什么是跨域访问?

    AJAX 跨域访问是用户访问A网站时所产生的对B网站的跨域访问请求均提交到A网站的指定页面

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6utdMcWq-1646129708209)(C:\Users\14715\AppData\Roaming\Typora\typora-user-images\image-20210730132631230.png)]

6.3.2 如何解决跨域访问?

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

6.4 前端页面之间的传值

6.4.1 cookie

  • 工具方法封装:

    // 定义键值对之间的分隔符
    var operator = "=";// 根据键获取cookie的值
    function getCookieValue(key) {// 获取cookie字符串对象var cookieStr = document.cookie;// 按'; '切割cookieStr字符串var array = cookieStr.split("; ");for (var i = 0; i < array.length; i++) {// 按'='切割键值对var kvStr = array[i].split(operator);var k = kvStr[0];var v = kvStr[1];if (k === key) {return v;}}return null;
    }// 将键值对保存到cookie中
    function setCookieValue(key, value) {document.cookie = key + operator + value;
    }
    
  • A页面

    setCookieValue("username", userInfo.username);
    setCookieValue("userImg", userInfo.userImg);
    
  • B页面

    var name = getCookieValue("username");
    var img = getCookieValue("userImg");
    

6.4.2 localStorage

  • A页面

    // JSON.stringify()函数:将对象转成json格式字符串
    localStorage.setItem("user", JSON.stringify(userInfo));
    
  • B页面

    var jsonStr = localStorage.getItem("user");
    // eval()函数:将json格式字符串转为对象
    var userInfo = eval("(" + jsonStr + ")");
    // 取到值之后,立马将其移除
    localStorage.removeItem("user");
    

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

7.1 基于session实现单体项目用户认证

在单体项目中如何保证受限资源在用户未登录的情况下不允许访问?

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8vICS2ht-1646129708211)(C:\Users\14715\AppData\Roaming\Typora\typora-user-images\image-20210805090319162.png)]

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

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

7.2 基于token实现前后端分离用户认证

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WcX7QXGF-1646129708212)(C:\Users\14715\AppData\Roaming\Typora\typora-user-images\image-20210805104951376.png)]

7.3 基于token的用户认证实现

7.3.1 登录认证接口生成token

// UserController
@GetMapping("/login")
public ResultVO login(@RequestParam("username") String name,@RequestParam("password") String pwd) {return userService.checkLogin(name, pwd);
}
// UserServiceImpl
@Override
public ResultVO checkLogin(String name, String pwd) {Example userExample = new Example(Users.class);Example.Criteria userCriteria = userExample.createCriteria();userCriteria.andEqualTo("username", name);List<Users> users = usersMapper.selectByExample(userExample);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 + "LZP123456");return new ResultVO(ResStatus.OK, token, users.get(0));} else {return new ResultVO(ResStatus.NO, "登录失败,密码错误!", null);}}
}

7.3.2 登录页面接收到token存储到cookie

// login.html
doSubmit: function () {// 特判:防止用户在浏览器保存了登录信息,即此时一打开登录页面账号和密码就已经存在this.checkLoginInfo();if (!vm.isRight) {// 校验不通过,给出提示信息vm.tip = "校验失败:" + vm.tip;} else {var url = baseUrl + "user/login";// 验证通过,提交信息到后端// 通过axios发送异步请求axios({method: "GET",url: url,params: {username: vm.username,password: vm.password}}).then(res => {// 校验后台是否登录成功var vo = res.data;if (vo.code === 10000) {// 如果登录成功,就把token存储到cookiesetCookieValue("token", vo.msg);// 跳转到index页面window.location.href = "index.html";} else {// 后台登录失败,给出提示信息vm.tip = vo.msg;}});}
}

7.3.3 购物车页面加载时访问购物车列表接口

  • 获取token

  • 携带token访问接口

    <script type="text/javascript">var baseUrl = "http://localhost:8080/";var vm = new Vue({el: "#container",data: {token: ""},created: function () {// 从cookie中获取tokenthis.token = getCookieValue("token");console.log("token:" + this.token);// 发送请求axios({method: "GET",url: baseUrl + "shopcar/list",params: {token: this.token}}).then(res => {console.log(res);});}});
    </script>
    

7.3.4 在购物车列表接口校验token

@GetMapping("/list")
@ApiImplicitParam(dataType = "string", name = "token", value = "授权令牌", required = true)
public ResultVO listCars(String token) {// 1、获取token// 2、校验tokenif (token == null) {return new ResultVO(ResStatus.NO, "请先登录!", null);}String decode = Base64Utils.decode(token);if (decode.endsWith("LZP123456")) {// token校验成功return new ResultVO(ResStatus.OK, "success", null);}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的结构

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CR0vBIzx-1646129708213)(C:\Users\14715\AppData\Roaming\Typora\typora-user-images\image-20210805142631751.png)]

7.4.2 生成JWT

  • 添加依赖

    <!-- https://mvnrepository.com/artifact/com.auth0/java-jwt -->
    <dependency><groupId>com.auth0</groupId><artifactId>java-jwt</artifactId><version>3.10.3</version>
    </dependency><!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt -->
    <dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.9.1</version>
    </dependency>
    
  • 生成token

    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)) // 设置token过期时间.signWith(SignatureAlgorithm.HS256, "LZP123456") // 设置加密方式和加密密码.compact();
    

7.4.3 JWT校验

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

    try {// 验证tokenJwtParser parser = Jwts.parser();parser.setSigningKey("LZP123456"); // 解析token的signingKey必须和生成token时设置密码一致 "LZP123456"// 如果token 正确(密码正确,有效期内)则正常执行、否则抛出异常Jws<Claims> claimsJws = parser.parseClaimsJws(token);Claims body = claimsJws.getBody(); // 获取token中用户数据String subject = body.getSubject(); // 获取生效token设置的subjectString v1 = body.get("key1", String.class); // 获取生成token时存储的Claims的map中的值System.out.println("subject:" + subject);System.out.println("v1:" + v1);// token校验成功return new ResultVO(ResStatus.OK, "success", null);
    } catch (ExpiredJwtException e) {return new ResultVO(ResStatus.NO, "登录过期,请重新登录!", null);
    } catch (UnsupportedJwtException e) {return new ResultVO(ResStatus.NO, "Token不合法,请自重!", null);
    } catch (Exception e) {return new ResultVO(ResStatus.NO, "请重新登录!", null);
    }
    

7.4.3 拦截器校验Token

  • 创建拦截器

    package com.lzp.fmmall.intercept;import com.fasterxml.jackson.databind.ObjectMapper;
    import com.lzp.fmmall.vo.ResStatus;
    import com.lzp.fmmall.vo.ResultVO;
    import io.jsonwebtoken.*;
    import org.springframework.web.servlet.HandlerInterceptor;import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    import java.io.PrintWriter;/*** @Author LZP* @Date 2021/8/5 16:24* @Version 1.0*/
    public class CheckTokenInterceptor implements HandlerInterceptor {@Overridepublic 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);return false;} else {try {//验证tokenJwtParser parser = Jwts.parser();parser.setSigningKey("LZP123456");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();}
    }
    
  • 配置拦截器

    @Configuration
    public class InterceptorConfig implements WebMvcConfigurer {@Autowiredprivate CheckTokenInterceptor checkTokenInterceptor;@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(checkTokenInterceptor).addPathPatterns("/**").excludePathPatterns("/user/**");}
    }
    

7.5 请求头传递token

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

7.5.1 axios通过请求头传值

axios({method: "GET",url: baseUrl + "shopcar/list",headers: {token: this.token}
}).then(res => {console.log(res);
});

7.5.2 在拦截器重放行options请求

@Component
public class CheckTokenInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 处理预检// 放行options请求String method = request.getMethod();if ("OPTIONS".equalsIgnoreCase(method)) {return true;}// 通过请求头参数获取tokenString token = request.getHeader("token");System.out.println("---------------" + token);if (token == null) {ResultVO resultVO = new ResultVO(ResStatus.NO, "请先登录", null);// 提示请求登录doResponse(response, resultVO);} else {try {//验证tokenJwtParser parser = Jwts.parser();parser.setSigningKey("LZP123456");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 实现流程分析

  • 流程图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IojJfYHV-1646129708214)(C:\Users\14715\AppData\Roaming\Typora\typora-user-images\image-20210806154325715.png)]

8.2 完成后台接口开发

8.2.1 数据库操作实现

  • 分析数据表结构

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3I5trhj2-1646129708215)(C:\Users\14715\AppData\Roaming\Typora\typora-user-images\image-20210806162717891.png)]

  • 添加测试数据

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aN9Z4Xw7-1646129708216)(C:\Users\14715\AppData\Roaming\Typora\typora-user-images\image-20210806164116830.png)]

  • 编写sql语句

    selectimg_id,img_url,img_bg_color,prod_id,category_id,index_type,seq,status,create_time,update_timefrom index_img where `status` = 1 ORDER BY seq;
    
  • 在Mapper接口(DAO)中定义操作方法

    public interface IndexImgMapper extends GeneralDAO<IndexImg> {// 1、查询轮播图信息:查询status=1 且 按照seq进行排序public List<IndexImg> listIndexImgs();}
    
  • 配置映射文件

    <!-- BaseResultMap是由逆向工程生成的 -->
    <select id="listIndexImgs"  resultMap="BaseResultMap">selectimg_id,img_url,img_bg_color,prod_id,category_id,index_type,seq,status,create_time,update_timefrom index_imgwhere `status` = 1ORDER BY seq;</select>
    

8.2.2 业务层实现

  • IndexImgService接口

    public interface IndexImgService {public ResultVO listIndexImgs();}
    
  • IndexImgServiceImpl实现类

    @Service
    public class IndexImgServiceImpl implements IndexImgService {@Autowiredprivate IndexImgMapper indexImgMapper;@Overridepublic 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类

    package com.lzp.fmmall.controller;import com.lzp.fmmall.service.IndexImgService;
    import com.lzp.fmmall.vo.ResultVO;
    import io.swagger.annotations.Api;
    import io.swagger.annotations.ApiOperation;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.CrossOrigin;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;/*** @Author LZP* @Date 2021/8/6 16:53* @Version 1.0*/
    @RestController
    @CrossOrigin
    @RequestMapping("/index")
    @Api(value = "提供首页数据显示所需的接口", tags = "首页管理")
    public class IndexController {@Autowiredprivate IndexImgService indexImgService;@GetMapping("/indeximg")@ApiOperation("首页轮播图接口")public ResultVO listIndexImgs() {return indexImgService.listIndexImgs();}}
    

8.3 完成前端功能

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aZzzhB8S-1646129708217)(C:\Users\14715\AppData\Roaming\Typora\typora-user-images\image-20210806183041664.png)]

九、首页—分类列表

9.1 实现流程分析

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-I648WZdL-1646129708218)(C:\Users\14715\AppData\Roaming\Typora\typora-user-images\image-20210807131307420.png)]

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

9.2 接口开发

9.2.1 数据库操作实现

  • 数据表结构

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dOsmZiqA-1646129708219)(C:\Users\14715\AppData\Roaming\Typora\typora-user-images\image-20210807131907731.png)]

  • 添加测试数据

  • 编写接口实现所需的SQL

    • 连接查询

      select c1.category_id 'category_id1',c1.category_name 'category_name1',c1.category_level 'category_level1',c1.parent_id 'parent_id1',c1.category_icon 'category_icon1',c1.category_slogan 'category_slogan1',c1.category_pic 'category_pic1',c1.category_bg_color 'category_bg_color1',c2.category_id 'category_id2',c2.category_name 'category_name2',c2.category_level 'category_level2',c2.parent_id 'parent_id2',c3.category_id 'category_id3',c3.category_name 'category_name3',c3.category_level 'category_level3',c3.parent_id 'parent_id3'
      fromcategory c1
      INNER JOINcategory c2
      on c2.parent_id = c1.category_id
      LEFT JOINcategory 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定义操作方法

    @Repository
    public interface CategoryMapper extends GeneralDAO<Category> {// 连接查询public List<CategoryVO> selectAllCategories();// 子查询,根据parentId查询子分类public List<CategoryVO> selectAllCategories2(int parentId);
    }
    
  • 映射配置

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.lzp.fmmall.dao.CategoryMapper"><resultMap id="BaseResultMap" type="com.lzp.fmmall.entity.Category"><!--WARNING - @mbg.generated--><id column="category_id" jdbcType="INTEGER" property="categoryId" /><result column="category_name" jdbcType="VARCHAR" property="categoryName" /><result column="category_level" jdbcType="INTEGER" property="categoryLevel" /><result column="parent_id" jdbcType="INTEGER" property="parentId" /><result column="category_icon" jdbcType="VARCHAR" property="categoryIcon" /><result column="category_slogan" jdbcType="VARCHAR" property="categorySlogan" /><result column="category_pic" jdbcType="VARCHAR" property="categoryPic" /><result column="category_bg_color" jdbcType="VARCHAR" property="categoryBgColor" /></resultMap><!-- 连接查询弊端:只能查询指定层级,不利于扩展--><resultMap id="categoryVOMap" type="com.lzp.fmmall.entity.CategoryVO"><!--WARNING - @mbg.generated--><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.lzp.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.lzp.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">selectc1.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'fromcategory c1INNER JOINcategory c2onc2.parent_id = c1.category_idLEFT JOINcategory c3onc3.parent_id = c2.category_idwherec1.category_level = 1</select><!-------------------------------------------><!-- 子查询优势:无论有多少层级,都能查询的到 --><resultMap id="categoryVOMap2" type="com.lzp.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.lzp.fmmall.dao.CategoryMapper.selectAllCategories2"/></resultMap><!-- 根据父级分类的id查询子级分类  --><select id="selectAllCategories2" resultMap="categoryVOMap2">selectcategory_id,category_name,category_level,parent_id,category_icon,category_slogan,category_pic,category_bg_colorfromcategorywhereparent_id = #{parentId}</select> 
    </mapper>
    

9.2.2 业务层实现

  • CategoryService接口

    package com.lzp.fmmall.service;import com.lzp.fmmall.vo.ResultVO;public interface CategoryService {public ResultVO listCategories();}
    
  • CategoryServiceImpl

    @Service
    public class CategoryServiceImpl implements CategoryService {@Autowiredprivate CategoryMapper categoryMapper;@Overridepublic ResultVO listCategories() {List<CategoryVO> categoryVOS = categoryMapper.selectAllCategories();ResultVO resultVO = new ResultVO(ResStatus.OK, "success", categoryVOS);return resultVO;}
    }
    

9.2.3 控制层实现

  • IndexController

    @Autowired
    private CategoryService categoryService;@GetMapping("/category-list")
    @ApiOperation("商品分类查询接口")
    public ResultVO listCategory() {return categoryService.listCategories();
    }
    

9.3 前端功能实现

见源码即可

十、首页—商品推荐

10.1 流程分析

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Sz4M3ea3-1646129708220)(C:\Users\14715\AppData\Roaming\Typora\typora-user-images\image-20210807185246390.png)]

10.2 接口开发

10.2.1 数据库实现

推荐算法:推荐最新上架的商品

说明:商品推荐算法是根据多个维度进行权重计算,计算出一个匹配值

  • 数据表分析及数据准备

  • sql

    # 商品推荐,查询最新上架的商品
    select * from product ORDER BY create_time DESC LIMIT 0, 3;
    # 子查询 根据商品id查询商品图片
    select * from product_img WHERE item_id = 1;
    
  • 在beans子工程entity包创建ProductVO,并在其中添加一个字段imgs,用来存储商品的图片集合

    public class ProductVO [private List<ProductImg> imgs;public List<ProductImg> getImgs() { return imgs;}public void setImgs(List<ProductImg> imgs) {this.imgs = imgs;}
    ]
    

10.2.2 DAO

  • ProductMapper

    @Repository
    public interface ProductMapper extends GeneralDAO<Product> {// 查询推荐商品/*推荐算法:1、根据用户最近搜索商品进行查询2、根据商品销量进行查询3、根据后台管理员指定4、根据最新上架商品进行查询*/public List<ProductVO> selectRecommendProducts();}
    
  • ProductMapper映射文件

    <resultMap id="ProductVOMap" type="com.lzp.fmmall.entity.ProductVO"><!--WARNING - @mbg.generated--><id column="product_id" jdbcType="VARCHAR" property="productId" /><result column="product_name" jdbcType="VARCHAR" property="productName" /><result column="category_id" jdbcType="INTEGER" property="categoryId" /><result column="root_category_id" jdbcType="INTEGER" property="rootCategoryId" /><result column="sold_num" jdbcType="INTEGER" property="soldNum" /><result column="product_status" jdbcType="INTEGER" property="productStatus" /><result column="create_time" jdbcType="TIMESTAMP" property="createTime" /><result column="update_time" jdbcType="TIMESTAMP" property="updateTime" /><result column="content" jdbcType="LONGVARCHAR" property="content" /><collection property="imgs" column="product_id" select="com.lzp.fmmall.dao.ProductImgMapper.selectProductImgsByProductId"/></resultMap><select id="selectRecommendProducts" resultMap="ProductVOMap">SELECTproduct_id,product_name,category_id,root_category_id,sold_num,product_status,content,create_time,update_timefromproductORDER BYcreate_time DESCLIMIT 0, 3</select>
    
  • ProductImgMapper

    public interface ProductImgMapper extends GeneralDAO<ProductImg> {// 根据商品id查询商品对应的图片集合public List<ProductImg> selectProductImgsByProductId();}
    
  • ProductImgMapper映射文件

    <!-- resultMap用逆向工程自动生成的 -->
    <select id="selectProductImgsByProductId" resultMap="BaseResultMap">selectid,item_id,url,sort,is_main,created_time,updated_timeFROMproduct_imgWHEREitem_id = #{producId};</select>
    

10.2.3 业务层

  • ProductService

    public interface ProductService {public ResultVO listRecommendProducts();}
    
  • ProductServiceImpl

    @Service
    public class ProductServiceImpl implements ProductService {@Autowiredprivate ProductMapper productMapper;@Overridepublic ResultVO listRecommendProducts() {List<ProductVO> productVOS = productMapper.selectRecommendProducts();ResultVO resultVO = new ResultVO(ResStatus.OK, "success", productVOS);return resultVO;}
    }

10.2.4 控制层

直接在IndexController中编写接口

@Autowired
private ProductService productService;@GetMapping("/recommend-products")
@ApiOperation("商品推荐查询接口")
public ResultVO listRecommendProducts() {return productService.listRecommendProducts();
}

10.3 前端实现

见源码即可

十一、首页—分类商品推荐

按照商品的分类推荐销量最高的6个商品

11.1 流程分析

加载分类商品推荐有两种实现方案:

方案一:当加载首页面时不加载分类的推荐商品,监听进度条滚动事件,当进度条触底(滚动指定的距离)就触发分类推荐商品的加载,每次只加载一个分类的商品。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cO0Am8Xh-1646129708222)(C:\Users\14715\AppData\Roaming\Typora\typora-user-images\image-20210808101008256.png)]

方案二:一次性加载所有分类的推荐商品,整体进行初始化。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NUTT0o7K-1646129708223)(C:\Users\14715\AppData\Roaming\Typora\typora-user-images\image-20210808101145780.png)]

11.2 接口开发

11.2.1 数据库实现

  • 数据准备

    -- 添加商品
    
  • 查询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;
    
  • 实体类:

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class CategoryVO {private Integer categoryId;private String categoryName;private Integer categoryLevel;private Integer parentId;private String categoryIcon;private String categorySlogan;private String categoryPic;private String categoryBgColor;// 实现首页类别显示private List<CategoryVO> categories;// 实现首页分类商品推荐private List<ProductVO> products;}
    
  • Mapper

    CategoryMapper
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-75vcPIzf-1646129708224)(C:\Users\14715\AppData\Roaming\Typora\typora-user-images\image-20210808154021063.png)]
    ProductMapper
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-obOy3V5k-1646129708225)(C:\Users\14715\AppData\Roaming\Typora\typora-user-images\image-20210808154622563.png)]
  • 映射文件

    ProductMapper.xml
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-J9slphuB-1646129708226)(C:\Users\14715\AppData\Roaming\Typora\typora-user-images\image-20210808155521505.png)]
    CategoryMapper.xml
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vFNWizyD-1646129708227)(C:\Users\14715\AppData\Roaming\Typora\typora-user-images\image-20210808161114825.png)]

11.2.2 业务层实现

  • CategoryService接口

    public interface CategoryService {public ResultVO listFirstLevelCategories();}
    
  • CategoryServiceImpl实现类

    /*** 查询所有一级分类,同时查询当前分类下销量最高的6个商品* @return*/
    @Override
    public ResultVO listFirstLevelCategories() {List<CategoryVO> categoryVOS = categoryMapper.selectFirstLevelCategories();ResultVO resultVO = new ResultVO(ResStatus.OK, "success", categoryVOS);return resultVO;
    }
    

11.2.3 控制层实现

11.3 前端实现

十二、商品详情展示—显示商品基本信息

点击首页推荐的商品、轮播图商品广告、商品列表页面点击商品,就会进入到商品的详情页面

12.1 流程分析

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-avWUWB8R-1646129708228)(C:\Users\14715\AppData\Roaming\Typora\typora-user-images\image-20210809085810869.png)]

12.2 接口实现

12.2.1 商品详情接口

商品基本信息、商品套餐、商品图片

  • 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完成

  • 业务层实现

    @Transactional(propagation = Propagation.SUPPORTS)
    @Override
    public ResultVO getProductBasicInfo(String productId) {// 1、商品基本信息Example example = new Example(Product.class);Example.Criteria criteria = example.createCriteria();criteria.andEqualTo("productId", productId);criteria.andEqualTo("productStatus", 1); // 状态为1表示上架商品List<Product> products = productMapper.selectByExample(example);if (products.size() > 0) {// 2、商品图片Example example1 = new Example(ProductImg.class);Example.Criteria criteria1 = example1.createCriteria();criteria1.andEqualTo("itemId", productId);List<ProductImg> productImgs = productImgMapper.selectByExample(example1);// 3、商品套餐Example example2 = new Example(ProductSku.class);Example.Criteria criteria2 = example2.createCriteria();criteria2.andEqualTo("productId", productId);criteria2.andEqualTo("status", 1);List<ProductSku> productSkus = productSkuMapper.selectByExample(example2);Map<String, Object> basicInfo = new HashMap<>(3);basicInfo.put("product", products.get(0));basicInfo.put("productImgs", productImgs);basicInfo.put("productSkus", productSkus);return new ResultVO(ResStatus.OK, "success", basicInfo);} else {return new ResultVO(ResStatus.NO, "查询的商品不存在!", null);}
    }
    
  • 控制层实现

    @RestController
    @CrossOrigin
    @RequestMapping("/product")
    @Api(value = "提供商品信息相关的接口", tags = "商品管理")
    public class ProductController {@Autowiredprivate ProductService productService;@ApiOperation("商品基本信息查询接口")@GetMapping("/detail-info/{pid}")public ResultVO getProductBasicInfo(@PathVariable("pid") String pid) {return productService.getProductBasicInfo(pid);}}
    

12.3 前端实现

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

  • 业务层实现

    package com.lzp.fmmall.service.impl;import com.lzp.fmmall.dao.ProductParamsMapper;
    import com.lzp.fmmall.entity.ProductParams;
    import com.lzp.fmmall.service.ProductParamsService;
    import com.lzp.fmmall.vo.ResStatus;
    import com.lzp.fmmall.vo.ResultVO;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import tk.mybatis.mapper.entity.Example;import java.util.List;/*** @Author LZP* @Date 2021/8/10 9:43* @Version 1.0*/
    @Service
    public class ProductParamsServiceImpl implements ProductParamsService {@Autowiredprivate ProductParamsMapper productParamsMapper;@Overridepublic ResultVO getProductParamsInfo(String productId) {Example example = new Example(ProductParams.class);Example.Criteria criteria = example.createCriteria();criteria.andEqualTo("productId", productId);List<ProductParams> productParams = productParamsMapper.selectByExample(example);if (productParams.size() > 0) {return new ResultVO(ResStatus.OK, "success", productParams.get(0));} else {return new ResultVO(ResStatus.NO, "该商品可能是三无产品!", null);}}
    }
    
  • 控制层实现

    @Autowired
    private ProductParamsService productParamsService;@ApiOperation("商品参数信息查询接口")
    @GetMapping("/detail-params/{pid}")
    public ResultVO getProductParamsInfo(@PathVariable("pid") String pid) {return productParamsService.getProductParamsInfo(pid);
    }
    
  • 前端实现

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

  • 数据库ti添加数据

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-M24UdK8z-1646129708229)(C:\Users\14715\AppData\Roaming\Typora\typora-user-images\image-20210810142534399.png)]

  • sql

    # 联表查询 评论表和用户表
    SELECTu.user_id,u.username,u.nickname,u.user_img,c.comm_id,c.product_id,c.product_name,c.order_item_id,c.is_anonymous,c.comm_type,c.comm_level,c.comm_content,c.comm_imgs,c.sepc_name,c.reply_status,c.reply_content,c.reply_time,c.is_show
    FROM users u
    INNER JOINproduct_comments c
    ON	u.user_id = c.user_id
    where c.product_id = 3 and c.is_show = 1
    
  • 业务层实现

    • 接口
    package com.lzp.fmmall.service;import com.lzp.fmmall.vo.ResultVO;public interface ProductCommentsService {public ResultVO getProductComments(String productId);}
    • 实现类
    package com.lzp.fmmall.service.impl;import com.lzp.fmmall.dao.ProductCommentsMapper;
    import com.lzp.fmmall.entity.ProductCommentsVO;
    import com.lzp.fmmall.service.ProductCommentsService;
    import com.lzp.fmmall.vo.ResStatus;
    import com.lzp.fmmall.vo.ResultVO;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import tk.mybatis.mapper.entity.Example;import java.util.List;/*** @Author LZP* @Date 2021/8/10 14:34* @Version 1.0*/
    @Service
    public class ProductCommentsServiceImpl implements ProductCommentsService {@Autowiredprivate ProductCommentsMapper productCommentsMapper;@Overridepublic ResultVO getProductComments(String productId) {List<ProductCommentsVO> productCommentsVOS = productCommentsMapper.selectProductComments(productId);return new ResultVO(ResStatus.OK, "success", productCommentsVOS);}
    }
  • 控制层实现

    @Autowired
    private ProductCommentsService productCommentsService;@ApiOperation("商品评论信息查询接口")
    @GetMapping("/detail-comments/{pid}")
    public ResultVO getProductCommentsInfo(@PathVariable("pid") String pid) {return productCommentsService.getProductComments(pid);
    }
    
  • 前端实现

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

15.1 流程分析

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EVUKXUyO-1646129708231)(C:\Users\14715\AppData\Roaming\Typora\typora-user-images\image-20210810165054971.png)]

15.2 接口开发

15.2.1 改成商品评论列表接口

分页查询

  • 定义PageHelper

    package com.lzp.fmmall.utils;import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;import java.util.List;/*** @Author LZP* @Date 2021/8/10 16:52* @Version 1.0*/
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class PageHelper<T> {// 总记录数private int count;// 总页数private int pageCount;// 分页数据private List<T> list;}
    
  • 改造数据库操作

    ProductCommentsMapper
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dX7QsmYS-1646129708232)(C:\Users\14715\AppData\Roaming\Typora\typora-user-images\image-20210810171945565.png)]
    ProductCommentsMapper.xml映射配置文件
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9cpbAYBA-1646129708233)(C:\Users\14715\AppData\Roaming\Typora\typora-user-images\image-20210810172044122.png)]
  • 改造业务逻辑层

    ProductCommentsService
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jeX79mNp-1646129708233)(C:\Users\14715\AppData\Roaming\Typora\typora-user-images\image-20210810172734418.png)]
    ProductCommentsServiceImpl
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3Ei95w3e-1646129708235)(C:\Users\14715\AppData\Roaming\Typora\typora-user-images\image-20210810172828944.png)]

15.2.2 评价统计接口实现

  • 数据库实现

    • 统计当前商品的总评论数
    • 统计当前商品的好评/中评/差评
  • 业务层

    • 接口

      /*** 根据商品id查询当前商品的总评论数* @param productId* @return*/
      public ResultVO getCommentsCountByProductId(String productId);
      
    • 实现类

      @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、查询好评数Example example1 = new Example(ProductComments.class);Example.Criteria criteria1 = example1.createCriteria();criteria1.andEqualTo("productId", productId);criteria1.andEqualTo("commType", 1);int goodTotal = productCommentsMapper.selectCountByExample(example1);// 3、查询中评数Example example2 = new Example(ProductComments.class);Example.Criteria criteria2 = example2.createCriteria();criteria2.andEqualTo("productId", productId);criteria2.andEqualTo("commType", 0);int midTotal = productCommentsMapper.selectCountByExample(example2);// 4、查询差评数Example example3 = new Example(ProductComments.class);Example.Criteria criteria3 = example3.createCriteria();criteria3.andEqualTo("productId", productId);criteria3.andEqualTo("commType", -1);int badTotal = productCommentsMapper.selectCountByExample(example3);// 5、计算好评率String p = String.valueOf(Double.parseDouble(String.valueOf(goodTotal)) / Double.parseDouble(String.valueOf(total)) * 100);// 小数点后保留两位小数String percent = p.substring(0, p.lastIndexOf(".") + 3);Map<String, Object> map = new HashMap<>(5);map.put("total", total);map.put("goodTotal", goodTotal);map.put("midTotal", midTotal);map.put("badTotal", badTotal);map.put("percent", percent);return new ResultVO(ResStatus.OK, "success", map);
      }
      
  • 控制层

    @Autowired
    private ProductCommentsService productCommentsService;@ApiOperation("商品评论统计信息查询接口")
    @GetMapping("/detail-comments-count/{pid}")
    public ResultVO getCommentsCountByProductId(@PathVariable("pid") String pid) {return productCommentsService.getCommentsCountByProductId(pid);
    }
    

15.3 前端实现

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

十七、购物车—添加购物车(未登录状态)

17.1 流程分析

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7iKj9Fpq-1646129708236)(C:\Users\14715\AppData\Roaming\Typora\typora-user-images\image-20210811141800270.png)]

17.2 接口实现

17.2.1 修改购物车数据表结构

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gYOv0lV6-1646129708237)(C:\Users\14715\AppData\Roaming\Typora\typora-user-images\image-20210811141909373.png)]

  • 数据表修改完成之后,对此表重新进行逆向工程,这里要将逆向工程的配置文件里面生成表的策略里的’%'改成具体的表名,否则会对其他表造成影响

17.2.2 业务层

  • 定义一个购物车业务类ShoppingCarService接口

    public interface ShoppingCarService {public ResultVO addShoppingCar(ShoppingCar car);}
    
  • 实现类ShoppingCarServiceImpl

    @Service
    public class ShoppingCarServiceImpl implements ShoppingCarService {@Autowiredprivate ShoppingCarMapper shoppingCarMapper;@Overridepublic ResultVO addShoppingCar(ShoppingCar car) {int i = shoppingCarMapper.insert(car);if (i > 0) {return new ResultVO(ResStatus.OK, "success", null);} else {return new ResultVO(ResStatus.NO, "fail", null);}}
    }
    

17.3 前端展示

introduction.html

addShoppingCar() {/*注意:这里受限资源需要判断当前用户是否登录,而判断是否登录不是简简单单的判断以下token是否为null或者空字符串""就行的因为我们此时用的是jwt,是后台自动生成的token,只有后台知道当前的token是否有效,(token怎样才算有效:具有正确性、时效性)正因如此所以要通过后台去完成校验*/var url5 = baseUrl + "shopcar/add";// 获取从cookie中获取token和userIdvar userId = getCookieValue("userId");// 拼接选中的套餐属性var chooseSkuPropStr = "";for (var key in this.chooseSkuProps) {chooseSkuPropStr += key + ":" +  this.chooseSkuProps[key] + ";";}// 发送异步请求axios({url: url5,method: "POST",headers: {token: this.token},data: {"carNum": this.num,"carTime": "","productId": this.productId,"productPrice": this.productSkus[this.currentSkuIndex].sellPrice,"skuId": this.productSkus[this.currentSkuIndex].skuId,"skuProps": chooseSkuPropStr,"userId": userId}}).then(res => {var vo = res.data;if (vo.code == 10000) {// 添加成功layer.msg("添加购物车成功!");} else if (vo.code == 10001) {// 添加失败layer.msg("添加购物车失败!");} else {var tipStr = "";if (vo.code == 20001) {// 没有登录tipStr = "请登录";} else if (vo.code == 20002) {// 登录过期tipStr = "登录过期,请重新登录";}// 跳转到登录页,而且要带参数跳/*参数如下:1、tip(提示信息)2、returnUrl(回跳url)3、pid(商品id)4、currentSkuIndex(当前选中的套餐下标)5、num(当前商品下当前套餐的数量)*/var loginUrl = "login.html?tip=" + tipStr+ "&returnUrl=introduction.html"+ "&pid=" + this.productId+ "&sid=" + this.currentSkuIndex+ "&num=" + this.num;// 对url进行编码window.location.href = encodeURI(loginUrl);}});
}

这里我们使用了一个类似京东的效果,就是用户在没有登录之前是可以查看商品详情的,但是如果想要添加购物车等访问这些一系列受限资源时,需要去判断当前用户是否登录,若已登录,则可以直接添加,反之,则需要跳到登录页面;而这里我们跳到登录页面时传递了一个returnUrl参数,目的就是方便用户登录成功后再回跳到上一次访问的页面,这样一来给用户的体验感会比较好。跳转所要传递的参数如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Fl5k2Rrp-1646129708238)(C:\Users\14715\AppData\Roaming\Typora\typora-user-images\image-20210822092649654.png)]

login.html

doSubmit: function () {// 特判:防止用户在浏览器保存了登录信息,即此时一打开登录页面账号和密码就已经存在this.checkLoginInfo();if (!vm.isRight) {// 校验不通过,给出提示信息vm.tip = "校验失败:" + vm.tip;} else {var url = baseUrl + "user/login";// 验证通过,提交信息到后端// 通过axios发送异步请求axios({method: "GET",url: url,params: {username: vm.username,password: vm.password}}).then(res => {// 校验后台是否登录成功console.log(res);var vo = res.data;if (vo.code == 20000) {// 如果登录成功,就把token存储到cookiesetCookieValue("token", vo.msg);// 将用户id、用户名和用户头像存入cookie中setCookieValue("userId", vo.data.userId);setCookieValue("username", vo.data.username);setCookieValue("userImg", vo.data.userImg);// 先判断,returnUrl是否为nullif (this.returnUrl == null) {// 跳转到index页面window.location.href = "index.html";} else {// 回到returnUrl指定的页面var url = this.returnUrl + "?pid=" + this.pid+ "&sid=" + this.sid+ "&num=" + this.num;window.location.href = url;}} else {// 后台登录失败,给出提示信息vm.tip = vo.msg;}});}
}

登录成功之后的逻辑我们也进行了相应的修改,如果当前页面获取到的returnUrl不为null,即表示用户此时是从某个受限资源页面跳转过来的,等登录成功后必须回跳到之前的页面,反之,就跳到index.html(主页)即可。

修改后的跳转逻辑代码如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YDlAlvoL-1646129708239)(C:\Users\14715\AppData\Roaming\Typora\typora-user-images\image-20210822093140884.png)]

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

18.1 流程分析

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-akMLyoDA-1646129708240)(C:\Users\14715\AppData\Roaming\Typora\typora-user-images\image-20210822093808979.png)]

18.2 接口实现


http://www.ppmy.cn/news/658075.html

相关文章

风云录音机隐私政策

风云录音机隐私政策 开发者&#xff1a;大连世纪成交科技服务有限公司 发布日期&#xff1a;2021年9月27日 更新日期&#xff1a;2021年9月27日 生效日期&#xff1a;2021年9月27日 本软件尊重并保护所有使用服务用户的个人隐私权。除本隐私权政策另有规定外&#xff0c;在未征…

[风云人物]风云(中国第一本企业家自传)

http://www.tianya.cn/publicforum/content/enterprise/1/8487.shtml

《传智播客全方位打造“1024程序员节”,致敬百万程序员》

2017年是传智播客创办1024程序员节的第三年&#xff0c;希望以节日的形式&#xff0c;向那些用技术和创新改变世界的程序员们致敬&#xff0c;同时提升程序员群体的社会关注度。 今年的节日玩法更为丰富&#xff0c;线上线下遥相呼应&#xff0c;并联合了摩拜共享单车、外设品…

开源风云 20 年!

作者 | 顾钧 责编 | 胡巍巍 出品 | CSDN&#xff08;ID&#xff1a;CSDNnews&#xff09; 虽然已经 2019 年&#xff0c;但还是有不少人&#xff08;甚至是大部分人&#xff09;&#xff0c;一提到“开源”&#xff0c;想到的就是“免费”与“个人开发的业余&#xff08;低水平…

YOLOv5改进系列(12)——更换Neck之BiFPN

【YOLOv5改进系列】前期回顾: YOLOv5改进系列(0)——重要性能指标与训练结果评价及分析 YOLOv5改进系列(1)——添加SE注意力机制

Linux内核的编译、安装、调试

这里写目录标题 编译安装内核下载内核安装依赖更改.config编译内核安装首先安装模块安装内核更改引导更改grub重启 其他操作清理内核源目录卸载安装的内核修改内核配置菜单实现对新加入内核源码的控制 常见问题1. Module.symvers is missing2. No rule to make target ‘debian…

照片做成视频

找寻了挺久&#xff0c;pc端的照片做成视频软件最后选择了【数码大师】&#xff0c;里面包含加载所有照片、设置每张照片过度的动效、设置背景音乐&#xff0c;最后导出文件。

如何将小视频制作成动态图片

制作动态图片&#xff1a; 1.首先我们要先准备一个视频&#xff0c;并记住视频的总时长。 2.打开 http://ezgif.com/ 如图所示&#xff1a; 3.点击 Video to GIF&#xff0c;进入如下页面&#xff1a; 4.点击浏览&#xff0c;并选择视频的路径&#xff0c;然后点击Upload 5.对图…