# 一直有人问我前端,这个要改配置

 

项目源码

直接刷maven开redis导sql就可以用注意本地sql和线上sql区别

链接:https://pan.baidu.com/s/19tBV2OBfREaWMAJskV0uIg?pwd=1111
提取码:1111
--来自百度网盘超级会员V3的分享

概述

电商项

目录

项目源码

链接:https://pan.baidu.com/s/19tBV2OBfREaWMAJskV0uIg?pwd=1111提取码:1111--来自百度网盘超级会员V3的分享

概述

电商项目整体介绍

为什么要做电商系统

项目亮点

功能模块介绍

项目开发所需工具准备

数据库设计与项目初始化

数据库表设计

如何技术选型

项目初始化生成逆向文件

打通数据库链路.

配置log4j2日志组件

AOP统一打印请求和返回信息

用户模块开发

接口文档地址

枚举的应用

API统一返回对象

注册接口开发

自定义异常类

GlobalExceptionHandler

java异常体系

对密码进行md5保护

登录接口开发

用户模块剩余接口开发

商品分类模块开发

商品分类模块介绍

商品分类功能模块设计

开发添加分类接口part1

开发添加分类接口part2

@Valid注解优雅校验入参

Swagger自动生成API文档

更新目录接口开发

统一校验管理员身份

删除目录接口,分页目录开发

用户分类列表接口开发

利用Redis缓存加速响应

idea调试技巧

统一开关

条件断点开关

进入方法内部

进入下一行

跳出方法

表达式求值

商品模块开发

添加商品接口开发

UUID介绍

图片上传接口开发

资源映射开发

更新和删除商品接口开发

批量上下架商品街口开发

后台列表商品详情接口开发

前台商品列表开发

购物车模块开发

购物车模块介绍

用户过滤器开发

添加商品接口开发

购物车列表

更新购物车接口开发

删除接口

选中购物车相关接口开发

全选择或者全不选择某购物车的商品

订单模块开发

创建订单接口开发

数据库事务

订单详情

订单列表

取消订单接口开发

二维码接口开发

后台订单列表接口开发

支付接口开发

管理订单接口开发

管理员发货

完结订单

测试

上线与部署

服务器环境配置

部署到云服务器


目整体介绍

为什么要做电商系统

项目亮点

功能模块介绍

项目开发所需工具准备

首先安装maven helper

然后free mybatis plugin

数据库设计与项目初始化

数据库表设计

直接导入sql

如何技术选型

项目初始化生成逆向文件

首先打开idea选择新建一个spring-boot项目

首先添加mybatis和mysql依赖

<dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.1</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

然后是插件用于自动生成文件

<plugin>
                <groupId>org.mybatis.generator</groupId>
                <artifactId>mybatis-generator-maven-plugin</artifactId>
                <version>1.4.1</version>
                <configuration>
                    <verbose>true</verbose>
                    <overwrite>true</overwrite>
                </configuration>
            </plugin>

接下来还需要在资源目录下引用这个插件的配置文件

首先在资源目录中粘贴jar包然后编写配置文件

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
  PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
  "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
  <!-- 配置文件,放在resource目录下即可 -->
  <!--数据库驱动个人配置-->
  <classPathEntry
    location="src/main/resources/jar/mysql-connector-java-8.0.29.jar"/>
  <context id="MysqlTables" targetRuntime="MyBatis3">
    <property name="autoDelimitKeywords" value="true"/>
    <!--可以使用``包括字段名,避免字段名与sql保留字冲突报错-->
    <property name="beginningDelimiter" value="`"/>
    <property name="endingDelimiter" value="`"/>
    <!-- optional,旨在创建class时,对注释进行控制 -->
    <commentGenerator>
      <property name="suppressDate" value="true"/>
      <property name="suppressAllComments" value="true"/>
    </commentGenerator>
    <!--数据库链接地址账号密码-->
    <jdbcConnection driverClass="com.mysql.cj.jdbc.Driver"
      connectionURL="jdbc:mysql://127.0.0.1:3306/imooc_mall?useUnicode=true&amp;characterEncoding=UTF-8&amp;zeroDateTimeBehavior=convertToNull"
      userId="root"
      password="12345678">
      <property name="nullCatalogMeansCurrent" value="true"/>
    </jdbcConnection>
    <!-- 非必需,类型处理器,在数据库类型和java类型之间的转换控制-->
    <javaTypeResolver>
      <property name="forceBigDecimals" value="false"/>
    </javaTypeResolver>
    <!--生成Model类存放位置-->
    <javaModelGenerator targetPackage="com.imooc.mall.model.pojo"
      targetProject="src/main/java">
      <!-- 是否允许子包,即targetPackage.schemaName.tableName -->
      <property name="enableSubPackages" value="true"/>
      <!-- 是否对类CHAR类型的列的数据进行trim操作 -->
      <property name="trimStrings" value="true"/>
      <!-- 建立的Model对象是否 不可改变  即生成的Model对象不会有 setter方法,只有构造方法 -->
      <property name="immutable" value="false"/>
    </javaModelGenerator>
    <!--生成mapper映射文件存放位置-->
    <sqlMapGenerator targetPackage="mappers" targetProject="src/main/resources">
      <property name="enableSubPackages" value="true"/>
    </sqlMapGenerator>
    <!--生成Dao类存放位置-->
    <javaClientGenerator type="XMLMAPPER" targetPackage="com.imooc.mall.model.dao"
      targetProject="src/main/java">
      <property name="enableSubPackages" value="true"/>
    </javaClientGenerator>
    <!--生成对应表及类名-->
    <table schema="root" tableName="imooc_mall_cart" domainObjectName="Cart"
      enableCountByExample="false"
      enableUpdateByExample="false" enableDeleteByExample="false" enableSelectByExample="false"
      selectByExampleQueryId="false">
    </table>
    <table tableName="imooc_mall_category" domainObjectName="Category" enableCountByExample="false"
      enableUpdateByExample="false" enableDeleteByExample="false" enableSelectByExample="false"
      selectByExampleQueryId="false">
    </table>
    <table tableName="imooc_mall_order" domainObjectName="Order" enableCountByExample="false"
      enableUpdateByExample="false" enableDeleteByExample="false" enableSelectByExample="false"
      selectByExampleQueryId="false">
    </table>
    <table tableName="imooc_mall_order_item" domainObjectName="OrderItem"
      enableCountByExample="false"
      enableUpdateByExample="false" enableDeleteByExample="false" enableSelectByExample="false"
      selectByExampleQueryId="false">
    </table>
    <table tableName="imooc_mall_product" domainObjectName="Product" enableCountByExample="false"
      enableUpdateByExample="false" enableDeleteByExample="false" enableSelectByExample="false"
      selectByExampleQueryId="false">
    </table>
    <table tableName="imooc_mall_user" domainObjectName="User" enableCountByExample="false"
      enableUpdateByExample="false" enableDeleteByExample="false" enableSelectByExample="false"
      selectByExampleQueryId="false">
    </table>

  </context>
</generatorConfiguration>

爆红不用管

用户名和密码需要改

接下来利用generator为表生成对应的代码

如图所示双击划线

如图所示成功生成一系列文件

打通数据库链路.

先运行spring-boot发现报错

没有配置数据源

所以要在applicationContext中配置数据库

spring.datasource.name=imooc_mall_datasource
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/imooc_mall?useUnicode=true&characterEncoding=utf8&autoReconnect=true&useSSL=false&serverTimezone=Asia/Shanghai
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=root

可以成功运行

引入注解扫描

接下来编写控制层和服务层

实现接口

接下来添加mapper配置文件的位置

控制层

运行并访问

但是接下来我们发现程序爆红

但是程序可以运行

这是因为在主类中

我们把信息只告诉了mybatis却没有告诉idea

结下案例就是为所有的dao中的接口加上注解

资源注解就可以了

可以发现报错解除

配置log4j2日志组件

首先排除logback依赖

然后引入自身依赖

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

接下来对日志文件进行配置

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="fatal">
  <Properties>
    <Property name="baseDir" value="${sys:user.home}/logs"/>
  </Properties>

  <Appenders>
    <Console name="Console" target="SYSTEM_OUT">
      <!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch) -->
      <ThresholdFilter level="info" onMatch="ACCEPT"
        onMismatch="DENY"/>
      <PatternLayout
        pattern="[%d{MM:dd HH:mm:ss.SSS}] [%level] [%logger{36}] - %msg%n"/>
    </Console>

    <!--debug级别日志文件输出-->
    <RollingFile name="debug_appender" fileName="${baseDir}/debug.log"
      filePattern="${baseDir}/debug_%i.log.%d{yyyy-MM-dd}">
      <!-- 过滤器 -->
      <Filters>
        <!-- 限制日志级别在debug及以上在info以下 -->
        <ThresholdFilter level="debug"/>
        <ThresholdFilter level="info" onMatch="DENY" onMismatch="NEUTRAL"/>
      </Filters>
      <!-- 日志格式 -->
      <PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n"/>
      <!-- 策略 -->
      <Policies>
        <!-- 每隔一天转存 -->
        <TimeBasedTriggeringPolicy interval="1" modulate="true"/>
        <!-- 文件大小 -->
        <SizeBasedTriggeringPolicy size="100 MB"/>
      </Policies>
    </RollingFile>

    <!-- info级别日志文件输出 -->
    <RollingFile name="info_appender" fileName="${baseDir}/info.log"
      filePattern="${baseDir}/info_%i.log.%d{yyyy-MM-dd}">
      <!-- 过滤器 -->
      <Filters>
        <!-- 限制日志级别在info及以上在error以下 -->
        <ThresholdFilter level="info"/>
        <ThresholdFilter level="error" onMatch="DENY" onMismatch="NEUTRAL"/>
      </Filters>
      <!-- 日志格式 -->
      <PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n"/>
      <!-- 策略 -->
      <Policies>
        <!-- 每隔一天转存 -->
        <TimeBasedTriggeringPolicy interval="1" modulate="true"/>
        <!-- 文件大小 -->
        <SizeBasedTriggeringPolicy size="100 MB"/>
      </Policies>
    </RollingFile>

    <!-- error级别日志文件输出 -->
    <RollingFile name="error_appender" fileName="${baseDir}/error.log"
      filePattern="${baseDir}/error_%i.log.%d{yyyy-MM-dd}">
      <!-- 过滤器 -->
      <Filters>
        <!-- 限制日志级别在error及以上 -->
        <ThresholdFilter level="error"/>
      </Filters>
      <!-- 日志格式 -->
      <PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n"/>
      <Policies>
        <!-- 每隔一天转存 -->
        <TimeBasedTriggeringPolicy interval="1" modulate="true"/>
        <!-- 文件大小 -->
        <SizeBasedTriggeringPolicy size="100 MB"/>
      </Policies>
    </RollingFile>
  </Appenders>
  <Loggers>
    <Root level="debug">
      <AppenderRef ref="Console"/>
      <AppenderRef ref="debug_appender"/>
      <AppenderRef ref="info_appender"/>
      <AppenderRef ref="error_appender"/>
    </Root>

  </Loggers>
