学习锋迷商城的的笔记

文章目录

    • 锋迷商城项目
      • 1.通过Maven聚合工程搭建项目:
        • 1. 创建一个Maven的父项目,然后修改它的pom.xml文件,可以删除src等一些没有用的目录
        • 2.在父项目下面创建多个module,包括(common,beans,mapper,service,api)把它们全部打包成jar包
      • 3.由于mapper层需要调用beans层(pojo),需要在pom.xml文件中,然后可以在mapper中加入相关的依赖。
      • 4.创建service需要调用mapper,和common,需要在pom.xml文件中加上
      • 5.api层需要接收前端的请求所以需要我们创建一个SpringBoot工程,你可以创建一个Maven工程,然后加入相关的依赖
      • 6.api子工程,对外提供接口
    • 锋迷商城数据库设计
      • 2.软件开发步骤
      • 3.数据库设计流程
      • 3.数据建模工具PDMan
        • Spu和Sku的区别
      • 4.锋城业务流程设计
        • 4.1用户管理9业务流程分析
      • 5接口介绍
        • 5.1接口规范
      • 5.2Swagger(自动生成服务器接口的规范性文档)
        • 5.2.1引入相关的依赖:
        • 5.2.2 创建相关的配置类
        • 5.2.3根据你的配置的端口号进行相关的测试
        • 5.2.4 swagger提供了一套注解对每个接口进行详细的说明
        • 5.2.5swagger-ui插件使用
          • 1.api的module加入依赖
          • 2.进行访问,然后可以使用它进行相关的测试
      • 一、锋迷商城设计及实现用户管理
        • 1.UserDao接口的创建:
        • 2.UserMapper.xml
        • 3.UserService
        • 4.UserServiceimpl:
        • 5.api模块的UserController:
        • 6.ResultVO一个和前端进行数据交互的类
        • 7.在common模块的MD5Utils类:
      • 二、逆向工程
        • 7.1逆向工程配置
        • 7.2在pom.xml文件中指定generatorConfig.xml文件的路径
      • 三、跨域问题
      • 四、前端页面的传值
        • 4.1Cookie使用(自定义封装一个js,cookie_utils.js)
        • 4.2localStorage
        • 4.3Vue实现登录
      • 五、前后端分离开发用户认证的问题
        • 5.1单体项目中:
        • 5.2前后端分离项目中
        • 基于token认证的用户代码实现
      • 六、JWT(json Web Token)一个别人封装好的工具类生成相关的token
      • 6.1生成JWT
        • 6.2使用拦截器进行拦截
      • 6.3使用请求头进行传递token
        • axios通过请求头传值 里面的参数用Headers 不用Params
        • 6.3.1 **CheckTokenInterceptor类** 前端会发送预险性请求(只有它通过以后才可以进行第二次请求),需要拦截器进行放行
      • 七首页的分类列表的的实现
        • 7.1接口实现
        • 7.2业务层实现
        • 控制层实现
      • 八、商品的推荐功能实现
        • 8.1 流程分析
        • 8.2接口开发
          • 8.2.1数据库的实现
        • ProductMapper文件:
        • ProductImgMapper文件:
        • ProductMapper.xml文件实现
      • 8.2.2业务层实现
        • 8.2.3控制层实现
      • 九、商品详情显示(在Introduction.html进行相关的显示)
        • 9.1接口实现
        • 9.1.1 商品详情接口
      • 十、显示商品评价的信息(通过用户和商品评论表进行相关的连表查询)
        • 10.1 新建的VO,ProductCommentsVO (一对一的连表查询可以不用在实体类中声明另一个实体类)
    • 十一、添加购物车的功能实现
        • 10.1流程分析:
        • 10.2数据库的相关的操作
        • 10.2.1 购买的数量的前端实现在vue的methods中添加+和-的点击事件
        • 10.2.2给加入购物车这个按钮添加点击事件
    • 十二、添加购物车时候用户未登录
        • 12.1 添加购物车用户未登录,业务处理方式:
        • 12.2我们使用第三种难度最大的来
        • 12.3使用Layui进行添加购物车成功/失败的提示
          • 12.3.1声明layui的弹窗组件
        • 12.3.2成功失败进行提示
    • 十三购物车的列表
        • 13.1数据库dao接口的实现
        • 13.2pojo接口实现
        • 13.3修改购物车
          • 13.31通过这个进行购物车数量的修改:
          • 13.32changNum函数进行实现:
    • 十四购物车提交订单结算功能实现
      • 14.1实现流程分析
    • 十五、订单提交及支付流程
      • 15.1流程分析
      • 15.2订单添加接口实现
      • 15.3数据库操作
      • 15.4serviceimpl层实现 注意:这个方法需要加上@Transactional,也就是订单生成的时候,快照也必须生成
    • swagger报错解决
    • 十六 商品分类信息的查询
      • 16.1 流程分析
      • 16.2 接口开发
        • 16.2.1根据类别查询商品接口

锋迷商城项目

使用Maven聚合项目进行创建(一个maven的父项目多个maven的子项目),

可以在父项目pom.xml文件中加上:

<package>pom<package>

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

1.通过Maven聚合工程搭建项目:

1. 创建一个Maven的父项目,然后修改它的pom.xml文件,可以删除src等一些没有用的目录

<packaging>pom<packaing>

2.在父项目下面创建多个module,包括(common,beans,mapper,service,api)把它们全部打包成jar包

pom.xml加上

<packaging>jarpackaging>

3.由于mapper层需要调用beans层(pojo),需要在pom.xml文件中,然后可以在mapper中加入相关的依赖。

   <dependencies>


        <dependency>
            <groupId>org.examplegroupId>
            <artifactId>beansartifactId>
            <version>2.0.1version>
        dependency>
    dependencies>

4.创建service需要调用mapper,和common,需要在pom.xml文件中加上

 <dependency>
            <groupId>org.examplegroupId>
            <artifactId>mapperartifactId>
            <version>2.0.1version>
        dependency>
        <dependency>
            <groupId>org.examplegroupId>
            <artifactId>commonartifactId>
            <version>2.0.1version>
        dependency>

5.api层需要接收前端的请求所以需要我们创建一个SpringBoot工程,你可以创建一个Maven工程,然后加入相关的依赖

6.api子工程,对外提供接口

总的说父项目的所以依赖可以被子项目引用,子项目也可以单独的添加所需的依赖

锋迷商城数据库设计