</Configuration>

AOP统一打印请求和返回信息

首先引入依赖

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
            <version>2.7.0</version>
        </dependency>

接下来建一个过滤器对请求进行拦截和打印新建一个filter包

package com.imooc.mall.filter;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;

/**
 * 描述:  打印请求和响应信息
 */
@Aspect
@Component
public class WebLogAspect {
    private final Logger log= LoggerFactory.getLogger(WebLogAspect.class);
    @Pointcut("execution(public * com.imooc.mall.controller.*.*(..))")
    public void weblog(){

    }
    /*
    请求之前信息
     */
    @Before("weblog()")
    public void doBefore(JoinPoint joinPoint){
        //收到请求记录请求内容
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        log.info("URL:" + request.getRequestURL().toString());
        log.info("HTPP_METHOD:" + request.getMethod());
        log.info("IP:" + request.getRemoteAddr());
        log.info("CLASS_METHOD:"+joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
        log.info("ARGS:" + Arrays.toString(joinPoint.getArgs()));
    }
    /*
    请求之后信息
     */
    @AfterReturning(returning = "res",pointcut = "weblog()")
    public void doAfterReturning(Object res) throws JsonProcessingException {
        //处理完成请求,返回内容
        log.info("RESPONSE:" + new ObjectMapper().writeValueAsString(res));

    }
}

接下来测试一下

打印成功

用户模块开发

接口文档地址

慕慕生鲜接口文档

枚举的应用

API统一返回对象

打开controller编写用户注册接口

看一下返回示例

接下来新建common包用来编写通用类

返回信息

package com.imooc.mall.common;

/**
 * 描述:  通用返回对象
 */
public class ApiRestResponse<T> {
    private Integer status;
    private String msg;
    private T data;
    private static final int OK_CODE = 10000;
    private static final String OK_MSG = "SUCCESS";

    public Integer getStatus() {
        return status;
    }l

    public void setStatus(Integer status) {
        this.status = status;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }

    public ApiRestResponse(Integer status, String msg, T data) {
        this.status = status;
        this.msg = msg;
        this.data = data;
    }

    public ApiRestResponse(Integer status, String msg) {
        this.status = status;
        this.msg = msg;
    }
    //无参构造函数,不需要传递任何信息使用默认信息
    public ApiRestResponse() {
        this(OK_CODE,OK_MSG);
    }
    //返回返回构造函数的信息也就是返回默认方法
    public static <T> ApiRestResponse<T> success(){
        return new ApiRestResponse<>();
    }
    //返回数据成功的方法
    public static <T> ApiRestResponse<T> success(T result){
        ApiRestResponse<T> response = new ApiRestResponse<>();
        response.setData(result);
        return response;
    }
    //返回数据失败的方法
    public static <T> ApiRestResponse<T> error(Integer code, String msg){
        return new ApiRestResponse<>(code,msg);
    }
    //新建一个枚举类把错误都收拢在一起
    
}

因为错误有许多种,每次写都很麻烦我们可以建一个枚举类用来存储错误信息

首先新建一个异常包再新建一个异常枚举类

package com.imooc.mall.exception;

public enum ImoocMallExceptionEnum {
    NEED_USER_NAME(10001,"用户名不能为空");
    /**
     * 异常码
     */
    Integer code;
    /**
     * 异常信息
     */
    String msg;

    ImoocMallExceptionEnum(Integer code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }
}

接下来再写一个通用类返回对象中的error方法返回刚才所编写的枚举类并且加上tostring方法

package com.imooc.mall.common;

import com.imooc.mall.exception.ImoocMallExceptionEnum;

/**
 * 描述:  通用返回对象
 */
public class ApiRestResponse<T> {
    private Integer status;
    private String msg;
    private T data;
    private static final int OK_CODE = 10000;
    private static final String OK_MSG = "SUCCESS";

    public Integer getStatus() {
        return status;
    }

    public void setStatus(Integer status) {
        this.status = status;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }

    public ApiRestResponse(Integer status, String msg, T data) {
        this.status = status;
        this.msg = msg;
        this.data = data;
    }

    public ApiRestResponse(Integer status, String msg) {
        this.status = status;
        this.msg = msg;
    }
    //无参构造函数,不需要传递任何信息使用默认信息
    public ApiRestResponse() {
        this(OK_CODE,OK_MSG);
    }
    //返回返回构造函数的信息也就是返回默认方法
    public static <T> ApiRestResponse<T> success(){
        return new ApiRestResponse<>();
    }
    //返回数据成功的方法
    public static <T> ApiRestResponse<T> success(T result){
        ApiRestResponse<T> response = new ApiRestResponse<>();
        response.setData(result);
        return response;
    }
    //返回数据失败的方法
    public static <T> ApiRestResponse<T> error(Integer code, String msg){
        return new ApiRestResponse<>(code,msg);
    }
    //返回数据失败的方法
    public static <T> ApiRestResponse<T> error(ImoocMallExceptionEnum ex){
        return new ApiRestResponse<>(ex.getCode(),ex.getMsg());
    }
    //新建一个枚举类把错误都收拢在一起

    @Override
    public String toString() {
        return "ApiRestResponse{" +
                "status=" + status +
                ", msg='" + msg + '\'' +
                ", data=" + data +
                '}';
    }
}

注册接口开发

首先在错误泛型中加上密码不能为空的错误

再加上一个密码过短的泛型

接下来在userService中编写一个注册方法

然后在实现类中实现出来

然后在userMapper接口中编写通过名称查询的方法

爆红是因为还没有在对应的xml文件中进行描述

接下来返回接口发现selectByName方法可以使用了

接下来转向实体类

因为是在实体类没有在控制层所以我们不能直接返回自定义的错误对象所以我们可以用抛出异常的方法这里我们可以自定义一个错误异常

自定义异常类

在异常包中再自定义一个异常类

package com.imooc.mall.exception;

/**
 * 统一异常
 */
public class ImoocMallException extends Exception{
    private final  Integer code;
    private final String message;

    public ImoocMallException(Integer code, String message) {
        this.code = code;
        this.message = message;
    }
    public ImoocMallException(ImoocMallExceptionEnum exceptionEnum){
        this(exceptionEnum.getCode(),exceptionEnum.getMsg());
    }

    public Integer getCode() {
        return code;
    }

    @Override
    public String getMessage() {
        return message;
    }

    @Override
    public String toString() {
        return "ImoocMallException{" +
                "code=" + code +
                ", message='" + message + '\'' +
                '}';
    }
}

接下来回到实体类

抛出之前写的实体类同时还需要再自定义一个泛型异常提示不能重名在ImoocMallExceptionEnum中

接着在刚才实体类的构造函数中填入这个异常并且抛出异常

接下来接着编写实体类

对于返回count为0时的异常我们需要在异常类中再次添加枚举

接下来在异常中将其抛出

接下来回到控制层

如果前面判断没有返回异常的话就调用服务层的注册方法完成注册接下来断点调试运行一下

用postman模拟一下

如图所示发送请求发现返回成功

接下来打开数据库去看一下

发现添加成功

接下来看一下用户名为空会发生什么

接下来我们统一返回格式将信息隐藏起来

GlobalExceptionHandler

在异常包中新建一个类GlobalException

首先编写一个返回系统异常的方法

接下来去异常类中添加系统异常的枚举

接下来通过返回通用类来返回这个异常并且打印出日志

接下来看一下业务异常

启动程序看一下是否起作用

可以看到现在返回的是三个字段

java异常体系

对密码进行md5保护

首先新建工具包和md5工具类

编写代码虽然我看不懂他在干什么

但是这种md5加密方式公开的算法是可以被破解的

接下来我们进行加盐,在这个明文的基础上随机添加一些难以破解的值

在common中新建Constant常量类

定义不可修改的全局变量盐值然后在md5工具中在密码后面加上盐

再次运行查看结果

接下来回到用户实体类对注册的结果进行加密

接下来重新启动程序观察是否加密成功后可以加入到数据库

登录接口开发

首先在·控制层编写loginIn方法并在里面加上用户密码账户校验

再回到实体类,首先将传入的密码进行加密后再与数据库进行比较

接下来去mapper层编写查询密码的方法

接下来编写xml

新增密码错误的枚举类

编写实现类

在实现类上方添加Override注解然后alt+回车选择第四个选项把实现类提取到service

接下来在控制层调用

如果直接用success返回对象是不行的相当于把密码直接返回给了对方

接下来在定义一个session中的key

然后用key设置对象

到此登录接口就开发完毕了

用户模块剩余接口开发

我们来获取session看看session存进去了没有

更新个人信息接口

首先写user控制层定义新方法

报错枚举类用户未登录

接下来要到实体类中书写update方法

在枚举类中添加更新·失败的枚举

编写接口实现实体类

接下来回到控制层调用实体类方法并且抛出异常

接下来测试一下update

更新成功

再次登录也返回了个性签名

接下来·编写登出接口

接下来编写管理员登录接口

现在service层判断是否是管理员并且实现实体类

编写无管理员权限错误枚举

编写控制层

测试一下

商品分类模块开发

商品分类模块介绍

商品分类功能模块设计

开发添加分类接口part1

首先创建商品分类控制层

对于添加商品中的请求的参数我们可以进行再封装,是因为虽然和CATEGORY中的属性重复了但是我们查询添加的时候不需要其他格外的属性不然会被黑客恶意利用查询

package com.imooc.mall.model.request;

public class AddCategoryReq {
    private String name;
    private Integer type;
    private Integer parentId;
    private Integer orderNum;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getType() {
        return type;
    }

    public void setType(Integer type) {
        this.type = type;
    }

    public Integer getParentId() {
        return parentId;
    }

    public void setParentId(Integer parentId) {
        this.parentId = parentId;
    }

    public Integer getOrderNum() {
        return orderNum;
    }

    public void setOrderNum(Integer orderNum) {
        this.orderNum = orderNum;
    }
}

接下来编写控制层

判断名字编写名字不能为空的报错枚举类

接下来添加注解并且完善控制层不要忘记注入userService依赖

开发添加分类接口part2

创建CategoryService服务层并且创建实现类

在xml中编写按照名称查找的sql语句

编写实体类先把传入参数的属性拷贝到列表类中然后再通过名称查找并且赋值给实体类判断原先的名称是否存在如果存在则不允许重名

我们发现这里抛出异常

运行时异常我们可以改把我们创建的异常类继承运行时的异常

发现不用再抛异常了

为什么不早点说这个要坑死我啊!‘

接下来再写一个新增失败异常枚举

用于在更新失败时抛出

最后不要忘记把方法提取到service层

实现类不要忘记加上service注解

接下来回到控制层

首先要引入CategoryService

接下来启动测试一下

因为传入的参数是类所以要用RequestBody注解

登录后新增成功

@Valid注解优雅校验入参

因为spring-boot版本问题在2.3

先添加注解这里需要引入依赖

<dependency>
            <groupId>jakarta.validation</groupId>
            <artifactId>jakarta.validation-api</artifactId>
            <version>2.0.2</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>

接下来对商品列表add方法用Valid注解进行改造

然后将校验部分给注释掉

接下来回到添加类中添加注解

首先用size限制目录名称长度的大小

利用max设置层级数最大为3

接下来测试一下

但是我们发现控制台上的异常并不会在postman中返回

接下来我们回到全局异常处理类

在这里修改系统异常的返回枚举

再定义一个方法返回方法参数无效异常

package com.imooc.mall.exception;

import com.imooc.mall.common.ApiRestResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.properties.bind.BindResult;
import org.springframework.validation.BindingResult;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.ArrayList;
import java.util.List;

/**
 * 描述:  处理统一异常的handler
 */
@ControllerAdvice
public class GlobalExceptionHandler {
    private final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);
    @ExceptionHandler(Exception.class)
    @ResponseBody
    public Object handleException(Exception e){
        log.error("Default Exception:",e);
        return ApiRestResponse.error(ImoocMallExceptionEnum.SYSTEM_ERROR);
    }
    @ExceptionHandler(ImoocMallException.class)
    @ResponseBody
    public Object handleImoocMallException(ImoocMallException e){
        log.error("ImoocMallException:",e);
        return ApiRestResponse.error(e.getCode(),e.getMessage());
    }
    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseBody
    public ApiRestResponse handleMethodArgumentNotValidException(MethodArgumentNotValidException e){
        log.error("MethodArgumentNotValidException:",e);
        return handleBindingResult(e.getBindingResult());
    }
    private ApiRestResponse handleBindingResult(BindingResult result){
        //把异常处理为对外暴露的提示
        List<String> list = new ArrayList<>();
        if (result.hasErrors()){
            List<ObjectError> allErrors = result.getAllErrors();
            for (int i =0; i< allErrors.size();i++){
                ObjectError objectError = allErrors.get(i);
                String message = objectError.getDefaultMessage();
                list.add(message);
            }
        }
        if (list.size() == 0){
            return ApiRestResponse.error(ImoocMallExceptionEnum.REQUEST_PARAM_ERROR);
        }
        return  ApiRestResponse.error(ImoocMallExceptionEnum.REQUEST_PARAM_ERROR.getCode(),list.toString());
    }

}

Swagger自动生成API文档

首先引入依赖

 dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.9.2</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.9.2</version>
        </dependency>

一定要加上版本号不然注解会爆红

接下来在mian函数上加上EnableSwagger2注解

接下来新建软件包config专门存储配置文件

package com.imooc.mall.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.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;

@Configuration
public class SpringFoxConfig {

    //访问http://localhost:8083/swagger-ui.html可以看到API文档
    @Bean
    public Docket api() {
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .select()
                .apis(RequestHandlerSelectors.any())
                .paths(PathSelectors.any())
                .build();
    }

    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title("慕慕生鲜")
                .description("")
                .termsOfServiceUrl("")
                .build();
    }
}

接下来新建mvcConfig类

package com.imooc.mall.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * 描述:     配置地址映射
 */
@Configuration
public class ImoocMallWebMvcConfig implements WebMvcConfigurer {

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("swagger-ui.html").addResourceLocations(
                "classpath:/META-INF/resources/");
        registry.addResourceHandler("/webjars/**").addResourceLocations(
                "classpath:/META-INF/resources/webjars/");
    }
}

这是一件非常神奇的事情必须粘贴教程了里面的ImoocMallWebMvcConfig才可以打开swagger-ui.html

接下来用注解写配置

启动发现版本报错于是我将swagger的版本改成3.0.0

然后再配置文件中添加了一句话

spring.mvc.pathmatch.matching-strategy: ANT_PATH_MATCHER

整体配置文件如下

spring.datasource.name=imooc_mall_datasource
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/imooc_mall?useUnicode=true&characterEncoding=utf8&autoReconnect=true&useSSL=false&serverTimezone=Asia/Shanghai
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=root
mybatis.mapper-locations=classpath:mappers/*.xml
spring.mvc.pathmatch.matching-strategy: ANT_PATH_MATCHER

更新目录接口开发

首先是在控制层.

编写控制方法

然后编写请求类

package com.imooc.mall.model.request;

import javax.validation.constraints.Max;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

public class UpdateCategoryReq {
    @NotNull(message = "id不能为null")
    private Integer id;
    @Size(min = 2, max = 5)
    private String name;
    @Max(3)
    private Integer type;
    private Integer parentId;
    private Integer orderNum;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getType() {
        return type;
    }

    public void setType(Integer type) {
        this.type = type;
    }

    public Integer getParentId() {
        return parentId;
    }

    public void setParentId(Integer parentId) {
        this.parentId = parentId;
    }

    public Integer getOrderNum() {
        return orderNum;
    }

    public void setOrderNum(Integer orderNum) {
        this.orderNum = orderNum;
    }
}

然后在更新方法中传入这个控制层参数

接下来去实体类中完善更新的方法

完善控制层

运行测试

发现鸭货层级改变更新完成

测试一下重名

统一校验管理员身份

首先我们要先写过滤器

package com.imooc.mall.filter;

import com.imooc.mall.common.ApiRestResponse;
import com.imooc.mall.common.Constant;
import com.imooc.mall.exception.ImoocMallException;
import com.imooc.mall.exception.ImoocMallExceptionEnum;
import com.imooc.mall.model.pojo.User;
import com.imooc.mall.service.UserService;

import javax.annotation.Resource;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.io.PrintWriter;

public class AdminFilter implements Filter {
    @Resource
    UserService userService;
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpSession session = request.getSession();
        User currentUser = (User) session.getAttribute(Constant.IMOOC_MALL_USER);
        if (currentUser == null){
            //直接输出json格式打的未登录错误
            PrintWriter out = new HttpServletResponseWrapper((HttpServletResponse) servletResponse).getWriter();
            out.write("{\n" +
                    "    \"status\": 10007,\n" +
                    "    \"msg\": \"用户未登录\",\n" +
                    "    \"data\": null\n" +
                    "}");
            out.flush();
            out.close();
            return;
        }
        boolean adminRole = userService.checkAdminRole(currentUser);
        if (adminRole){
            filterChain.doFilter(servletRequest,servletResponse);
        }else {
            PrintWriter out = new HttpServletResponseWrapper((HttpServletResponse) servletResponse).getWriter();
            out.write("{\n" +
                    "    \"status\": 10009,\n" +
                    "    \"msg\": \"无管理员权限\",\n" +
                    "    \"data\": null\n" +
                    "}");
            out.flush();
            out.close();
            return;
        }
    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        Filter.super.init(filterConfig);
    }

    @Override
    public void destroy() {
        Filter.super.destroy();
    }
}

接下来我们需要把过滤器配置上去

在config中新建一个AdminFilterConfig

package com.imooc.mall.config;

import com.imooc.mall.filter.AdminFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.servlet.Filter;

/**
 * 描述:  Admin过滤器的配置
 */
@Configuration
public class AdminFilterConfig {
    @Bean
    public AdminFilter adminFilter(){
        return new AdminFilter();
    }
    @Bean(name = "adminFilterConf")
    public FilterRegistrationBean adminFilterConfig(){
        FilterRegistrationBean filterFilterRegistrationBean = new FilterRegistrationBean();
        filterFilterRegistrationBean.setFilter(adminFilter());
        filterFilterRegistrationBean.addUrlPatterns("/admin/category/*");
        filterFilterRegistrationBean.addUrlPatterns("/admin/produce/*");
        filterFilterRegistrationBean.setName("adminFilterConf");
        return filterFilterRegistrationBean;
    }
}

接下来在商品目录控制层先写一个删除试试先不返回任何东西

发现postman返回的数据中文出现乱码之类我在配置文件中再配置一下