2.软件开发步骤

  • 提出问题

  • 可行性分析(技术(一般可以相关人员实现),成本,法律法规)

  • 概要设计

    • 系统设计(技术选型,架构模式)
    • 数据库设计
    • UI设计
    • 业务流程设计
  • 详细设计

    • 实现步骤(业务流程的实现细节)
  • 编码

    • 根据设计好的实现步骤进行代码实现
    • 开发过程使用单元测试
  • 测试

    • 集成测试
    • 功能测试(墨盒)
    • 性能测试(白盒)高并发,压力测试
  • 交付/部署实施

    3.数据库设计流程

  • 根据功能分析出数据库实体(javaBean)

    • 商品,订单,购物车,用户,地址…
  • 提取实体属性

    • spu商品(id,商品名称,商品图片,商品描述…)

    • 1 min10 … …

    • sku(skuId, 参数 , 价格 商品id

    • 101 内存8G\存储128G 2999 1

    • 102 内存12G\存储256G 3999 1

    • 地址(姓名,地址,电话…)

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

可以知道价格的依赖于参数的改变而改变,参数依赖于id改变,不满足数据库设计表的要求,可以设计两张表进行实现。

  • 使用数据库的第三范式进行检查数据项是否合理
  • 分析实体关系图:E-R图 (一对一,一对多)
  • 数据库建模(三线图)建模工具(PdMan)
  • 建库建表-sql

3.数据建模工具PDMan

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

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

  • 导出sql指令(模型—导出DDL脚本

  • 记录数据库模型版本管理

  • 可以连接数据库直接生成表

    Spu和Sku的区别

  • spu(Standard Product Unit):商品信息聚合的最小 单位。通俗讲属性值,特性相同的商品可以称为一个SPU.

    产品: 荣耀8 小米10

  • sku(Stock Keeping Unit)最小存货单元,定义为保存最小库存的控制最小可用单元

    sku 荣耀8 8G/128G 10

    sku 荣耀8 4G/124G 20

    注意一下 :订单表的设计功能:因为订单表只要用户一下订单,订单表的相关信息就不可以进行改变 ,所以需要进行地址的的快照和商品信息的快照,这样就算你临时改变了价格的信息或者其他的也没有关系

    购物车的设计:

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

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

4.锋城业务流程设计

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

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

每个人会被分配不同的功能

4.1用户管理9业务流程分析

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

前后端分离架构:前端和后端开发开发和部署,前端只能通过异步发送请求,后端只负责接收请求及参数,处理请求,返回结果

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

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

**前端可以发送如图所示的请求:**需要url,params

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

5接口介绍

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

标准定义:API(Application Programming interface)应用程序编程接口,就是软件系统不同组成部分衔接的约定。

5.1接口规范

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

5.2Swagger(自动生成服务器接口的规范性文档)

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

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

  • swagger作用
  • 生成接口规范性文档
  • 生成接口测试工具

5.2.1引入相关的依赖:


        <dependency>
            <groupId>io.springfoxgroupId>
            <artifactId>springfox-swagger2artifactId>
            <version>2.9.2version>
        dependency>

        <dependency>
            <groupId>io.springfoxgroupId>
            <artifactId>springfox-swagger-uiartifactId>
            <version>2.9.2version>
        dependency>


5.2.2 创建相关的配置类

可以在api这个module中进行相关的controller层的测试,建立一个config包下面的SwaggerConfig类进行相关的测试,加上@Configuration,@EnableSwagger2注解,然后进行配置相关的信息

package com.qfedu.fmmall.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.w3c.dom.DocumentType;
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;

@Configuration
@EnableSwagger2
public class SwaggerConfig {
     
    /*
    * swagger生成我们的接口文档:
    * 1.需要配置生成文档的信息
    * 2.配置生成规则
    *
    * */
    @Bean
    public Docket docket(){
     

//创建封面信息对象
        ApiInfoBuilder apiInfoBuilder=new ApiInfoBuilder();//指定生成文档中的封面信息:文档标题,作者,版本
        apiInfoBuilder.title("《锋迷商城》后端接口说明")
                .description("此文档详细说明了锋迷商城项目后端接口规范")
                .version("v 2.0.1")
                .contact(new Contact("houge","www.houge.com","houge@hou.com"));


        ApiInfo apiInfo=apiInfoBuilder.build();


        Docket docket=new Docket(DocumentationType.SWAGGER_2) //指定文档风格
                .apiInfo(apiInfo)
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.qfedu.fmmall.controller"))
//                定义了path之后只会为user开头的请求进行扫描 .paths(PathSelectors.regex("/user/"))
//                PathSelectors.any()表示任何的请求
                .paths(PathSelectors.any())
                .build();


        return docket;

    }




}

5.2.3根据你的配置的端口号进行相关的测试

http://localhost:8080/swagger-ui.html

5.2.4 swagger提供了一套注解对每个接口进行详细的说明

@Api(value=" 用户管理",tags="提供用户的登录和注册的接口")//这个接口可以直接放在@Controller注解下面

@ApiOperation 和ApiImplicitParams({ @ApiImplicitParam(dataType="",name=“username”,value="",required=true), @ApiImplictParm}) 这两个注解放在@RequestMapping("/login")请求之上,用来修饰方法和方法中的参数。

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

 @ApiOperation("用户登录的接口")
    @ApiImplicitParams({
     
            @ApiImplicitParam(dataType = "string",name = "username",value = "用户登录的账号",required = true),
            @ApiImplicitParam(dataType = "string",name = "password",value = "用户登录的密码",defaultValue = "111111",required = false)
    })
    @RequestMapping("/login")
//    @RequestParam可以有默认的参数
    public ResultVO login(@RequestParam("username") String name,@RequestParam(value = "password",defaultValue = "111111") String pwd){
     

        return userService.checkLogin(name,pwd);


    }
    @RequestMapping(value = "regist",metho

@ApiModel 和@ApiModelProperty接口参数返回一个对象类型时,需要在实体类中添加注解说明(也就是Beans这个Module进行相关的配置)

package com.qfedu.fmmall.entity;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor

@ApiModel(value = "用户的买家信息",description = "买家的相关的参数")
public class User {
     

    @ApiModelProperty(name = "用户id",required = false,dataType = "int")
    private Integer userId;

    @ApiModelProperty(dataType = "string",name = "买家姓名",required = true)
    private String  userName;
    @ApiModelProperty(dataType = "string",name = "买家密码",required = true)
    private String userPwd;
    @ApiModelProperty(dataType = "string",name = "买家真实姓名",required = true)
    private String userRealname;
    @ApiModelProperty(dataType = "string",name = "用户图片",required = true)
    private String userImg;


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

5.2.5swagger-ui插件使用

1.api的module加入依赖

        
        <dependency>
            <groupId>com.github.xiaoymingroupId>
            <artifactId>swagger-bootstrap-uiartifactId>
            <version>1.9.6version>
        dependency>


2.进行访问,然后可以使用它进行相关的测试

http://ip:port/doc.html

一、锋迷商城设计及实现用户管理

1.UserDao接口的创建:

package com.qfedu.fmmall.dao;

import com.qfedu.fmmall.entity.User;
import org.springframework.stereotype.Repository;

@Repository
public interface UserDao {
     

//    用户注册
    public int insert(User user);

//   根据用户名进行登录的验证
    public User queryByName(String name);


}


2.UserMapper.xml


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">

 <resultMap id="userResultMap" type="User">
  <id column="user_id" property="userId">id>

 <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>
     <result column=" user_birth" property="userBirth">result>
     <result column="user_regtime " property="userRegtime">result>
     <result column="user_modtime " property="userModtime">result>

 resultMap>

    
    <select id="queryByName" resultType="User">

     select *from users where username=#{username}

 select>
    <insert id="insert" parameterType="User">

        insert into users(username,password,user_regtime,user_modtime) values (#{username},
        #{password},#{userRegtime},#{userModtime})
    insert>
    mapper>

3.UserService

package com.qfedu.fmmall.service;

import com.qfedu.fmmall.entity.User;
import com.qfedu.fmmall.vo.ResultVO;

public interface UserService {
     
//    ResultVO是一个响应给前端的自定义的一个类。
    public ResultVO checkLogin(String username, String pwd);

//    用户注册
    public ResultVO insert(String username, String pwd);
}

4.UserServiceimpl:

package com.qfedu.fmmall.service.impl;

import com.qfedu.fmmall.service.UserService;
import com.qfedu.fmmall.dao.UserDao;
import com.qfedu.fmmall.entity.User;
import com.qfedu.fmmall.utils.MD5Utils;
import com.qfedu.fmmall.vo.ResultVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Date;

@Service
@Transactional
//使所有的线程都用这个对象,单例模式默认是开启的
@Scope("singleton")
public class UserServiceimpl implements UserService {
     
    @Autowired
    private UserDao userDao;//可以在UserDao上面加上userDao,这个不会报红,但是没有什么意义
    @Override
    public ResultVO checkLogin(String username, String pwd) {
     
//        查询用户名
        User user = userDao.queryByName(username);
        if(user==null){
     
//            用户名不正确

            return new ResultVO(10001,"用户名不正确",null);



        }else {
     
            //密码使用MD5进行加密
            String md5Pwd = MD5Utils.md5(pwd);

            if(md5Pwd.equals(user.getPassword())){
     
//          验证成功
                return  new ResultVO(200,"登录成功",user);
            }else {
     
                //密码不正确
                return  new ResultVO(10001,"密码错误",null);

            }


        }



    }
    @Transactional
    @Override
    public ResultVO insert(String username, String pwd) {
     
//        判断这个用户是否被注册

//        加上这个锁可以使用所有的注册都用这个userServiceimpl
        synchronized (this){
     
//            把密码进行MD5的加密
            String password = MD5Utils.md5(pwd);

            User user1 = userDao.queryByName(username);
//表示用户名没有被注册过,可以进行注册
            if (user1==null){
     
//一个是注册时间,regtime,一个是修改时间modtime
                User user=new User(username,password,new Date(),new Date());
                int i = userDao.insert(user);
                if(i>0){
     
                    return new ResultVO(1000,"注册成功",null);
                }else {
     

                    return new ResultVO(1001,"注册失败",null);

                }


            }
//            判断一下用户名是否已经被注册,然后把数据返回前端,goodjob,Noone can influence you
            else {
     

                return new ResultVO(1001,"用户名已经被注册",null);
            }



        }


    }
}

5.api模块的UserController:

package com.qfedu.fmmall.vo;


import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor

@ApiModel(value = "ResultVO对象",description = "响应封装的数据给前端")
public class ResultVO {
     
//    响应给前端的状态码
    @ApiModelProperty(dataType = "int",value = "响应的状态码")
    private  int code;

//    响应给前端的提示消息
    @ApiModelProperty(dataType = "string",value = "响应的消息")
    private  String msg;
//响应给前端的数据
    @ApiModelProperty(dataType = "object",value = "响应数据的内容")
    private  Object data;
}

6.ResultVO一个和前端进行数据交互的类

package com.qfedu.fmmall.vo;


import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor

@ApiModel(value = "ResultVO对象",description = "响应封装的数据给前端")
public class ResultVO {
     
//    响应给前端的状态码
    @ApiModelProperty(dataType = "int",value = "响应的状态码")
    private  int code;

//    响应给前端的提示消息
    @ApiModelProperty(dataType = "string",value = "响应的消息")
    private  String msg;
//响应给前端的数据
    @ApiModelProperty(dataType = "object",value = "响应数据的内容")
    private  Object data;
}

7.在common模块的MD5Utils类:

package com.qfedu.fmmall.utils;

import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

//MD5 生成器
public class MD5Utils {
     
	public static String md5(String password){
     
		//生成一个md5加密器
		try {
     
			MessageDigest md = MessageDigest.getInstance("MD5");
			//计算MD5 的值
			md.update(password.getBytes());
			//BigInteger 将8位的字符串 转成16位的字符串 得到的字符串形式是哈希码值
			//BigInteger(参数1,参数2) 参数1 是 1为正数 0为0 -1为负数
			return new BigInteger(1, md.digest()).toString(16);
		} catch (NoSuchAlgorithmException e) {
     
			e.printStackTrace();
		}
		return null;
	}
}

二、逆向工程

根据创建好的表,生成实体类,和DAO层、映射文件

在Dependencies下面加入依赖,这个依赖是一个Mybatis的maven插件

<build>
    <plugins>
        <plugin>
            <groupId>org.mybatis.generatorgroupId>
            <artifactId>mybatis-generator-maven-pluginartifactId>
            <version>1.3.5version>
            <configuration>
                <configurationFile>${basedir}/src/main/resources/generator/generatorConfig.xmlconfigurationFile>
            configuration>
            

            <dependencies>
                <dependency>
                    <groupId>mysqlgroupId>
                    <artifactId>mysql-connector-javaartifactId>
                    <version>5.1.46version>
                dependency>
                <dependency>
                    <groupId>tk.mybatisgroupId>
                    <artifactId>mapperartifactId>
                    <version>4.1.5version>
                dependency>


            dependencies>

  plugin>

    plugins>


build>

7.1逆向工程配置

在resources的generator目录下面创建generatorConfig.xml

  1. 需要修改数据库的配置

  2. 需要修改pojo,mapper,Mapper.xml文件生成位置的配置

  3. 
            <table tableName="%">table>
    
  4. 
           <plugin type="tk.mybatis.mapper.generator.MapperPlugin">
               <property name="mappers" value="com.hou.general.GeneralDao"/>
           plugin>
      
    
  5. 指定你的用Configuration generatorConfig.xml文件的路径

  6. 注意一下你的文件一定想要有空格什么东西的


DOCTYPE generatorConfiguration
        PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
        "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">

<generatorConfiguration>
    



    
    <context id="MysqlContext" targetRuntime="MyBatis3Simple" >

        <property name="beginningDelimiter" value="`"/>
        <property name="endingDelimiter" value="`"/>

        
        <plugin type="tk.mybatis.mapper.generator.MapperPlugin">
            <property name="mappers" value="com.qfedu.fmmall.general.GeneralDao"/>
        plugin>

        
        <commentGenerator>
            <property name="suppressDate" value="true"/>
            
            <property name="suppressAllComments" value="true"/>
        commentGenerator>


        
        <jdbcConnection driverClass="com.mysql.jdbc.Driver"
                        connectionURL="jdbc:mysql://localhost:3306/fmmall?characterEncoding=utf8"
                        userId="root"
                        password="roothouzhicong">
        jdbcConnection>

        <javaTypeResolver>
            
            <property name="forceBigDecimals" value="false"/>
        javaTypeResolver>

        
        
        <javaModelGenerator targetPackage="com.qfedu.fmmall.entity" targetProject="src/main/java">
            <property name="enableSubPackages" value="true"/>
            <property name="trimStrings" value="true"/>
        javaModelGenerator>

        
        <sqlMapGenerator targetPackage="/" targetProject="src/main/resources/mappers"/>

        
        <javaClientGenerator targetPackage="com.qfedu.fmmall.dao" targetProject="src/main/java"
                             type="XMLMAPPER"/>

        
        <table tableName="%">table>

    context>
generatorConfiguration>

7.2在pom.xml文件中指定generatorConfig.xml文件的路径

加上了这个:

 <configuration>
                <configurationFile>${basedir}/src/main/resources/generator/generatorConfig.xmlconfigurationFile>
            configuration>
        <plugin>
            <groupId>org.mybatis.generatorgroupId>
            <artifactId>mybatis-generator-maven-pluginartifactId>
            <version>1.3.5version>
            <configuration>
                <configurationFile>${basedir}/src/main/resources/generator/generatorConfig.xmlconfigurationFile>
            configuration>

            <dependencies>
                <dependency>
                    <groupId>mysqlgroupId>
                    <artifactId>mysql-connector-javaartifactId>
                    <version>5.1.46version>
                dependency>
                <dependency>
                    <groupId>tk.mybatisgroupId>
                    <artifactId>mapperartifactId>
                    <version>4.1.5version>
                dependency>


            dependencies>


        plugin>

三、跨域问题

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

解决方案:

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

后端解决办法:在UserController加上@CrossOrigin注解

前端通过Ajax请求跨域登录:

		<form>
							<div class="user-name"  style="margin-top: 20px;">
								<label for="user"><span class="glyphicon glyphicon-user" aria-hidden="true">span>label>
								<input type="text" name="username" id="userName" placeholder="邮箱/手机/用户名">
							div>
							<div class="user-pass"  style="margin-top: 20px;">
								<label for="password"><span class="glyphicon glyphicon-lock" aria-hidden="true">span>label>
								<input type="password" name="" id="userPwd" placeholder="请输入密码">
							div>
						form>




<input type="button" name="" id="submitBtn"  value="登 录" class="am-btn am-btn-primary am-btn-sm">




<script src="static/js/jquery-1.7.2.min.js">script>
<script type="text/javascript">

$("#submitBtn").click(function(){
       
	var name=$("#userName").val();
	var pwd=$('#userPwd').val();
  $.get("http://localhost:8080/user/login",{
       
	  username:name,
	  password:pwd,
  },function(res){
       
	  console.log(res);



  },"json"
 
  
  
  
  )



})

script>





前端使用JSONP设置,后端使用@CrossOrigin注解解决—设置响应头header允许跨域。

debugger;前端 可以加上代码debugger进行相关的调试。可以直接进行前端的校验

四、前端页面的传值

cookie和localstorage可以进行前端的页面之间的传值

Cookie浏览器端的缓存文件:大小受浏览器的限制。

LocalStorage:为了存储更大容量的数据

区别:cookie可以和后台进行传值,localStorage只可以在前端存储值,但是存储的时间长。

4.1Cookie使用(自定义封装一个js,cookie_utils.js)

var opertor="=";

function getCookieValue(keyStr){
	
	
	var s=window.document.cookie;
	var arr=s.split("; ");
for(var i=0;i

A页面设置值:

function(res){
	  console.log(res);
	  if(res.code==1000){
// 获取前端传过来的数据 data
		var userInfo=res.data;
		// cookie和localstorage可以进行前端的页面之间的传值
	setCookieValue("username",userInfo.username);
	setCookieValue("userImg",userInfo.userImg);

		window.location.href="index.html";
	  }else{

		$("#tips").html("<label style='color:red'>"+ res.msg +"label>");




	  }

B页面取值:

var name=getCookieValue("username");
 var userImg=getCookieValue("userImg");
 console.log(name+userImg);

4.2localStorage

A页面:

	localStorage.setItem("user",JSON.stringify(userInfo));

B页面:

var jsonStr=localStorage.getItem("user");

// 把json串转换为对象
var userInfo=eval("("+jsonStr+")");


// 把取到的值消失
localStorage.removeItem("user");
console.log(userInfo);

4.3Vue实现登录

data:{
		username:"",
		password:"",	
		tips:" ",
		colorStyle:"",
		isRight:false,
	


	},
	methods:{
		doSubmit:function() {
			// 校验成功

			if(vm.isRight){
				var url=baseUrl+"/user/login";
				axios.get(url,{	
					params:{
						username:vm.username,password:vm.password

					} }
				
					).then((res)=>{

				console.log(res);

					var vo=res.data;
					if(vo.code==1){
						window.location.href="index.html";
					}else{
						vm.tips="账号或者密码错误";
					}



				});

			}else{
				vm.tips="请输入正确的用户名和密码";
				vm.colorStyle="color:red"
			}

            //  1.进行数据的校验

            if(vm.username==" "){
                vm.tips="请输入用户名";
                vm.colorStyle="color:red";


            }
             
         },
         checkInfo:function(){
            if(vm.username==""){
                vm.tips="请输入用户名";
                this.colorStyle="color:red";
				vm.isRight=false;


            }else if(vm.username.length<6 ||vm.username.length>20){
                vm.tips="账号长度必须为6-20";
                vm.colorStyle="color:red";
				vm.isRight=false;





            }else{
// 校验密码
				if(vm.password==" "){
                vm.tips="请输入密码";
                this.colorStyle="color:red";
				vm.isRight=false;


            }else if(vm.password.length<6 ||vm.password.length>16){
				vm.tips="密码长度为6-16";
                this.colorStyle="color:red";

			}else{
				vm.tips=" ";
				vm.isRight=true;
			}



			}





         }







	}


from表单(用@keyup进行表单的输入的绑定):

	<form>
							<div class="user-name"  style="margin-top: 20px;">
								<label for="user"><span class="glyphicon glyphicon-user" aria-hidden="true">span>label>
								
								<input type="text" name="username" v-model="username" id="userName" @keyup="checkInfo" placeholder="邮箱/手机/用户名">
							div>
							<div class="user-pass"  style="margin-top: 20px;">
								<label for="password"><span class="glyphicon glyphicon-lock" aria-hidden="true">span>label>
								<input type="password" name="" v-model="password" id="userPwd" placeholder="请输入密码"@keyup="checkInfo">
							div>
						form>

五、前后端分离开发用户认证的问题

5.1单体项目中:

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

可以知道每台服务器中存在多个Session,只是id不相同,Cookie中可以存放sessionId,然后判断是是不是同一个session

在单体项目中用户怎么认证的?

在单体项目中视图资源和控制器都在同一台服务器,用户的多次请求老师基于同一个会话,可以基于session进行会话的验证:

  1. 用户登录将信息存放在session中
  2. 根据session中是否有用户信息来判断用户是否可以进行登录。

5.2前后端分离项目中

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

可以知道使用token实现用户验证,token存在于cookie中(同一台服务器可以访问cookie),然后验证token是否正确

基于token认证的用户代码实现

在commons模块中引入

package com.qfedu.fmmall.utils;

import java.util.Base64;

//base64 加密 解密 激活邮件的时候 为 邮箱地址 code验证码 进行加密
//当 回传回来后 进行邮箱地址 和 code 的解密
public class Base64Utils {
     
	//加密
	public static String encode(String msg){
     
		return Base64.getEncoder().encodeToString(msg.getBytes());
	}
	//解密
	public static String decode(String msg){
     
		return new String(Base64.getDecoder().decode(msg));
	}
}

登录成功生成token:UserController

package com.qfedu.fmmall.controller;


import com.qfedu.fmmall.entity.Users;
import com.qfedu.fmmall.service.UserService;
import com.qfedu.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.*;

/*@Controller
@ResponseBody*/

@RestController
@RequestMapping("/user")
@CrossOrigin
@Api(value = "提供用户的登录和注册的接口",tags = "用户管理")
public class UserController {
     

    @Autowired
    private UserService userService;

//    @ApiIgnore加上这个注解会swagger忽略这个方法
    @ApiOperation("用户登录的接口")
    @ApiImplicitParams({
     
            @ApiImplicitParam(dataType = "string",name = "username",value = "用户登录的账号",required = true),
            @ApiImplicitParam(dataType = "string",name = "password",value = "用户登录的密码",required = true)
    })
    @RequestMapping("/login")
//    @RequestParam可以有默认的参数
    public ResultVO login(@RequestParam("username") String name,@RequestParam(value = "password") String pwd){
     

        return userService.checkLogin(name,pwd);


    }

    @ApiOperation(value = "用户注册")
    @PostMapping("/regist")
    @ApiImplicitParams({
     
            @ApiImplicitParam(dataType = "string",name = "username",value = "用户注册的账号",required = true),
            @ApiImplicitParam(dataType = "string",name = "password",value = "用户注册的密码",required = true)
    })
//    前端用user传值,后端可以用users 接收
     public ResultVO register(@RequestBody Users users){
     

        return userService.insert(users.getUsername(),users.getPassword());

    }


}

然后在UserServiceimpl中进行token的加密:

// 如果登录成功,则需要生成令牌token(token就是按照规则生成的字符串)
String token= Base64Util.encode(username+“roothouzhicong”);

package com.qfedu.fmmall.service.impl;

import com.qfedu.fmmall.dao.UsersMapper;
import com.qfedu.fmmall.entity.Users;
import com.qfedu.fmmall.service.UserService;
import com.qfedu.fmmall.utils.MD5Utils;
import com.qfedu.fmmall.vo.ResultStatus;
import com.qfedu.fmmall.vo.ResultVO;
import org.apache.logging.log4j.util.Base64Util;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import tk.mybatis.mapper.entity.Example;

import java.util.Date;
import java.util.List;

@Service
@Transactional
//使所有的线程都用这个对象,单例模式默认是开启的
@Scope("singleton")
public class UserServiceimpl implements UserService {
     
    @Autowired
    private UsersMapper userDao;//可以在UserDao上面加上userDao,这个不会报红,但是没有什么意义
    @Override
    public ResultVO checkLogin(String username, String pwd) {
     
//        查询用户名

        Example example = new Example(Users.class);
        Example.Criteria criteria = example.createCriteria();
        criteria.andEqualTo("username",username);
        List<Users> users = userDao.selectByExample(example);

//
        if(users.size()==0){
     
//            用户名不正确

            return new ResultVO(10001,"用户名不正确",null);



        }else {
     
            //密码使用MD5进行加密
            String md5Pwd = MD5Utils.md5(pwd);
            System.out.println(users.get(0).getPassword());

            if(md5Pwd.equals(users.get(0).getPassword())){
     

//                如果登录成功,则需要生成令牌token(token就是按照规则生成的字符串)
                String token= Base64Util.encode(username+"roothouzhicong");

                //          验证成功
                return  new ResultVO(ResultStatus.OK,token,users.get(0));
            }else {
     
                //密码不正确
                return  new ResultVO(ResultStatus.NO,"密码错误",null);

            }


        }



    }
    @Transactional
    @Override
    public ResultVO insert(String username, String pwd) {
     
//        判断这个用户是否被注册

//        加上这个锁可以使用所有的注册都用这个userServiceimpl
        synchronized (this){
     
//            把密码进行MD5的加密
            String password = MD5Utils.md5(pwd);

            //        查询用户名

            Example example = new Example(Users.class);
            Example.Criteria criteria = example.createCriteria();
            criteria.andEqualTo("username",username);
            List<Users> users = userDao.selectByExample(example);
//表示用户名没有被注册过,可以进行注册
            if (users.size()==0){
     
//一个是注册时间,regtime,一个是修改时间modtime
                Users user=new Users(username,password,new Date(),new Date());
                int i = userDao.insert(user);
                if(i>0){
     
                    return new ResultVO(ResultStatus.OK,"注册成功",null);
                }else {
     

                    return new ResultVO(ResultStatus.NO,"注册失败",null);

                }


            }
//            判断一下用户名是否已经被注册,然后把数据返回前端,goodjob,Noone can influence you
            else {
     

                return new ResultVO(ResultStatus.NO,"用户名已经被注册",null);
            }



        }


    }
}

前端设置token:

	doSubmit:function() {
			// 校验成功

			if(vm.isRight){
				var url=baseUrl+"/user/login";
				axios.get(url,{	
					params:{
						username:vm.username,password:vm.password

					} }
				
					).then((res)=>{

				console.log(res);

					var vo=res.data;

					console.log(vo);
					if(vo.code==1){
					
 // 如果登录成功就把token存储到时cookie中
						setCookieValue("token",vo.msg);

						 window.location.href="index.html";
					}else{
						vm.tips="账号或者密码错误";
					}



				});

前端的购物车获取token:

	<script type="text/javascript">
		// 进入购物车时要访问购物车列表的接口shopController接口
		var baseUrl="http://localhost:8080/";    
		var vm=new Vue({
       
			el:"#app",
			data:{
       
				token:"",
			},
			created() {
       
				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>

登录进行来可以把购物车获取token,前端的token用CookieUtils.js封装的包进行相关的获取,

package com.qfedu.fmmall.controller;


import com.qfedu.fmmall.utils.Base64Utils;
import com.qfedu.fmmall.vo.ResultStatus;
import com.qfedu.fmmall.vo.ResultVO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@CrossOrigin
@Api(value = "提供购物车业务相关的接口",tags = "购物车管理接口")
@RequestMapping("/shopcart")
public class ShopCartController {
     

    @RequestMapping("/list")
    @ApiImplicitParam(dataType = "string",name = "token",value = "登录的一个标志",required = true)
    public ResultVO shopcartList(String token){
     
//        校验输入的token看看是否是用户自己登录的token
       //解密 
        String decode = Base64Utils.decode(token);
        if(token==null){
     
            return new ResultVO(ResultStatus.NO, "请先登录", null);


        }else if(decode.endsWith("roothouzhicong")) {
     


            System.out.println("购物车列表相关的接口------------");
            return new ResultVO(ResultStatus.OK, "success", null);


        }else {
     

            return new ResultVO(ResultStatus.NO, "登录已经过期,请重新登录!!", null);

        }


    }
}

六、JWT(json Web Token)一个别人封装好的工具类生成相关的token

  1. 用自定义的token生成的时效性不可以进行定义
  2. 安全性较差

JWT结构:

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

6.1生成JWT

  • 添加依赖:

    
            <dependency>
                <groupId>com.auth0groupId>
                <artifactId>java-jwtartifactId>
                <version>3.10.3version>
            dependency>
    
            <dependency>
            <groupId>io.jsonwebtokengroupId>
            <artifactId>jjwtartifactId>
            <version>0.9.1version>
        dependency>
    
    

UserServiceimpl(登录成功)生成token:

 HashMap<String,Object> map=new HashMap<>();

                JwtBuilder builder= Jwts.builder();
                String token = builder.setSubject(username)   //主题就是token中携带的数据
                        .setIssuedAt(new Date()) //设置token的生成时间
                        .setId(users.get(0).getUserId() + "") //设置用户的id为tokenid
                        .setClaims(map)                         //map中可以存放用户的角色权限信息
                        .setExpiration(new Date(System.currentTimeMillis() + 24 * 60 * 60 * 1000)) //设置token的过期时间
                        .signWith(SignatureAlgorithm.HS256, "houzhicong") //设置加密的方式
                        .compact();

                //          验证成功

前端ShopCart.html通过Cookie获取生成的token:

	<script type="text/javascript">
		// 进入购物车时要访问购物车列表的接口shopController接口
		var baseUrl="http://localhost:8080/";    
		var vm=new Vue({
       
			el:"#app",
			data:{
       
				token:"",
			},
			created() {
       
				this.token=getCookieValue("token");
				console.log("token="+this.token);
				axios({
       
					method:"get",
					url:baseUrl+"shopcart/list",
					Headers:{
       
						token:this.token,
					}

				}).then(function (res) {
       
					console.log(res);
				});
			},
			




		})
		
		
		
		
		
		
		
		
		
		script>

后端ShopController进行解析Token:

package com.qfedu.fmmall.controller;


import com.qfedu.fmmall.utils.Base64Utils;
import com.qfedu.fmmall.vo.ResultStatus;
import com.qfedu.fmmall.vo.ResultVO;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.JwtParser;
import io.jsonwebtoken.Jwts;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@CrossOrigin
@Api(value = "提供购物车业务相关的接口",tags = "购物车管理接口")
@RequestMapping("/shopcart")
public class ShopCartController {
     

    @RequestMapping("/list")
    @ApiImplicitParam(dataType = "string",name = "token",value = "登录的一个标志",required = true)
    public ResultVO shopcartList(String token){
     
//        校验输入的token看看是否是用户自己登录的token
//        String decode = Base64Utils.decode(token);
        if(token==null){
     
            return new ResultVO(ResultStatus.NO, "请先登录", null);


        }else {
     
            JwtParser parser= Jwts.parser();
            parser.setSigningKey("houzhicong");//解析token 必须和生成token时候生成的密码一致

//            如果token正确(密码正确,有效期内) 则正常执行,否则抛出异常
            try{
     


                Jws<Claims> claimsJws=parser.parseClaimsJws(token);

                Claims body=claimsJws.getBody();//获取token中的用户数据
                String subject=body.getSubject();//获取生成token设置subject
               String v1=body.get("key1",String.class);//获取生成token时存储的Claims的map中的值
                return new ResultVO(ResultStatus.OK, "success", null);

            }catch (Exception e){
     
                return new ResultVO(ResultStatus.NO, "登录已经过期,请重新登录!!", null);


            }



        }


    }
}

6.2使用拦截器进行拦截

  1. 创建一个CheckTokenInterceptor
  2. 创建一个拦截器的类 InterceptorConfig
6.3.1
package com.qfedu.fmmall.config;

import com.qfedu.fmmall.interceptor.CheckTokenInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
     
    @Autowired
    private CheckTokenInterceptor checkTokenInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
     
        registry.addInterceptor(new CheckTokenInterceptor())
                .addPathPatterns("/**")
                .excludePathPatterns("/user/**"
                ,"/doc.html"
                ,"/swagger-ui/**");
    }
}

6.3使用请求头进行传递token

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

axios通过请求头传值 里面的参数用Headers 不用Params

axios({
					method:"get",
					url:baseUrl+"shopcart/list",
					Headers:{
						token:this.token,
					}

				}).then(function (res) {
					console.log(res);
				});
			},
			

6.3.1 CheckTokenInterceptor类 前端会发送预险性请求(只有它通过以后才可以进行第二次请求),需要拦截器进行放行

package com.qfedu.fmmall.interceptor;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.qfedu.fmmall.vo.ResultStatus;
import com.qfedu.fmmall.vo.ResultVO;
import io.jsonwebtoken.*;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

@Configuration
public class CheckTokenInterceptor implements HandlerInterceptor {
     
//   打ov 可以看到它的方法


    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
     
        String token = request.getParameter("token");

//        System.out.println("token----------");

//        前端会发送预险性请求
        String method = request.getMethod();

        if("options".equalsIgnoreCase(method)){
     

            return true;
        }

        if(token==null){
     
//            提示用户进行登录

            PrintWriter out = response.getWriter();
            ResultVO resultVO= new ResultVO(ResultStatus.NO, "请先登录", null);
//         抽出一个方法进行
            doResponse(response,resultVO);


//            拦截
            return  false;

        }else {
     
//            验证token

            try{
     

                JwtParser parser= Jwts.parser();
                parser.setSigningKey("houzhicong");
                Jws<Claims> claimsJws=parser.parseClaimsJws(token);
                return true;
            }catch (ExpiredJwtException e){
     
                ResultVO resultVO= new ResultVO(ResultStatus.NO, "登录过期,请重新登录", null);
                doResponse(response,resultVO);


                return false;
            }
            catch (UnsupportedJwtException e){
     
                ResultVO resultVO= new ResultVO(ResultStatus.NO, "Token不合法,请自重", null);
                doResponse(response,resultVO);


                return false;
            }

            catch (Exception e){
     
                ResultVO resultVO= new ResultVO(ResultStatus.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();
//        写上Json格式的resultVO
        String s = new ObjectMapper().writeValueAsString(resultVO);
        out.println(s);
        out.flush();
        out.close();


    }
}

七首页的分类列表的的实现

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

得出结论:数据量较少的情况,使用一次性查询,数据量较多使用多次查询

方案一:一次性查询三级分类

  • 优点只需一查询
  • 缺点:数据库查询效率较低,页面首次加载的速度较慢

方案二:

  • 先只查询一级分类,用户点击/鼠标移动到一级分类,动态加载二级分类
  • 缺点:需要多次连接数据库

7.1接口实现

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

一次性查询出来的sql语句:inner join 和left join 的区别 left join 左边没有关联的数据也会全部显示

select 

c1.category_id 'category_id1',
c1.category_name 'category_name',
c1.category_level 'category_level',
c1.parent_id 'parent_id',
c1.category_icon 'category_icon',
c1.category_slogan 'category_slogan',
c1.category_pic 'category_pic',
c1.category_bg_color 'category_bg_color',

c2.category_id 'category_id2',
c2.category_name 'category_name2',
c2.category_level 'category_leve2',
c2.parent_id 'parent_id2',




c3.category_id 'category_id3',
c3.category_name 'category_name3',
c3.category_level 'category_leve3',
c3.parent_id 'parent_id3'

from category c1
left join category c2 on c2.parent_id=c1.category_id
left join category c3 on c3.parent_id=c2.category_id
where c1.category_level=1

select *from category c1
  inner join category c2 on c2.parent_id=c1.category_id
  left join category c3 on c3.parent_id=c2.category_id
   where c1.category_level=1
 
--根据父级分类的parent_id进行查询 1 级 parent_id=0
select *from category where parent_id=0,
  • 创建用于封装查询的类别信息CategoryVO

    在Beans模块中entity包下面创建一个CategoryVO实体类用于封装Category和前端 进行数据的响应,相对于Category多了这个属性

    //实体类中实现当前分类的子分类
    private List<CategoryVO> categories;
    
            public List<CategoryVO> getCategories() {
           
                return categories;
            }
    
            public void setCategories(List<CategoryVO> categories) {
           
                this.categories = categories;
            } 
    
  • 在CategoryMapper中定义一个函数

    package com.qfedu.fmmall.dao;
    
    import com.qfedu.fmmall.entity.Category;
    import com.qfedu.fmmall.entity.CategoryVO;
    import com.qfedu.fmmall.general.GeneralDao;
    
    import java.util.List;
    
    public interface CategoryMapper extends GeneralDao<Category> {
           
    //使用连接查询实现分类列表查询
        public List<CategoryVO> selectAllCategories();
        
        
        //    子查询
        public List<CategoryVO> selectAllCategories2(int parentId);
    }
    
  • 映射文件配置

    
    DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.qfedu.fmmall.dao.CategoryMapper">
      <resultMap id="BaseResultMap" type="com.qfedu.fmmall.entity.Category">
        
        <id column="category_id" jdbcType="VARCHAR" 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_bg_color" jdbcType="VARCHAR" property="categoryBgColor" />
      resultMap>
    
    
    
      <resultMap id="CategoryVoMap" type="com.qfedu.fmmall.entity.CategoryVO">
        
        <id column="category_id1" jdbcType="VARCHAR" 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_bg_color1" jdbcType="VARCHAR" property="categoryBgColor" />
        <collection property="categories" ofType="com.qfedu.fmmall.entity.CategoryVO">
          <id column="category_id2" jdbcType="VARCHAR" property="categoryId" />
          <result column="category_name2" jdbcType="VARCHAR" property="categoryName" />
          <result column="category_level2" jdbcType="INTEGER" property="categoryLevel" />
          <result column="parent_id2" jdbcType="INTEGER" property="parentId" />
    
         <collection property="categories" ofType="com.qfedu.fmmall.entity.CategoryVO">
    
           <id column="category_id3" jdbcType="VARCHAR" property="categoryId" />
           <result column="category_name3" jdbcType="VARCHAR" property="categoryName" />
           <result column="category_level3" jdbcType="INTEGER" property="categoryLevel" />
           <result column="parent_id3" jdbcType="INTEGER" property="parentId" />
    
    
         collection>
        collection>
    
      resultMap>
    
    
    
    
    
    
    
    
    
      <select id="selectAllCategories" resultMap="CategoryVoMap">
    
    select
    
    c1.category_id 'category_id1',
    c1.category_name 'category_name',
    c1.category_level 'category_level',
    c1.parent_id 'parent_id',
    c1.category_icon 'category_icon',
    c1.category_slogan 'category_slogan',
    c1.category_pic 'category_pic',
    c1.category_bg_color 'category_bg_color',
    
    c2.category_id 'category_id2',
    c2.category_name 'category_name2',
    c2.category_level 'category_leve2',
    c2.parent_id 'parent_id2',
    
    
    
    
    c3.category_id 'category_id3',
    c3.category_name 'category_name3',
    c3.category_level 'category_leve3',
    c3.parent_id 'parent_id3'
    
    from category c1
    left join category c2 on c2.parent_id=c1.category_id
    left join category c3 on c3.parent_id=c2.category_id
    where c1.category_level=1
    
    
      select>
        
        
       
    
    

使用子查询实现分类列表的查询:

```xml
    

  
    
    
    
    
    
    
    
    
    



  






  
  


7.2业务层实现

CategoryService

package com.qfedu.fmmall.service;

import com.qfedu.fmmall.vo.ResultVO;

public interface CategoryService {
     

    public ResultVO queryAllCategory();
}

CategoryServiceimpl:

package com.qfedu.fmmall.service.impl;

import com.qfedu.fmmall.dao.CategoryMapper;
import com.qfedu.fmmall.entity.CategoryVO;
import com.qfedu.fmmall.service.CategoryService;
import com.qfedu.fmmall.vo.ResultStatus;
import com.qfedu.fmmall.vo.ResultVO;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.List;


@Service
public class CategoryServiceimpl implements CategoryService {
     
    @Resource
    private CategoryMapper categoryMapper;

    @Override
    public ResultVO queryAllCategory() {
     
        List<CategoryVO> categoryVOS = categoryMapper.selectAllCategories2(0);

        return new ResultVO(ResultStatus.OK,"success",categoryVOS);


    }
}

控制层实现

indexController实现:

@Autowired
    private CategoryService categoryService;


    @RequestMapping("category-list")
    @ApiOperation("商品分类查询接口")
    public ResultVO queryAllCategory(){
     

        return categoryService.queryAllCategory();
    }


八、商品的推荐功能实现

8.1 流程分析

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

推荐最新上架的商品

8.2接口开发

8.2.1数据库的实现
  • sql语句实现

    -- 商品推荐查询最新上架信息
    select *from product order by create_time desc limit 0,3;
    -- 商品图片查询 根据商品id查询商品图片
    select *from product_img where item_id=2;
    
    

    在子工程beans工程下面创建ProductVO (加上了这个属性 private List imgs;因为一个商品包含多张表)

    package com.qfedu.fmmall.entity;
    
    import javax.persistence.Column;
    import javax.persistence.Id;
    import java.util.Date;
    import java.util.List;
    
    public class ProductVO {
           
        /**
         * 商品id
         */
        @Id
        @Column(name = "product_id")
        private Integer productId;
    
        /**
         * 商品名称
         */
        @Column(name = "product_name")
        private String productName;
    
        /**
         * 商品分类id
         */
        @Column(name = "category_id")
        private Integer categoryId;
    
        /**
         * 一级分类外键id
         */
        @Column(name = "root_category_id")
        private Integer rootCategoryId;
    
        /**
         * 销量
         */
        @Column(name = "sold_num")
        private Integer soldNum;
    
        /**
         * 商品状态
         */
        @Column(name = "product_status")
        private Integer productStatus;
    
        /**
         * 商品内容
         */
        private String content;
    
        /**
         * 创建时间
         */
        @Column(name = "create_time")
        private Date createTime;
    
        /**
         * 更新时间
         */
        @Column(name = "update_time")
        private Date updateTime;
    
    
        private List<ProductImg> imgs;
    
        public List<ProductImg> getImgs() {
           
            return imgs;
        }
    
        public void setImgs(List<ProductImg> imgs) {
           
            this.imgs = imgs;
        }
    
        @Override
        public String toString() {
           
            return "ProductVO{" +
                    "imgs=" + imgs +
                    '}';
        }
    
        /**
         * 获取商品id
         *
         * @return product_id - 商品id
         */
        public Integer getProductId() {
           
            return productId;
        }
    
        /**
         * 设置商品id
         *
         * @param productId 商品id
         */
        public void setProductId(Integer productId) {
           
            this.productId = productId;
        }
    
        /**
         * 获取商品名称
         *
         * @return product_name - 商品名称
         */
        public String getProductName() {
           
            return productName;
        }
    
        /**
         * 设置商品名称
         *
         * @param productName 商品名称
         */
        public void setProductName(String productName) {
           
            this.productName = productName == null ? null : productName.trim();
        }
    
        /**
         * 获取商品分类id
         *
         * @return category_id - 商品分类id
         */
        public Integer getCategoryId() {
           
            return categoryId;
        }
    
        /**
         * 设置商品分类id
         *
         * @param categoryId 商品分类id
         */
        public void setCategoryId(Integer categoryId) {
           
            this.categoryId = categoryId;
        }
    
        /**
         * 获取一级分类外键id
         *
         * @return root_category_id - 一级分类外键id
         */
        public Integer getRootCategoryId() {
           
            return rootCategoryId;
        }
    
        /**
         * 设置一级分类外键id
         *
         * @param rootCategoryId 一级分类外键id
         */
        public void setRootCategoryId(Integer rootCategoryId) {
           
            this.rootCategoryId = rootCategoryId;
        }
    
        /**
         * 获取销量
         *
         * @return sold_num - 销量
         */
        public Integer getSoldNum() {
           
            return soldNum;
        }
    
        /**
         * 设置销量
         *
         * @param soldNum 销量
         */
        public void setSoldNum(Integer soldNum) {
           
            this.soldNum = soldNum;
        }
    
        /**
         * 获取商品状态
         *
         * @return product_status - 商品状态
         */
        public Integer getProductStatus() {
           
            return productStatus;
        }
    
        /**
         * 设置商品状态
         *
         * @param productStatus 商品状态
         */
        public void setProductStatus(Integer productStatus) {
           
            this.productStatus = productStatus;
        }
    
        /**
         * 获取商品内容
         *
         * @return content - 商品内容
         */
        public String getContent() {
           
            return content;
        }
    
        /**
         * 设置商品内容
         *
         * @param content 商品内容
         */
        public void setContent(String content) {
           
            this.content = content == null ? null : content.trim();
        }
    
        /**
         * 获取创建时间
         *
         * @return create_time - 创建时间
         */
        public Date getCreateTime() {
           
            return createTime;
        }
    
        /**
         * 设置创建时间
         *
         * @param createTime 创建时间
         */
        public void setCreateTime(Date createTime) {
           
            this.createTime = createTime;
        }
    
        /**
         * 获取更新时间
         *
         * @return update_time - 更新时间
         */
        public Date getUpdateTime() {
           
            return updateTime;
        }
    
        /**
         * 设置更新时间
         *
         * @param updateTime 更新时间
         */
        public void setUpdateTime(Date updateTime) {
           
            this.updateTime = updateTime;
        }
    }
    

    ProductMapper文件:

    package com.qfedu.fmmall.dao;
    
    import com.qfedu.fmmall.entity.Product;
    import com.qfedu.fmmall.entity.ProductVO;
    import com.qfedu.fmmall.general.GeneralDao;
    
    import java.util.List;
    
    public interface ProductMapper extends GeneralDao<Product> {
           
    //    查询推荐商品信息
        public List<ProductVO> selectRecommendProducts();
    }
    

    ProductImgMapper文件:

    package com.qfedu.fmmall.dao;
    
    import com.qfedu.fmmall.entity.ProductImg;
    import com.qfedu.fmmall.general.GeneralDao;
    
    import java.util.List;
    
    public interface ProductImgMapper extends GeneralDao<ProductImg> {
           
        public List<ProductImg> selectProductImgByProductId(int productId);
    }
    

    ProductMapper.xml文件实现

    注意一下子查询 的语句:

     <collection property="imgs" column="product_id"
                    select="com.qfedu.fmmall.dao.ProductImgMapper.selectProductImgByProductId">collection>
    
    
    DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.qfedu.fmmall.dao.ProductMapper">
      <resultMap id="BaseResultMap" type="com.qfedu.fmmall.entity.Product">
        
        <id column="product_id" jdbcType="INTEGER" 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="content" jdbcType="VARCHAR" property="content" />
        <result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
        <result column="update_time" jdbcType="TIMESTAMP" property="updateTime" />
      resultMap>
    
      <resultMap id="ProductVoMap" type="com.qfedu.fmmall.entity.ProductVO">
        
        <id column="product_id" jdbcType="INTEGER" 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="content" jdbcType="VARCHAR" property="content" />
        <result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
        <result column="update_time" jdbcType="TIMESTAMP" property="updateTime" />
        <collection property="imgs" column="product_id"
                    select="com.qfedu.fmmall.dao.ProductImgMapper.selectProductImgByProductId">collection>
      resultMap>
    
    
      <select id="selectRecommendProducts" resultMap="ProductVoMap">
    
    
    select
    product_id,
    product_name,
    category_id,
    root_category_id,
    sold_num,
    product_status,
    content,
    create_time,
    update_time
    
    
    from product order by create_time desc limit 0,3;
    
    
      select>
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    mapper>
    

    8.2.2业务层实现

    package com.qfedu.fmmall.service;
    
    import com.qfedu.fmmall.entity.ProductVO;
    import com.qfedu.fmmall.vo.ResultVO;
    
    import java.util.List;
    
    public interface ProductService {
           
        public ResultVO selectRecommendProducts();
    
    }
    
    

    8.2.3控制层实现

     @ApiOperation("商品推荐查询信息接口")
        @RequestMapping(value = "/list-recommends",method = RequestMethod.GET)
        public ResultVO selectProductRecommend(){
           
            ResultVO resultVO = productService.selectRecommendProducts();
            return resultVO;
    
    
        }
    
    

九、商品详情显示(在Introduction.html进行相关的显示)

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

  1. 获取商品id
  2. 可以查询商品的详情信息(商品的基本信息,商品套餐,商品图片信息。)
  3. 将商品参数返回给前端

9.1接口实现

9.1.1 商品详情接口

商品基本信息(product),商品套餐(sku),商品图片(product_img)

  • 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;
    
  • 可以用子查询实现这个相关的操作

  • dao接口实现(通过product,product_img,product_sku三张表获取商品的详情信息)

    @Repository
    public interface ProductMapper extends GeneralDao<Product> {
           
    //    查询推荐商品信息
        public List<ProductVO> selectRecommendProducts();
    }
    
    
    package com.qfedu.fmmall.dao;
    
    import com.qfedu.fmmall.entity.ProductImg;
    import com.qfedu.fmmall.general.GeneralDao;
    import org.springframework.stereotype.Repository;
    
    import java.util.List;
    @Repository
    public interface ProductImgMapper extends GeneralDao<ProductImg> {
           
        public List<ProductImg> selectProductImgByProductId(int productId);
    }
    
    package com.qfedu.fmmall.dao;
    
    import com.qfedu.fmmall.entity.ProductSku;
    import com.qfedu.fmmall.general.GeneralDao;
    import org.springframework.stereotype.Repository;
    
    @Repository
    public interface ProductSkuMapper extends GeneralDao<ProductSku> {
           
    }
    
    
  • 业务层实现

//这里为不需要事务,但是如果某些事务如果调用了我也加入到事务中来
//    事务默认的隔离级别是可重复读 repeateable read
    @Transactional(propagation = Propagation.SUPPORTS)
    public ResultVO selectProductBasicInfo(String productId) {
     
//      1.查询商品的基本信息



        Example example = new Example(Product.class);
        Example.Criteria criteria = example.createCriteria();
        criteria.andEqualTo("productId",productId);
        criteria.andEqualTo("productStatus",1);

        List<Product> products = productMapper.selectByExample(example);
//        System.out.println(products);


        if(products.size()>0){
     
            //      2.查询商品的图片信息
           Example example1 = new Example(ProductImg.class);
            Example.Criteria criteria1 = example1.createCriteria();
            criteria1.andEqualTo("itmeId",productId);
            List<ProductImg> productImgs = productImgMapperMapper.selectByExample(example1);
//            System.out.println(productImgs);


            //        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);
//            System.out.println(productSkus);


//            把所有的商品的详情信息放入HashMap当中进行使用
            HashMap<String,Object> basicInfo=new HashMap<>();
            basicInfo.put("product",products.get(0));
            basicInfo.put("productImgs",productImgs);
            basicInfo.put("productSkus",productSkus);

            return new ResultVO(ResultStatus.OK,"success",basicInfo);





        }else {
     

            new ResultVO(ResultStatus.NO,"查询商品基本信息失败",null);
        }
        return null;
    }
  • 控制层实现(这边把商品的详情的信息放到了ResultVO的Object中)
//    商品详情查询

    @RequestMapping(value = "/detail/{pid}",method = RequestMethod.GET)
    public ResultVO getProductBasicInfo(@PathVariable("pid") String productId){
     

        ResultVO resultVO = productService.selectProductBasicInfo(productId);
//        System.out.println(resultVO);

        return resultVO;

    }



十、显示商品评价的信息(通过用户和商品评论表进行相关的连表查询)

-- 根据评论的id查询评论信息,关联用户表查询用户信息
select u.username,u.user_img,c.* from product_comments c
inner join users u 
on c.user_id=u.user_id
where c.product_id=3

10.1 新建的VO,ProductCommentsVO (一对一的连表查询可以不用在实体类中声明另一个实体类)

package com.qfedu.fmmall.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import javax.persistence.Column;
import javax.persistence.Table;
import java.util.Date;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class ProductCommentsVO {
     


    private Integer productId;
    private String productName;
    private Integer orderItemId;

    private String isannonymouns;
    private Integer commType;
    private Integer commLevel;
    private String commImgs;
    private String sepcName;
    private Integer replyStatus;
    private String replyContent;
    private Date replyTime;
    private Integer isShow;


//用于封装评论对应的用户数据
    private Integer userId;
    private String username;
    private String nickname;
    private String userImg;


}

在Mapper定义相应的接口:

package com.qfedu.fmmall.dao;

import com.qfedu.fmmall.entity.ProductComments;
import com.qfedu.fmmall.entity.ProductCommentsVO;
import com.qfedu.fmmall.general.GeneralDao;
import org.springframework.stereotype.Repository;

import java.util.List;


@Repository
public interface ProductCommentsMapper extends GeneralDao<ProductComments> {
     

    public List<ProductCommentsVO> selectCommentsByProductId(int productId);
}

十一、添加购物车的功能实现

10.1流程分析:

点击添加购物车---------商品、选择套餐id,套餐属性,数量,token-------------进行token的校验

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

10.2数据库的相关的操作

  1. 增加字段sku_props

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

    shopping_cart

    表生成之后 用逆向工程重新生成shopping_cart表的相关的结构。修改generalConfig.xml把%修改成shopping_cart

    注意一下**%表示生成所有的表**

10.2.1 购买的数量的前端实现在vue的methods中添加+和-的点击事件

	changeNum:function(m){
						if(m==-1 && this.num>1){
						this.num=this.num-1;

						}else if(m==1 && this.num

进行商品数量的绑定可以用 v-model="num"进行双向绑定

<dd>
															<input id="min" class="am-btn am-btn-default"  type="button" value="-" @click="changeNum(-1)"/>
															<input id="text_box"  type="text" v-model="num" style="width:30px;" />
															<input id="add" class="am-btn am-btn-default"  type="button" value="+" @click="changeNum(1)" />
															<span id="stock1" class="tb-hidden">库存<span class="stock">{
    {productSkus[currentSkuIndex].stock}}span>span>
														dd>

10.2.2给加入购物车这个按钮添加点击事件

<li>
								<div class="clearfix tb-btn tb-btn-basket theme-login">
									<a id="LikBasket" title="加入购物车" href="" @click="addShopCart()"><i>i>加入购物车a>
								div>
							li>

把相关的添加的购物车的信息放入cart这个类中:

					addShopCart(){
					var uid=getCookieValue("userId");


						var propStr="";
						// 套餐属性转换成字符串
						for(var key in this.chooseskuProps){
							propStr+=key+":"+this.chooseskuProps[key]+";";
}


						var cart={
									
									"cartNum": this.num,
									"cartTime": "",
									"productId": this.productId,
									"productPrice": this.productSkus[this.currentSkuIndex].sellPrice,
									"skuId": this.productSkus.skuId,
									"skuProps":propStr,
									"userId": uid
								};

						//从cookie中获取token

						var token=getCookieValue("token");
						console.log("---token-------");
						console.log(token);

						// 把购物车的信息放入数据库中
						var url5=baesUrl+"shopcart/add";
						axios.post(
							{
								url:url5,
								methods:"post",
								headers:{
									token:token
								},
								data:cart
}


						).then( res=>{

							console.log("------res-----"+res);



						}

						);
					 


					}

十二、添加购物车时候用户未登录

12.1 添加购物车用户未登录,业务处理方式:

  1. 查询商品详情页面的时候,就提示先登录,跳转到登录页面
  2. 当点击添加购物车,弹窗显示先登录,完成登录,点击添加购物车
  3. 点击添加购物车,会跳转到登录页面,登录完成之后会跳转到商品详情页面。

12.2我们使用第三种难度最大的来

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

12.3使用Layui进行添加购物车成功/失败的提示

  • 引入lay-ui cdn

    
    <link rel="stylesheet" href="//unpkg.com/layui@2.6.8/dist/css/layui.css">
     
    
    <script src="//unpkg.com/layui@2.6.8/dist/layui.js">
          
    
    12.3.1声明layui的弹窗组件
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hxrSswP2-1633446686642)(C:\Users\CourageAndLove\AppData\Roaming\Typora\typora-user-images\image-20210922163219931.png)]

    12.3.2成功失败进行提示

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

十三购物车的列表

流程图:

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

步骤

  1. 获取用户user_id
  2. 通过user_id获取购物车的信息(包括商品的名字,商品图片的信息)
  3. 将购物车信息数据返回给前端 。
  4. 也就是有三张表,购物车表(shopping_cart),商品表(product),商品图片表(product_img 根据商品id查询商品主图)

13.1数据库dao接口的实现

  1. sql语句

    select  c.*,p.product_name,i.url from shopping_cart c
    inner join product p
    inner join product_img i
    on c.product_id=p.product_id 
    and i.item_id=p.product_id
    where user_id=2 and i.is_main=1;
    
   
2. dao接口

  ```java
    List  selectShoppingCartByUserId(int userid);

13.2pojo接口实现

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

注意一下数据库的shopping_cart表不需要加上这两个字段,是通过product_img表和product表的column属性来进行关联的

13.3修改购物车

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

13.31通过这个进行购物车数量的修改:

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

13.32changNum函数进行实现:
	
           methods:{
			   changeNum(event){
				   var oper=event.srcElement.dataset.oper;

				   var index=event.srcElement.dataset.id;
				   console.log(oper);
				   if(oper=="+"){
					  
						this.shoppingCartsSC[index].cartNum=parseInt(this.shoppingCartsSC[index].cartNum) +1;
					   

				   }else{

					if(this.shoppingCartsSC[index].cartNum>1){
					this.shoppingCartsSC[index].cartNum=parseInt(this.shoppingCartsSC[index].cartNum)-1;
  					}
					

				   }
				   //   修改的cartId和cartNum
					var cartId=this.shoppingCartsSC[index].cartId;
					var cartNum=this.shoppingCartsSC[index].cartNum;
					var url1=baseUrl+"shopcart/update/"+cartId+"/"+cartNum;
					axios({
						url:url1,
						method:"put",
						params:{
							token:this.token
						}

					}).then((res)=>{

						console.log(res);

})



			   },

// 			addNum:function(event){
// 				// 这个可以打印绑定的id的值
// 				console.log("--add"+event.srcElement.dataset.id);
// 			    var index=event.srcElement.dataset.id;
// 				this.shoppingCartsSC[index].cartNum=parseInt(this.shoppingCartsSC[index].cartNum) +1;

				
				
// 			},
			
// 			mulNum:function(event){
			
// 			    var index=event.srcElement.dataset.id;
// 				if(this.shoppingCartsSC[index].cartNum>1){
// 					this.shoppingCartsSC[index].cartNum=parseInt(this.shoppingCartsSC[index].cartNum)-1;
//   }
				
				
				
// 			}



		   }

十四购物车提交订单结算功能实现

14.1实现流程分析

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

十五、订单提交及支付流程

15.1流程分析

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

15.2订单添加接口实现

15.3数据库操作

  • 根据收货id获取收货的地址信息(tkmapper)
  • 根据购物车ID,查询购物车详情(需要关联查询商品名称,sku名称,库存,商品图片,商品价格)----》 获取生成商品快照的数据 只需在ShoppingCartVO中多加上一个stock字段就好,然后在ShoppCartMapper.xml加上需要查询的这个字段
  • 保存订单信息(tkMapper)
  • 修改库存(tkMapper)
  • 保存商品快照(tkMapper)

15.4serviceimpl层实现 注意:这个方法需要加上@Transactional,也就是订单生成的时候,快照也必须生成

  1. 生成OrderId的方法 UUID.random().toString()
  2. 通过时间戳生成System.currentTimeMillis()+(new Random().nextInt(9999)+100)+""
package com.qfedu.fmmall.service.impl;

import com.qfedu.fmmall.dao.OrdersItemMapper;
import com.qfedu.fmmall.dao.OrdersMapper;
import com.qfedu.fmmall.dao.ProductSkuMapper;
import com.qfedu.fmmall.dao.ShoppingCartMapper;
import com.qfedu.fmmall.entity.Orders;
import com.qfedu.fmmall.entity.OrdersItem;
import com.qfedu.fmmall.entity.ProductSku;
import com.qfedu.fmmall.entity.ShoppingCartVO;
import com.qfedu.fmmall.service.OrderService;
import com.qfedu.fmmall.vo.ResultStatus;
import com.qfedu.fmmall.vo.ResultVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import tk.mybatis.mapper.entity.Example;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.SQLException;
import java.util.*;

import static java.math.BigDecimal.*;

@Service

public class OrderServiceimpl implements OrderService{
     

    @Autowired
    private ShoppingCartMapper shoppingCartMapper;
    @Autowired
    private OrdersMapper ordersMapper;
    @Autowired
    private OrdersItemMapper ordersItemMapper;
    @Autowired
    private ProductSkuMapper productSkuMapper;
    /* userId 1(zhangsan) 3(houzhicong)
    *cids  "39,40,41"
    * @return
    *
    * */

//    int userId, String receiverName,
//    String receiverMobile,String address,
//    double price,int payType,String orderRemark   把这些用Orders对象来接收


//    保存订单的步骤
//    1.查询选中购买的购物车详情
//    2. 校验库存
//    3.保存订单
//    4.保存订单快照
//    5.购买成功需要删除购物车记录
//    可以知道这四个步骤需要同时成功或者同时失败,符合一个事务的操作(ACID)
    @Transactional
    public Map<String,String> addOrder(List<Integer> cids, Orders orders) throws  SQLException{
     
         Map<String,String> map=new HashMap<>();
//        根据cids查询购物车的详情记录(包括库存)
        List<ShoppingCartVO> shoppingCartVOList = shoppingCartMapper.selectShoppingcartByids(cids);


//        校验库存
        boolean f=true;

        String untitled="";
        for (ShoppingCartVO sc :shoppingCartVOList
                ) {
     
            if(Integer.valueOf(sc.getCartNum())>sc.getStock()){
     
                f=false;

            }

//            获取所有的商品名称,以,分割拼接成字符串
            untitled=untitled+sc.getProductName()+",";

        }
        if(f){
     
//            表示库存充足进行保存
//            1.userId  2 untitled名称 3 收件人地址,姓名,电话,地址
//            4. 总价格 5.支付方式
//            6.创建 订单的时间
//            7.订单初始状态 1 待支付
        orders.setStatus(1);
        orders.setUntitled(untitled);
        orders.setCreateTime(new Date());
        orders.setCancelTime(new Date());
        orders.setDeliveryTime(new Date());

//        生成订单编号
            String orderId = UUID.randomUUID().toString().replace("-", "");
            orders.setOrderId(orderId);


//            保存订单
            int i=ordersMapper.insert(orders);
            if(i>0){
     


//               ordersItem 生成商品快照
//                List ordersItemList=new ArrayList<>();
                for (ShoppingCartVO sc :shoppingCartVOList) {
     
//                    生成订单的编号

                    int cnum=Integer.valueOf(sc.getCartNum());
                  String itemid=System.currentTimeMillis()+(new Random().nextInt(9999)+100)+"";
                    String itemid1 = itemid.substring(1, 10);


//                 注意一下double需要转换为Bigdecimal类型


//                public OrdersItem(Integer orderId, Integer productId,
//                String productName,
//                String productImg, Integer skuId, String skuName,
//                BigDecimal productPrice, Integer buyCounts,
//                BigDecimal totalAmount, Date basketDate, Date buyTime,
//                Integer isComment)
                int itemid2=Integer.parseInt(itemid1);
                OrdersItem ordersItem=  new OrdersItem();
                ordersItem.setOrderId(itemid2);
                ordersItem.setProductId(Integer.valueOf(sc.getProductId()));
                ordersItem.setProductName(sc.getProductName());
                ordersItem.setProductImg(sc.getProductImg());
                ordersItem.setSkuId(Integer.valueOf(sc.getSkuId()));
                    System.out.println(sc.getSkuName());
                    ordersItem.setSkuName(sc.getSkuName());
                System.out.println(sc.getSellPrice());
                ordersItem.setProductPrice(new BigDecimal(String.valueOf(sc.getProductPrice())));

                ordersItem.setBuyCounts(cnum);
                ordersItem.setTotalAmount(sc.getProductPrice());
                ordersItem.setBasketDate(new Date());
                ordersItem.setBuyTime(new Date());
                ordersItem.setIsComment(0);

//                ordersItemList.add(ordersItem);
                      int m=ordersItemMapper.insert(ordersItem);

                    }

//                int j = ordersItemMapper.insertList(ordersItemList);
//  扣减库存???
//                根据套餐Id修改库存量
                for (ShoppingCartVO sc :shoppingCartVOList
                ) {
     
                    String skuId = sc.getSkuId();
                    int newStock=sc.getStock()-Integer.valueOf(sc.getCartNum());

//                    Example example = new Example(ProductSku.class);
//                    Example.Criteria criteria = example.createCriteria();
//                    criteria.andEqualTo("skuId",skuId);


//                    ProductSku productSku = productSkuMapper.selectByPrimaryKey(skuId);
                    ProductSku productSku=new ProductSku();
                    productSku.setSkuId(skuId);
                    productSku.setStock(String.valueOf(newStock));
//                    productSku.setSkuImg(null);

                    productSkuMapper.updateByPrimaryKeySelective(productSku);

                }

//  保存订单成功 删除购物车记录
                for (Integer cid:cids
                     ) {
     
                    shoppingCartMapper.deleteByPrimaryKey(cid);

                }

                 map.put("orderId",orderId);
                 map.put("productNames",untitled);

              return   map;


            }



        }else{
     
//            不足
   return null;

        }

        return null;
    }
}

swagger报错解决

For input String :""

在application.yml加上日志的配置:

logging:
  level:
    io.swagger.models.parameters.AbstractSerializableParameter: error

十六 商品分类信息的查询

16.1 流程分析

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

16.2 接口开发

16.2.1根据类别查询商品接口