spring.datasource.name=imooc_mall_datasource
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/imooc_mall?useUnicode=true&characterEncoding=utf8&autoReconnect=true&useSSL=false&serverTimezone=Asia/Shanghai
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=root
mybatis.mapper-locations=classpath:mappers/*.xml
spring.mvc.pathmatch.matching-strategy= ANT_PATH_MATCHER
#编码格式
server.servlet.encoding.force=true
server.servlet.encoding.charset=UTF-8
server.servlet.encoding.enabled=true
server.tomcat.uri-encoding=UTF-8

接下来尝试普通用户登录

接下来再尝试管理员登陆无报错返回值

删除目录接口,分页目录开发

首先去实体类编写实体类然后再提取接口

先查询是否存在

如果不存在就会返回删除失败异常,这里我们再去定义删除失败的枚举类

之后校验是否删除并且提取接口

在控制层调用接口

接下来运行尝试删除

我们删除鸭货

删除成功

接下来编写后台查询商品分类列表

首先在控制层定义方法

接下来编写实体类

接下来新建包vo

package com.imooc.mall.model.vo;

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

public class CategoryVo {
    private Integer id;

    private String name;

    private Integer type;

    private Integer parentId;

    private Integer orderNum;

    private Date createTime;

    private Date updateTime;
    private List<CategoryVo> childCategory = new ArrayList<>();

    @Override
    public String toString() {
        return "CategoryVo{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", type=" + type +
                ", parentId=" + parentId +
                ", orderNum=" + orderNum +
                ", createTime=" + createTime +
                ", updateTime=" + updateTime +
                ", childCategory=" + childCategory +
                '}';
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getType() {
        return type;
    }

    public void setType(Integer type) {
        this.type = type;
    }

    public Integer getParentId() {
        return parentId;
    }

    public void setParentId(Integer parentId) {
        this.parentId = parentId;
    }

    public Integer getOrderNum() {
        return orderNum;
    }

    public void setOrderNum(Integer orderNum) {
        this.orderNum = orderNum;
    }

    public Date getCreateTime() {
        return createTime;
    }

    public void setCreateTime(Date createTime) {
        this.createTime = createTime;
    }

    public Date getUpdateTime() {
        return updateTime;
    }

    public void setUpdateTime(Date updateTime) {
        this.updateTime = updateTime;
    }

    public List<CategoryVo> getChildCategory() {
        return childCategory;
    }

    public void setChildCategory(List<CategoryVo> childCategory) {
        this.childCategory = childCategory;
    }
}

然后去实现类中编写实现分页首先我们要引入分页工具pagehelper

<dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper-spring-boot-starter</artifactId>
            <version>1.2.13</version>
        </dependency>

接下来我们编写分页查询的mapper

接下来去xml编写sql语句

接下来回到实现类中调用这个mapper接口的方法并且把返回对象改成pageinfo

接下来回到控制层

接下来测试一下

先依赖循环报错

原因是spring-boot2.6以后被禁止了这里开启就可以了

开启

再次测试返回成功

用户分类列表接口开发

首先在控制层编写方法

接下来去实现类中编写实现方法

接着还在实现类中编写私有方法递归查找目录

接下来在mapper中新写一个方法

接着在xml中编写sql语句

接下来回到实体类编写

第二个方法是递归首先传进去父id0然后查找出来id为0的然后根据循环在查找出来第一个id为0的子类id

接下来回到控制层

启动程序看一下

利用Redis缓存加速响应

Spring-boot继承redis

首先引入依赖

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId>
        </dependency>

接下来在配置文件中对redis进行配置

spring.redis.host=localhost
spring.redis.port=6379
spring.redis.password=

在运行类上加上注解添加缓存功能

接下来为方法配置cache

注意这里的Cacheable要使用springfremarker的不要用错了

value代表key值

接下来对redis进行配置超时事件新建redis配置类

package com.imooc.mall.config;

import java.time.Duration;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory;

/**
 * 描述:     缓存的配置类
 */
@Configuration
@EnableCaching
public class CachingConfig {

    @Bean
    public RedisCacheManager redisCacheManager(RedisConnectionFactory connectionFactory) {

        RedisCacheWriter redisCacheWriter = RedisCacheWriter
                .lockingRedisCacheWriter(connectionFactory);
        RedisCacheConfiguration cacheConfiguration = RedisCacheConfiguration.defaultCacheConfig();
        cacheConfiguration = cacheConfiguration.entryTtl(Duration.ofSeconds(30));

        RedisCacheManager redisCacheManager = new RedisCacheManager(redisCacheWriter,
                cacheConfiguration);
        return redisCacheManager;
    }
}

cnmd老子改了几十遍就除了一个版本其他都给我报无法自动装入真nm恶心

在windows中启动redis

此外对于请求类还需要继承Serializable接口不然老版本会提示·序列化失败

接下来我们要为请求类继承接口这样就不会序列化失败

接下来重启程序

再次访问我们发现第一次访问还是比较慢2.87s这是因为要加载缓存

但是之后再次访问速度就缩短到了17ms

idea调试技巧

统一开关

统一开关所有断点的开关

条件断点开关

右键打的点可以为断点设置条件

进入方法内部

进入下一行

跳出方法

表达式求值

点击输入想要看到的东西

商品模块开发

添加商品接口开发

首先编写控制层创建新的控制类

接下来我们要编写请求类

将product中得复制过来然后添加get和set方法

package com.imooc.mall.model.request;

import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;

public class AddProductRq {
    @NotNull(message = "商品名称不能为空")
    private String name;
    @NotNull(message = "商品图片不能为空")
    private String image;

    private String detail;
    @NotNull(message = "商品价格不能为空")
    @Min(value = 1,message = "价格不能小于1分")
    private Integer categoryId;

    private Integer price;
    @NotNull(message = "商品库存不能为空")
    @Max(value = 10000,message = "库存不能大于10000")
    private Integer stock;

    private Integer status;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getImage() {
        return image;
    }

    public void setImage(String image) {
        this.image = image;
    }

    public String getDetail() {
        return detail;
    }

    public void setDetail(String detail) {
        this.detail = detail;
    }

    public Integer getCategoryId() {
        return categoryId;
    }

    public void setCategoryId(Integer categoryId) {
        this.categoryId = categoryId;
    }

    public Integer getPrice() {
        return price;
    }

    public void setPrice(Integer price) {
        this.price = price;
    }

    public Integer getStock() {
        return stock;
    }

    public void setStock(Integer stock) {
        this.stock = stock;
    }

    public Integer getStatus() {
        return status;
    }

    public void setStatus(Integer status) {
        this.status = status;
    }
}

接下来回到控制层继续编写填入添加类

接下来编写服务

然后编写实现类

然后我们还需要通过在productMapper中编写方法然后再xml中编写通过名字查找的sql语句

接下来完善实现类方法不要忘记提取方法到服务层我这类没写

然后再控制层调用实现类

就完成了但是我们的图片应该怎么传呢看下一节

UUID介绍

图片上传接口开发

首先在控制层编写的方法

写到如图所示的一步我们可以将上传地址放到常量中去

然后添加注解这样我们可以在配置文件中去注入

接下来回到控制层继续编写

如上图所示判断文件夹是否创建成功我们这类还需要写新的异常枚举

然后抛出新写的枚举

接下来回到控制层

如图所示返回/对象并且还要在catch中抛出图片添加失败的枚举并且还要更新枚举类

资源映射开发

首先测试一下之前写的两个接口启动时报错了原来是我的实体类忘记添加service的注解了

在这之前我发现不需要管理员登录就可以直接操作了这是不对的所以我还需要修改过滤器的配置文件

为admin/product也增加管理员校验

此时我们需要先登陆一下

登录管理员后添加成功

接下来测试一下上传图片接口

点击发送报错系统异常

图片大小超了

那我换一个小的试试艹

然后又报错了

空指针异常

这里传值没有传进去

因为他是一个静态变量普通方式处理是无法注入的

接下来我们用set方法注入静态变量

但是还是还是空值发现我没有加注解

加上注解后可以了但是报错文件夹创建失败

我猜测应该是我传入的地址的斜杠弄反了

改过来再试试最后面还要加上斜杠

如图所示图片上传成功

接下来我打开返回的地址发现是404

这里要说一下自定义静态资源映射目录

接下来我们要进行配置打开webmvc资源配置类新加一个规则

只要是imges开头的静态资源都会通过注入的地址来进行映射

接下来再次尝试一下我们发现图片已经可以访问了

更新和删除商品接口开发

首先还是先从控制层开始

然后呢我们也需要写一个请求类

package com.imooc.mall.model.request;

import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;

public class UpdateProductRq {
    @NotNull
    private Integer id;
    @NotNull(message = "商品名称不能为空")
    private String name;
    @NotNull(message = "商品图片不能为空")
    private String image;

    private String detail;
    @NotNull(message = "商品价格不能为空")
    @Min(value = 1,message = "价格不能小于1分")
    private Integer categoryId;

    private Integer price;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    @NotNull(message = "商品库存不能为空")
    @Max(value = 10000,message = "库存不能大于10000")

    private Integer stock;

    private Integer status;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getImage() {
        return image;
    }

    public void setImage(String image) {
        this.image = image;
    }

    public String getDetail() {
        return detail;
    }

    public void setDetail(String detail) {
        this.detail = detail;
    }

    public Integer getCategoryId() {
        return categoryId;
    }

    public void setCategoryId(Integer categoryId) {
        this.categoryId = categoryId;
    }

    public Integer getPrice() {
        return price;
    }

    public void setPrice(Integer price) {
        this.price = price;
    }

    public Integer getStock() {
        return stock;
    }

    public void setStock(Integer stock) {
        this.stock = stock;
    }

    public Integer getStatus() {
        return status;
    }

    public void setStatus(Integer status) {
        this.status = status;
    }
}

接下来在控制层填上请求类

补充连接然后拷贝一下请求类

接下来编写实现类

在控制层引入编写的实现类方法

编写删除控制层和实现类

接下来测试一下

首先是修改商品信息

我们可以看到名称已经被修改了接下来我们删除信息

删除成功

批量上下架商品街口开发

首先还是先写控制层

去服务实现类

然后再回到控制层先把服务类调用出来

接下来编写mapper层然后编写xml代码

说实话上面这个sql语句我还是看不懂

接下来在实体类中调用mapper

接下来测试一下

我们发现前两个的商品状态已经被修改了

后台列表商品详情接口开发

开始先从控制层入手

然后再服务类中实现这个方法

然后再去编写mapper接口的xml中的sql语句

接下来回到实体类使用分页查询包裹lsit

然后再回到控制层调用服务层的方法

接下来启动程序测试一下

测试成功

前台商品列表开发

接下来开发与前台相关的商品的接口

新建ProductController

接下来编写实现服务类

然后在控制层调用服务类方法

接下来再编写前台商品列表接口先写控制层

新建请求类

package com.imooc.mall.model.request;

import java.util.Date;

public class ProductListReq {
    private String keyword;

    private Integer categoryId;

    private String orderBy;

    private Integer pageNum=1;

    private Integer pageSize=10;

    public String getKeyword() {
        return keyword;
    }

    public void setKeyword(String keyword) {
        this.keyword = keyword;
    }

    public Integer getCategoryId() {
        return categoryId;
    }

    public void setCategoryId(Integer categoryId) {
        this.categoryId = categoryId;
    }

    public String getOrderBy() {
        return orderBy;
    }

    public void setOrderBy(String orderBy) {
        this.orderBy = orderBy;
    }

    public Integer getPageNum() {
        return pageNum;
    }

    public void setPageNum(Integer pageNum) {
        this.pageNum = pageNum;
    }

    public Integer getPageSize() {
        return pageSize;
    }

    public void setPageSize(Integer pageSize) {
        this.pageSize = pageSize;
    }
}

接下来还要编写实现类中的list方法

在进行复杂查询的时候我们可以构建一个query对象因为查询对象后期会越凌乱在这里新建一个query包然后新建一个ProductQuery类

package com.imooc.mall.query;

import java.util.List;

/**
 * 描述:  查询商品列表的query
 */
public class ProductListQuery {
    private String keyword;
    private List<Integer> categoryIds;

    public String getKeyword() {
        return keyword;
    }

    public void setKeyword(String keyword) {
        this.keyword = keyword;
    }

    public List<Integer> getCategoryIds() {
        return categoryIds;
    }

    public void setCategoryIds(List<Integer> categoryIds) {
        this.categoryIds = categoryIds;
    }
}

为什么要在query类中定义一个categoryids的列表呢

接下来回到服务类

在这个方法中我们一旦查询其中的一个商品,就要吧目录下的所有子目录的商品都查出来这时候我们可以用到之前写的商品目录实现类的递归方法

所以要先注入依赖

记下来引入这个方法

但是我们又发现了一个问题原来的方法传入的parentId的值固定为0所以这里我们要对原来的方法进行重构

首先相比较之前加入的parentId参数然后接口也要改一下然后就是控制层调用时要传入id=0

然后在商品列表接口对应得到实现类引用的方法中传入商品目录的id

但是我们得到的一个关于请求类中的目录id的列表是一个树状结构还需要平铺展开

如图所示接下来我们需要编写一个新的方法

完成方法后将获取的id的列表放到query中这个时候我们的query对象就构建完毕了

然后呢我们还要进行排序

首先获取传入请求类的排序号

接下来在Constan类中定义排序

虽然我也不知道是干啥的

接下来回到实现类进行过滤

过滤之后需要调用mapper的查询接口select这个还没有写现在来写一下

解下来来写查找sql

 <select id="selectList" resultMap="BaseResultMap" parameterType="com.imooc.mall.query.ProductListQuery">
    select
    <include refid="Base_Column_List"/>
    from imooc_mall_product
    <where>
        <if test="query.keyword != null">
          and name like #{query.keyword}
        </if>
        <if test="query.categoryIds != null">
        and category_id in
        <foreach collection="query.categoryIds" close=")" item="item" open="(" separator=",">
            #{item}
        </foreach>
        </if>
        and status = 1
    </where>
    order by update_time desc
  </select>

接下来调用

杰西莱将整个方法提取到服务层接口

然后在控制层中调用

接下来测试一下

前台商品详情查询成功

杰西莱我们按照价格排序试一下

可以看到价格递减排列

接下来尝试一下关键字查找

因为我们把车厘子下架了所以搜索不到

接下来搜索橙子

搜索成功

接下来查找目录

不要忘记打开redis

总数为18

购物车模块开发

购物车模块介绍

用户过滤器开发

首先新建控制层cartController

接下来是验证用户登录才能进入购物车接下来我们需要对用户进行校验

创建过滤器UserFilter

package com.imooc.mall.filter;

import com.imooc.mall.common.Constant;
import com.imooc.mall.model.pojo.User;

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

/**
 * 用户过滤器
 */
public class UserFilter implements Filter {
    public static User currentUser;
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        Filter.super.init(filterConfig);
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpSession session = request.getSession();
        currentUser = (User) session.getAttribute(Constant.IMOOC_MALL_USER);
        if (currentUser ==null){
            PrintWriter out = new HttpServletResponseWrapper((HttpServletResponse) servletResponse).getWriter();
            out.write("{\n" +
                    "    \"status\": 1000,\n" +
                    "    \"msg\": \"需要登录\",\n" +
                    "    \"data\": null\n" +
                    "}");
            out.flush();
            out.close();
            return;
        }
        filterChain.doFilter(servletRequest,servletResponse);
    }

    @Override
    public void destroy() {
        Filter.super.destroy();
    }
}

接下来编写配置文件

package com.imooc.mall.config;

import com.imooc.mall.filter.UserFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * 描述:  User过滤器配置
 */
@Configuration
public class UserFilterConfig {
    @Bean
    public UserFilter userFilter(){
        return new UserFilter();
    }
    @Bean(name = "UserFilterConf")
    public FilterRegistrationBean userFilterConfig(){
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
        filterRegistrationBean.setFilter(userFilter());
        filterRegistrationBean.addUrlPatterns("/cart/*");
        filterRegistrationBean.addUrlPatterns("/order/*");
        filterRegistrationBean.setName("userFilterConf");
        return filterRegistrationBean;
    }
}

然后返回可控制层在注解上规定url必须包含那些字符串

先对于添加方法返回null查看过滤器是否能够拦截

如图所示拦截成功

添加商品接口开发

服务类

实现类

在这里说明一下前端为什么要返回list而不是添加完成后就完事了这是因为如果添加完成后再次查询还是要掉一次接口这里直接返回这个list就不用调了

前端返回列表需要返回对象所以新建实体类

package com.imooc.mall.model.vo;

/**
 * 描述 :给前端展示用
 */
public class CartVo {
    private Integer id;

    private Integer productId;

    private Integer userId;

    private Integer quantity;

    private Integer selected;

    private Integer price;

    private Integer totalPrice;

    private String productName;

    private String productImage;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public Integer getProductId() {
        return productId;
    }

    public void setProductId(Integer productId) {
        this.productId = productId;
    }

    public Integer getUserId() {
        return userId;
    }

    public void setUserId(Integer userId) {
        this.userId = userId;
    }

    public Integer getQuantity() {
        return quantity;
    }

    public void setQuantity(Integer quantity) {
        this.quantity = quantity;
    }

    public Integer getSelected() {
        return selected;
    }

    public void setSelected(Integer selected) {
        this.selected = selected;
    }

    public Integer getPrice() {
        return price;
    }

    public void setPrice(Integer price) {
        this.price = price;
    }

    public Integer getTotalPrice() {
        return totalPrice;
    }

    public void setTotalPrice(Integer totalPrice) {
        this.totalPrice = totalPrice;
    }

    public String getProductName() {
        return productName;
    }

    public void setProductName(String productName) {
        this.productName = productName;
    }

    public String getProductImage() {
        return productImage;
    }

    public void setProductImage(String productImage) {
        this.productImage = productImage;
    }
}

定义上下架状态和枚举类

编写方法验证添加是否合法

判断完是否可以添加之后接着判断商品是否在购物车中

编写CartMapper查询购物车中的商品数量.

编写购物车是够被选中的枚举

完成剩余实体类方法

 @Override
    public List<CartVo> add(Integer userId, Integer productId, Integer count){
        validProduct(productId,count);
        Cart cart = cartMapper.selectCartByUserIdAndProductId(userId, productId);
        if (cart==null){
            //这个商品不在购物车里需要新增一个记录
            cart = new Cart();
            cart.setProductId(productId);
            cart.setUserId(userId);
            cart.setQuantity(count);
            cart.setSelected(Constant.Cart.CHECKED);
            cartMapper.insertSelective(cart);
        } else {
            //商品在购物车中已经存在进行数量叠加
            count=cart.getQuantity() + count;
            Cart cartNew = new Cart();
            cartNew.setQuantity(count);
            cartNew.setId(cart.getId());
            cartNew.setProductId(cart.getProductId());
            cartNew.setUserId(cart.getUserId());
            cart.setSelected(Constant.Cart.CHECKED);
            System.out.println(cart);
            int i = cartMapper.updateByPrimaryKeySelective(cartNew);
            System.out.println(i);
        }
        return this.list(userId);
    }

不要忘记提取方法到列表注解也不要忘记

因为列表还没有写所以这里返回值先设置为空之后再补上先测试一下能否添加成功

首先回到控制层注入依赖然后调用

接下来测试一下接口

登录后添加成功

查看数据库

购物车列表

控制层

 @GetMapping("/list")
    @ApiOperation("购物车列表")
    public ApiRestResponse list(){
        //内部获取用户id防止横向越权
        List<CartVo> list = cartService.list(UserFilter.currentUser.getId());
        return ApiRestResponse.success(list);

    }

实现类

@Override
    public List<CartVo> list(Integer userId){
        List<CartVo> cartVos = cartMapper.selectList(userId);
        //计算总价格
        for (CartVo cartVo:cartVos){
            cartVo.setTotalPrice(cartVo.getPrice() * cartVo.getQuantity());
        }
        return cartVos;
    }

实现类中的格外方法

枚举类

mapper层的xml方法

<select id="selectList" resultType="com.imooc.mall.model.vo.CartVo" parameterType="java.lang.Integer">
    select
    c.id as id,
    p.id as productId,
    c.user_id as userId,
    c.quantity as quantity,
    c.selected as selected,
    p.price as price,
    p.name as productName,
    p.image as productImage
    from imooc_mall_cart c
    left join imooc_mall_product p on p.id = c.product_id
    where c.user_id = #{userId}
    and p.status = 1
  </select>

既然写好了list我们就可以在之前的添加方法中返回这个list

之前的返回类型我写成CategoryVo了现在已经改回来了

然后回到控制层修改

接下来测试一下

两个接口都可以使用

更新购物车接口开发

控制层

@PostMapping
    @ApiOperation("更新购物车商品数量")
    public ApiRestResponse update(@RequestParam Integer productId,@RequestParam Integer count){
        List<CartVo> update = cartService.update(UserFilter.currentUser.getId(), productId, count);
        return ApiRestResponse.success(update);
    }

实现类

 @Override
    public List<CartVo> update(Integer userId,Integer productId,Integer count){
        validProduct(productId,count);
        Cart cart = cartMapper.selectCartByUserIdAndProductId(userId,productId);
        if (cart==null){
            //这个商品之前不在购物车里无法更新
            throw new ImoocMallException(ImoocMallExceptionEnum.CART_PRODUCT_NOT_EXIT);
        }else {
            Cart cartNew = new Cart();
            cartNew.setQuantity(count);
            cartNew.setId(cart.getId());
            cartNew.setProductId(cart.getProductId());
            cartNew.setUserId(cart.getUserId());
            cartNew.setSelected(Constant.Cart.CHECKED);
            cartMapper.updateByPrimaryKeySelective(cartNew);
        }
        return this.list(userId);

    }

mapper接口对应的xml

枚举类

CART_PRODUCT_NOT_EXIT(10019,"购物车中商品不存在"),

更新接口测试可用

删除接口

控制层

 @PostMapping("/delete")
    @ApiOperation("删除购物车中的商品")
    public ApiRestResponse delete(@RequestParam Integer productId){
        List<CartVo> cartVoList = cartService.delete(UserFilter.currentUser.getId(), productId);
        return ApiRestResponse.success(cartVoList);
    }

实体类

@Override
    public List<CartVo> delete(Integer userId, Integer productId){
        Cart cart = cartMapper.selectCartByUserIdAndProductId(userId,productId);
        if (cart == null){
            throw new ImoocMallException(ImoocMallExceptionEnum.CART_PRODUCT_NOT_EXIT);
        }else {
            int count = cartMapper.deleteByPrimaryKey(cart.getId());
            if (count == 0){
                throw new ImoocMallException(ImoocMallExceptionEnum.DELETE_FAILED);
            }
            return this.list(userId);
        }
    }

枚举类

17被成功删除删除接口可用

选中购物车相关接口开发

控制层

 @PostMapping("/select")
    @ApiOperation("选择/不选中购物车的某商品")
    public ApiRestResponse select(@RequestParam Integer productId,@RequestParam Integer selected) {
        List<CartVo> cartVos = cartService.selectOrNot(UserFilter.currentUser.getId(), productId, selected);
        return ApiRestResponse.success(cartVos);
    }

服务层

@Override
    public List<CartVo> selectOrNot(Integer userId,Integer productId,Integer selected){
        Cart cart = cartMapper.selectCartByUserIdAndProductId(userId,productId);
        if (cart == null){
            throw new ImoocMallException(ImoocMallExceptionEnum.UPDATE_FAILED);
        } else {
            cartMapper.selectOrNot(userId,productId,selected);
        }
        return this.list(userId);
    }

mapper接口对应的xml

 <update id="selectOrNot"  parameterType="map">
    update imooc_mall_cart
    set selected = #{selected}
    where user_id = #{userId}
    <if test="productId!=null">
      and product_id = #{productId}
    </if>
  </update>

发现selected变为0

全选择或者全不选择某购物车的商品

控制层

  @PostMapping("/selectAll")
    @ApiOperation("全选/全不选则购物车的某商品")
    public ApiRestResponse selectAll(@RequestParam Integer selected){
        List<CartVo> cartVos = cartService.selectAllOrNor(UserFilter.currentUser.getId(), selected);
        return ApiRestResponse.success(cartVos);
    }

服务层

 @Override
    public List<CartVo> selectAllOrNor(Integer userId,Integer selected){
        //改变选中状态
        cartMapper.selectOrNot(userId,null,selected);
        return this.list(userId);

    }

mapper对应的xml

跟上一个单独选中一样

测试成功

订单模块开发

创建订单接口开发

首先新建订单控制器

控制层

@PostMapping("order/create")
    @ApiOperation("创建订单")
    public ApiRestResponse create(@RequestBody @Valid CreateOrderReq createOrderReq){
        String orderNo = orderService.create(createOrderReq);
        return ApiRestResponse.success(orderNo);
    }

实现类

@Override
    public String create(CreateOrderReq createOrderReq){
        //拿到用户ID
        Integer userId = UserFilter.currentUser.getId();
        //从购物车查找已经勾选的商品
        List<CartVo> cartVoList = cartService.list(userId);
        List<CartVo> cartVoListTemp = new ArrayList<>();
        Iterator iterator = cartVoList.iterator();
        while (iterator.hasNext()) {
            CartVo cartVo = (CartVo) iterator.next();
            if (cartVo.getSelected().equals(Constant.Cart.CHECKED)){
                cartVoListTemp.add(cartVo);
            }

        }
        //如果购物车已勾选为空,报错
        cartVoList = cartVoListTemp;
        if (CollectionUtils.isEmpty(cartVoList)){
            throw new ImoocMallException(ImoocMallExceptionEnum.CART_EMPTY);
        }
        //判断商品是否存在,上下架状态,库存
        vaildSaleStatusAndStock(cartVoList);
        //把购物车对象转化为订单item对象
        List<OrderItem> orderItemList = cartVoListToOrderItemList(cartVoList);
        for (OrderItem orderItem:orderItemList){
            Product product = productMapper.selectByPrimaryKey(orderItem.getProductId());
            Integer stock = product.getStock() - orderItem.getQuantity();
            if (stock <0 ){
                throw new ImoocMallException(ImoocMallExceptionEnum.NOT_ENOUGH);
            }
            product.setStock(stock);
            productMapper.updateByPrimaryKeySelective(product);
        }
        //把购物车中已勾选的商品删除
        cleanCart(cartVoList);
        //生成订单
        Order order = new Order();
        //生成订单号,有独立的规则
        String orderNo = OrderCodeFactory.getOrderCode(Long.valueOf(userId));
        order.setOrderNo(orderNo);
        order.setUserId(userId);
        order.setTotalPrice(totalPrice(orderItemList));
        order.setReceiverName(createOrderReq.getReceiverName());
        order.setReceiverMobile(createOrderReq.getReceiverMobile());
        order.setReceiverAddress(createOrderReq.getReceiverAddress());
        order.setOrderStatus(Constant.OrderStatusEnum.NOT_PAID.getCode());
        order.setPostage(0);//包邮
        order.setPaymentType(1);//在线付款方式
        //插入到order表中
        orderMapper.insertSelective(order);
        //循环保存每个商品到order——item表
        for (OrderItem orderItem:orderItemList){
            orderItem.setOrderNo(order.getOrderNo());
            orderItemMapper.insertSelective(orderItem);
        }
        //把结果返回
        return orderNo;
    }

实现类中的功能函数

private void vaildSaleStatusAndStock(List<CartVo> cartVoList) {
        for (CartVo cartVo:cartVoList){
            Product product = productMapper.selectByPrimaryKey(cartVo.getProductId());
            //判断商品是否存在,商品是否上架
            if (product == null||product.getStatus().equals(Constant.SaleStatus.NOT_SALE)){
                throw new ImoocMallException(ImoocMallExceptionEnum.NOT_SELL);
            }
            //判断商品库存
            if (cartVo.getQuantity()>product.getStock()){
                throw new ImoocMallException(ImoocMallExceptionEnum.NOT_ENOUGH);
            }
        }
private List<OrderItem> cartVoListToOrderItemList(List<CartVo> cartVoList) {
        List<OrderItem> orderItemList = new ArrayList<>();
        for (CartVo cartVo:cartVoList){
            OrderItem orderItem =new OrderItem();
            orderItem.setProductId(cartVo.getProductId());
            //记录商品快照信息
            orderItem.setProductName(cartVo.getProductName());
            orderItem.setProductImg(cartVo.getProductImage());
            orderItem.setUnitPrice(cartVo.getPrice());
            orderItem.setQuantity(cartVo.getQuantity());
            orderItem.setTotalPrice(cartVo.getTotalPrice());
            orderItemList.add(orderItem);

        }
        return orderItemList;
    }
private Integer totalPrice(List<OrderItem> orderItemList) {
        Integer totalPrice = 0;
   
        for (OrderItem orderitem:orderItemList){
            totalPrice += orderitem.getTotalPrice();
        }
        return totalPrice;
    }
public void cleanCart(List<CartVo> cartVoList){
        for (CartVo cartVo:cartVoList){
            cartMapper.deleteByPrimaryKey(cartVo.getId());
        }
    }

枚举类

package com.imooc.mall.exception;

public enum ImoocMallExceptionEnum {
    NEED_USER_NAME(10001,"用户名不能为空"),
    NEED_PASSWORD(10002,"密码不能为空"),
    PASSWORD_TOO_SHORT(10003,"密码长度不能小于8位"),
    NAME_EXISTD(10004,"不允许重名"),
    INSERT_FAILED(10005,"插入失败,请重试"),
    WRONG_PASSWORD(10006,"用户名或密码错误"),
    NEED_LOGIN(10007,"用户未登录"),
    UPDATE_FAILED(10008,"更新失败"),
    NEED_ADMIN(10009,"无管理员权限"),
    PARA_NOT_NULL(10010,"参数不能为空"),
    CREATE_FAILED(10011,"新增失败"),
    REQUEST_PARAM_ERROR(10012,"参数错误"),
    DELETE_FAILED(10013,"删除失败"),
    MKDIR_FAILED(10014,"文件夹创建失败"),
    UPLOAD_FAILED(10015,"图片上传失败"),
    PRODUCT_NOT_EXIT(10016,"商品不存在"),
    NOT_SELL(10017,"商品状态不可售"),
    NOT_ENOUGH(10018,"商品库存不足"),
    CART_PRODUCT_NOT_EXIT(10019,"购物车中商品不存在"),
    CART_EMPTY(10020,"购物车已勾选的商品为空"),
    NO_ENUM(10020,"未找到对应的枚举类"),
    SYSTEM_ERROR(20000,"系统出现异常");

    /**
     * 异常码
     */
    Integer code;
    /**
     * 异常信息
     */
    String msg;

    ImoocMallExceptionEnum(Integer code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }
}
CART_EMPTY(10020,"购物车已勾选的商品为空"),
package com.imooc.mall.common;

import com.google.common.collect.Sets;
import com.imooc.mall.exception.ImoocMallException;
import com.imooc.mall.exception.ImoocMallExceptionEnum;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.util.Set;

/**
 * 常量类
 */
@Component
public class Constant {
    public static final String IMOOC_MALL_USER="IMOOC_MALL_USER";

    public static final String SALT = "ndksan..asdhibghuisabyuguihybageshi无限操你妈fka...sjnfasjnf";

    public static String FILE_UPLOAD_DIR;

    @Value("${file.upload.dir}")
    public void setFileUploadDir(String fileUploadDir){
        FILE_UPLOAD_DIR =fileUploadDir;
    }
    public interface ProductListOrderBy{
        Set<String> PRICE_ASC_DESC = Sets.newHashSet("price desc","price asc");
    }
    public interface SaleStatus{
        int NOT_SALE=0;//商品下架状态
        int SALE=1;//商品上架状态

    }
    public interface Cart{
        int UN_CHECKED=0;//购物车商品未被选中
        int CHECKED=1;//购物车商品被选中

    }
    public enum OrderStatusEnum{
        CANCELED(0,"用户已取消"),
        NOT_PAID(10,"未付款"),
        PAID(20,"已付款"),
        DELIVERED(30,"已发货"),
        FINISHED(40,"交易完成");
        private String value;
        private int code;
        OrderStatusEnum(int code,String value){
            setValue(value);
            setCode(code);
        }
        public static OrderStatusEnum codeOf(int code){
            for (OrderStatusEnum orderStatusEnum:values()){
                if (orderStatusEnum.getCode() == code){
                    return orderStatusEnum;
                }
            }
            throw new ImoocMallException(ImoocMallExceptionEnum.NO_ENUM);
        }
        public String getValue() {
            return value;
        }

        public void setValue(String value) {
            this.value = value;
        }

        public int getCode() {
            return code;
        }

        public void setCode(int code) {
            this.code = code;
        }
    }
}

mapper对应的mxl文件

订单号生成工具类

package com.imooc.mall.util;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Random;

/**
 * 生成订单号的工具类
 */
public class OrderCodeFactory {
    private static String getDateTime(){
        DateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
        return sdf.format(new Date());
    }
    private static int getRandom(Long n){
        Random random = new Random();
        //获取5位随机数
        return (int)(random.nextDouble() *(90000))+10000;
    }
    public static String getOrderCode(Long userId){
        return getDateTime() + getRandom(userId);
    }
}

请求类

package com.imooc.mall.model.request;

import javax.validation.constraints.NotNull;

public class CreateOrderReq {
    @NotNull
    private String receiverName;
    @NotNull
    private String receiverMobile;
    @NotNull
    private String receiverAddress;

    private Integer postage = 0;

    private Integer paymentType = 1;

    public String getReceiverName() {
        return receiverName;
    }

    public void setReceiverName(String receiverName) {
        this.receiverName = receiverName;
    }

    public String getReceiverMobile() {
        return receiverMobile;
    }

    public void setReceiverMobile(String receiverMobile) {
        this.receiverMobile = receiverMobile;
    }

    public String getReceiverAddress() {
        return receiverAddress;
    }

    public void setReceiverAddress(String receiverAddress) {
        this.receiverAddress = receiverAddress;
    }

    public Integer getPostage() {
        return postage;
    }

    public void setPostage(Integer postage) {
        this.postage = postage;
    }

    public Integer getPaymentType() {
        return paymentType;
    }

    public void setPaymentType(Integer paymentType) {
        this.paymentType = paymentType;
    }
}

测试接口

数据库事务

在上个方法上面添加一个注解就可以了

订单详情

控制层

@GetMapping("order/detail")
    @ApiOperation("前台订单详情")
    public ApiRestResponse create(@RequestParam String orderNo){
        orderService.detail(orderNo);
        return ApiRestResponse.success(orderNo);
    }

服务层实现类

@Override
    public OrderVo detail(String orderNo){
        Order order = orderMapper.selectByOrderNo(orderNo);
        //订单不存在则报错
        if (order == null){
            throw new ImoocMallException(ImoocMallExceptionEnum.NO_ORDER);
        }
        //订单存在,需要判断所属
        Integer userId = UserFilter.currentUser.getId();
        if (!order.getUserId().equals(userId)){
            throw new ImoocMallException(ImoocMallExceptionEnum.NOT_YOUR_ORDER);
        }
        OrderVo orderVo = getOrderVo(order);
        return orderVo;
    }

实现类中用到的方法

  public OrderVo getOrderVo(Order order){
        OrderVo orderVo = new OrderVo();
        BeanUtils.copyProperties(order,orderVo);
        //获取订单对应的orderItemVoList
        List<OrderItem> orderItems = orderItemMapper.selectByOrderNo(order.getOrderNo());
        List<OrderItemVo> orderItemVoList = new ArrayList<>();
        for (int i = 0; i < orderItems.size(); i++) {
            OrderItem orderItem = orderItems.get(i);
            OrderItemVo orderItemVo = new OrderItemVo();
            BeanUtils.copyProperties(orderItem,orderItemVo);
            orderItemVoList.add(orderItemVo);
        }
        orderVo.setOrderItemVoList(orderItemVoList);
        orderVo.setOrderStatusName(Constant.OrderStatusEnum.codeOf(orderVo.getOrderStatus()).getValue());
        return orderVo;

    }

mapper接口对应的xml语句

OrderItem

<select id="selectByOrderNo"  resultMap="BaseResultMap" parameterType="java.lang.String">
    select
    <include refid="Base_Column_List"></include>
    from imooc_mall_order_item
    where order_no  = #{orderNo}
  </select>

这个是orderMapper

 <select id="selectByOrderNo" resultMap="BaseResultMap">
    select
    <include refid="Base_Column_List"></include>
    from imooc_mall_order
    where order_no = #{orderNo}
  </select>

枚举类

NO_ORDER(10021,"订单不存在"),
NOT_YOUR_ORDER(10022,"订单不属于你"),

返回实体类

package com.imooc.mall.model.vo;

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

public class OrderVo {
    private String orderNo;

    private Integer userId;

    private Integer totalPrice;

    private String receiverName;

    private String receiverMobile;

    private String receiverAddress;

    private Integer orderStatus;

    private Integer postage;

    private Integer paymentType;

    private Date deliveryTime;

    private Date payTime;

    private Date endTime;

    private Date createTime;

    private Date updateTime;

    private String orderStatusName;

    private List<OrderItemVo> orderItemVoList;

    public String getOrderNo() {
        return orderNo;
    }

    public void setOrderNo(String orderNo) {
        this.orderNo = orderNo;
    }

    public Integer getUserId() {
        return userId;
    }

    public void setUserId(Integer userId) {
        this.userId = userId;
    }

    public Integer getTotalPrice() {
        return totalPrice;
    }

    public void setTotalPrice(Integer totalPrice) {
        this.totalPrice = totalPrice;
    }

    public String getReceiverName() {
        return receiverName;
    }

    public void setReceiverName(String receiverName) {
        this.receiverName = receiverName;
    }

    public String getReceiverMobile() {
        return receiverMobile;
    }

    public void setReceiverMobile(String receiverMobile) {
        this.receiverMobile = receiverMobile;
    }

    public String getReceiverAddress() {
        return receiverAddress;
    }

    public void setReceiverAddress(String receiverAddress) {
        this.receiverAddress = receiverAddress;
    }

    public Integer getOrderStatus() {
        return orderStatus;
    }

    public void setOrderStatus(Integer orderStatus) {
        this.orderStatus = orderStatus;
    }

    public Integer getPostage() {
        return postage;
    }

    public void setPostage(Integer postage) {
        this.postage = postage;
    }

    public Integer getPaymentType() {
        return paymentType;
    }

    public void setPaymentType(Integer paymentType) {
        this.paymentType = paymentType;
    }

    public Date getDeliveryTime() {
        return deliveryTime;
    }

    public void setDeliveryTime(Date deliveryTime) {
        this.deliveryTime = deliveryTime;
    }

    public Date getPayTime() {
        return payTime;
    }

    public void setPayTime(Date payTime) {
        this.payTime = payTime;
    }

    public Date getEndTime() {
        return endTime;
    }

    public void setEndTime(Date endTime) {
        this.endTime = endTime;
    }

    public Date getCreateTime() {
        return createTime;
    }

    public void setCreateTime(Date createTime) {
        this.createTime = createTime;
    }

    public Date getUpdateTime() {
        return updateTime;
    }

    public void setUpdateTime(Date updateTime) {
        this.updateTime = updateTime;
    }

    public String getOrderStatusName() {
        return orderStatusName;
    }

    public void setOrderStatusName(String orderStatusName) {
        this.orderStatusName = orderStatusName;
    }

    public List<OrderItemVo> getOrderItemVoList() {
        return orderItemVoList;
    }

    public void setOrderItemVoList(List<OrderItemVo> orderItemVoList) {
        this.orderItemVoList = orderItemVoList;
    }
}
package com.imooc.mall.model.vo;

public class OrderItemVo {
    private String orderNo;

    private Integer productId;

    private String productName;

    private String productImg;

    private Integer unitPrice;

    private Integer quantity;

    private Integer totalPrice;

    public String getOrderNo() {
        return orderNo;
    }

    public void setOrderNo(String orderNo) {
        this.orderNo = orderNo;
    }

    public Integer getProductId() {
        return productId;
    }

    public void setProductId(Integer productId) {
        this.productId = productId;
    }

    public String getProductName() {
        return productName;
    }

    public void setProductName(String productName) {
        this.productName = productName;
    }

    public String getProductImg() {
        return productImg;
    }

    public void setProductImg(String productImg) {
        this.productImg = productImg;
    }

    public Integer getUnitPrice() {
        return unitPrice;
    }

    public void setUnitPrice(Integer unitPrice) {
        this.unitPrice = unitPrice;
    }

    public Integer getQuantity() {
        return quantity;
    }

    public void setQuantity(Integer quantity) {
        this.quantity = quantity;
    }

    public Integer getTotalPrice() {
        return totalPrice;
    }

    public void setTotalPrice(Integer totalPrice) {
        this.totalPrice = totalPrice;
    }
}

登录后新生成一个订单测试成功

订单列表

控制层

 @GetMapping("order/list")
    @ApiOperation("前台订单")
    public ApiRestResponse list(@RequestParam Integer pageNum,@RequestParam Integer pageSize){
        PageInfo pageInfo = orderService.listForCustomer(pageNum, pageSize);
        return ApiRestResponse.success(pageInfo);

    }

实现类

 @Override
    public PageInfo listForCustomer(Integer pageNum,Integer pageSzie){
        Integer userId = UserFilter.currentUser.getId();
        PageHelper.startPage(pageNum,pageSzie);
        List<Order> orderList = orderMapper.selectForCustomer(userId);
        List<OrderVo> orderVoList = orderListToOrderVoList(orderList);
        PageInfo pageInfo = new PageInfo(orderVoList);
        pageInfo.setList(orderVoList);
        return pageInfo;

    }

实现类方法

private List<OrderVo> orderListToOrderVoList(List<Order> orderList) {
        List<OrderVo> orderVoList = new ArrayList<>();
        for (Order order:orderList){
            OrderVo orderVo = getOrderVo(order);
            orderVoList.add(orderVo);
        }
        return orderVoList;
    }
   public OrderVo getOrderVo(Order order){
        OrderVo orderVo = new OrderVo();
        BeanUtils.copyProperties(order,orderVo);
        //获取订单对应的orderItemVoList
        List<OrderItem> orderItems = orderItemMapper.selectByOrderNo(order.getOrderNo());
        List<OrderItemVo> orderItemVoList = new ArrayList<>();
        for (int i = 0; i < orderItems.size(); i++) {
            OrderItem orderItem = orderItems.get(i);
            OrderItemVo orderItemVo = new OrderItemVo();
            BeanUtils.copyProperties(orderItem,orderItemVo);
            orderItemVoList.add(orderItemVo);
        }
        orderVo.setOrderItemVoList(orderItemVoList);
        orderVo.setOrderStatusName(Constant.OrderStatusEnum.codeOf(orderVo.getOrderStatus()).getValue());
        return orderVo;
    }

取消订单接口开发

控制层

@PostMapping("order/cancel")
    @ApiOperation("前台取消订单")
    public ApiRestResponse cancel(@RequestParam String orderNo){
        orderService.cancel(orderNo);
        return ApiRestResponse.success();
    }

服务层实现类

 @Override
    public void cancel(String orderNo){
        Order order = orderMapper.selectByOrderNo(orderNo);
        //查不到订单报错
        if (order==null){
            throw new ImoocMallException(ImoocMallExceptionEnum.NO_ORDER);
        }
        //验证用户身份
        //订单存在,需要判断所属
        Integer userId= UserFilter.currentUser.getId();
        if (!order.getUserId().equals(userId)){
            throw new ImoocMallException(ImoocMallExceptionEnum.NOT_YOUR_ORDER);
        }
        if (order.getOrderStatus().equals(Constant.OrderStatusEnum.NOT_PAID.getCode())){
            order.setOrderStatus(Constant.OrderStatusEnum.CANCELED.getCode());
            order.setEndTime(new Date());
            orderMapper.updateByPrimaryKeySelective(order);
        }else {
            throw new ImoocMallException(ImoocMallExceptionEnum.WRONG_ORDER_STATUS);
        }

    }

枚举类

WRONG_ORDER_STATUS(10023,"订单状态不符"),

接口测试成功取消订单的状态变为0

二维码接口开发

需要引入依赖

<dependency>
            <groupId>com.google.zxing</groupId>
            <artifactId>javase</artifactId>
            <version>3.5.0</version>
        </dependency>

新建工具类

package com.imooc.mall.util;

import com.google.zxing.BarcodeFormat;
import com.google.zxing.WriterException;
import com.google.zxing.client.j2se.MatrixToImageWriter;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.QRCodeWriter;
import com.imooc.mall.common.Constant;

import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.Path;

/**
 * 描述:  生成二维码工具
 */
public class QRCodeGenerator {
    public static void generateQRCodeImage(String text,int width,int height,String filePath) throws WriterException, IOException {
        QRCodeWriter qrCodeWriter = new QRCodeWriter();
        BitMatrix bitMatrix = qrCodeWriter.encode(text, BarcodeFormat.QR_CODE, width, height);
        Path path = FileSystems.getDefault().getPath(filePath);
        MatrixToImageWriter.writeToPath(bitMatrix,"PNG",path);

    }

    public static void main(String[] args) throws IOException, WriterException {
        generateQRCodeImage("hello World",500,350,"D:/IdeaProject/mal/src/main/resources/images/QRTest.png");
    }
}

图片解码网址

在线生成 QR Code

接下来解码图片可以发现图片中所携带的信息

接下来回到实现类配置本地可访问的ip

控制层

@PostMapping("order/qrcode")
    @ApiOperation("生成支付二维码")
    public ApiRestResponse qrcode(@RequestParam String orderNo){
        String pngAddress= orderService.qrcode(orderNo);
        return ApiRestResponse.success(pngAddress);
    }

实现类

测试一下

访问图片并且解码

后台订单列表接口开发

新建订单后台管理控制层

控制层

@GetMapping("admin/order/list")
    @ApiOperation("管理员订单列表")
    public ApiRestResponse listForAdmin(@RequestParam Integer pageNum,@RequestParam Integer pageSize){
        PageInfo pageInfo = orderService.listForAdmin(pageNum, pageSize);
        return ApiRestResponse.success(pageInfo);
    }

实现类

@Override
    public PageInfo listForAdmin(Integer pageNum,Integer pageSize){
        PageHelper.startPage(pageNum,pageSize);
        List<Order> orderList = orderMapper.selectAllForAdmin();
        List<OrderVo> orderVoList = orderListToOrderVoList(orderList);
        PageInfo pageInfo = new PageInfo(orderList);
        pageInfo.setList(orderVoList);
        return pageInfo;

    }

mapper接口对应的xml

 <select id="selectAllForAdmin" resultMap="BaseResultMap" >
    select
    <include refid="Base_Column_List"></include>
    from imooc_mall_order
    order by create_time desc
  </select>

测试成功

支付接口开发

控制层

 @GetMapping("pay")
    @ApiOperation("支付接口")
    public ApiRestResponse pay(@RequestParam String orderNo){
        orderService.pay(orderNo);
        return ApiRestResponse.success();
    }

实现类

 @Override
    public void pay(String orderNo){
        Order order = orderMapper.selectByOrderNo(orderNo);
        //查不到订单,报错
        if (order == null){
            throw new ImoocMallException(ImoocMallExceptionEnum.NO_ORDER);
        }
        if (order.getOrderStatus() == Constant.OrderStatusEnum.NOT_PAID.getCode()){
            order.setOrderStatus(Constant.OrderStatusEnum.PAID.getCode());
            order.setPayTime(new Date());
            orderMapper.updateByPrimaryKeySelective(order);
        }else {
            throw new ImoocMallException(ImoocMallExceptionEnum.WRONG_ORDER_STATUS);
        }

    }

测试成功

管理订单接口开发

管理员发货

控制层

 /**
     * 发货。订单状态流程:0用户已取消,10未付款,20已付款
     * @return
     */
    @PostMapping("admin/order*/delivered")
    @ApiOperation("管理员发货")
    public ApiRestResponse delivered(@RequestParam String orderNo){
        orderService.deliver(orderNo);
        return ApiRestResponse.success();
    }

实现类

@Override
    public void deliver(String orderNo) {
        Order order = orderMapper.selectByOrderNo(orderNo);
        if (order == null) {
            throw new ImoocMallException(ImoocMallExceptionEnum.NO_ORDER);
        }
        if (order.getOrderStatus() == Constant.OrderStatusEnum.PAID.getCode()) {
            order.setOrderStatus(Constant.OrderStatusEnum.DELIVERED.getCode());
            order.setDeliveryTime(new Date());
            orderMapper.updateByPrimaryKeySelective(order);
        } else {
            throw new ImoocMallException(ImoocMallExceptionEnum.WRONG_ORDER_STATUS);
        }
    }

完结订单

控制层

/**
     * 完结订单+。订单状态流程:0用户已取消,10未付款,20已付款
     * @return
     */
    @PostMapping("order/finsh")
    @ApiOperation("完结订单")
    public ApiRestResponse finish(@RequestParam String orderNo){
        orderService.finish(orderNo);
        return ApiRestResponse.success();
    }

实现类

@Override
    public void finish(String orderNo){
        Order order = orderMapper.selectByOrderNo(orderNo);
        if (!userService.checkAdminRole(UserFilter.currentUser)&&!order.getUserId().equals(UserFilter.currentUser.getId())){
            throw new ImoocMallException(ImoocMallExceptionEnum.NO_ORDER);
        }
        //发货后可以完结订单
        if (order.getOrderStatus() == Constant.OrderStatusEnum.DELIVERED.getCode()){
            order.setEndTime(new Date());
            orderMapper.updateByPrimaryKeySelective(order);
        } else {
            throw new ImoocMallException(ImoocMallExceptionEnum.WRONG_ORDER_STATUS);
        }

订单完结测试

测试

测试流程

手机ip与电脑ip不同扫码无法支付那么如何让手机也可以扫码支付呢

可以获取本机局域网的ip

再次登录生成二维码发现局域网地址改变

上线与部署

首先在请求类中都加上toString方法

在原来的基础上添加生产环境配置文件

server.port=8080

spring.datasource.name=imooc_mall_datasource
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/imooc_mall?useUnicode=true&characterEncoding=utf8&autoReconnect=true&useSSL=false&serverTimezone=Asia/Shanghai
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=123456

mybatis.mapper-locations=classpath:mappers/*.xml

spring.redis.host=localhost
spring.redis.port=6379
spring.redis.password=

#上次文件的路径,根据部属情况,自行修改
file.upload.dir=/root/images/
file.upload.ip=59.110.172.187

icode=ABCDE

前后端进行地址映射

package com.imooc.mall.config;

import com.imooc.mall.common.Constant;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * 描述:     配置地址映射
 */
@Configuration
public class ImoocMallWebMvcConfig implements WebMvcConfigurer {

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/admin/**").addResourceLocations("classpath:/static/admin/");
        registry.addResourceHandler("/images/**").addResourceLocations("file:" + Constant.FILE_UPLOAD_DIR);
        registry.addResourceHandler("swagger-ui.html").addResourceLocations(
                "classpath:/META-INF/resources/");
        registry.addResourceHandler("/webjars/**").addResourceLocations(
                "classpath:/META-INF/resources/webjars/");
    }
}

加上admin

然后修改log4j2配置文件

把这里改成root

前端的ip也要换

然后全局替换ip为自己的服务器ip

服务器环境配置

首先登录到自己的服务器

首先查看一下readme文件

看一个在线文档

Introduction · JAVA环境镜像使用手册

首先修改数据库密码

登录成功最后的密码前要加上-p

接下来修改密码

创建数据库

接下来要替换服务器sql文件中的ip地址

然后把sql文件中

在服务器导入sql

接下来要对mysql地址进行授权

验证一下

接下来配置阿里云的安全

接下来安装redis

部署到云服务器

重新构建

双击打包报错

java.lang.IllegalStateException: Unable to find a @SpringBootConfiguration, you need to use @ContextConfiguration or @SpringBootTest(classes=...) with your test

插入新的依赖

<plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <configuration>
                    <skip>true</skip>
                </configuration>
            </plugin>

然后就ok了

如图所示打包完成后会显示新的jar包地址

然后上传到服务器root目录

在root目录新建图片文件夹然后把图片也传上去

接下来启动项目

nohup java -jar -Dserver.port=8080 -Dspring.profiles.active=prod /root/mal-0.0.1-SNAPSHOT.jar > /root/null 2>&1 &

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