SpringbootMall项目

目录

一、工具准备与技术选型

 二、数据表设计

1、用户表

2、 分类表

 3、商品表

4 、购物车表

5、订单表,订单号属于商家内部数据不公开,是单独拿出来,不能是主键ID,否则订单号是主键逐渐递增就很容易暴露订单数量了。

6、订单商品表 or 订单项目表

 三、项目初始化、打通数据库和配置log4j2日志组件

1.新建一个spring Inaliaze工程项目,spring选2的主流版本,Java8.还有就是加上spring web。

2.pom文件添加依赖和插件

    3.新建一个文件generatorConfig.xml​编辑

 4.数据库脚本建库

 5.使用插件自动生成dao等文件

6、链接数据库

 7.配置log4j2日志组件

8、Aop统一处理web请求日志 

四、用户模块开发

1.API统一返回对象(工具类)与注册接口开发

a.API统一返回对象

b.注册接口开发 

2.自定义异常类

3.GlobalExceptionHandler编写与MD5算法加密

4.登录接口开发

5.其余接口开发

6.用户模块关键技术点

五、商品目录分类模块开发

1.功能与接口设计

2.添加目录接口(上)

3.添加目录分类接口(下)

4.@Valid注解优雅校验

5.swagger自动生成API文档,爽

6.更新目录接口

7.统一校验管理员身份

8.删除目录接口与分页功能

9.用户目录分类列表接口(递归实现)

10.Redis缓存技术提高响应

11.商品分类模块总结

六、商品模块开发

1.添加商品接口

2.图片上传接口

3.静态资源映射开发

4.更新和删除商品接口

5.批量上下架商品接口,sql语句有难度!

6.后台商品列表和商品详情接口

7.前台商品列表接口(上)

8.前台商品列表接口(下)

9.商品模块测试与总结

七、购物车模块开发

八、订单模块开发

九、上线部署

十、一些疑问与解答

疑问1

解答1 

 疑问2

解答2

疑问3 

解答3

错误4 type写错tye

 错误5 productMapper.insert 和productMapper.insertSelective

疑问6

解答6

十一、数据库创建SQL语句


一、工具准备与技术选型

工具准备:idea以及2个插件,Navicat、postman

技术选型:

 

 

接口文档

 二、数据表设计

所有表都有2字段:创建时间和更新时间,而且设置都是在下方默认的位置,选择空白格子,在里面填入“CURRENT_TIMESTAMP”,并且不要勾选“根据当前时间戳更新”。

创建时间字段设置

更新时间字段设置

 

1、用户表

2、 分类表

 3、商品表

4 、购物车表

5、订单表,订单号属于商家内部数据不公开,是单独拿出来,不能是主键ID,否则订单号是主键逐渐递增就很容易暴露订单数量了。

6、订单商品表 or 订单项目表

 三、项目初始化、打通数据库和配置log4j2日志组件

1.新建一个spring Inaliaze工程项目,spring选2的主流版本,Java8.还有就是加上spring web。

2.pom文件添加依赖和插件

        <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>
            <version>8.0.21</version>
        </dependency>
            <plugin>
                    <groupId>org.mybatis.generator</groupId>
                    <artifactId>mybatis-generator-maven-plugin</artifactId>
                    <version>1.3.7</version>
                    <configuration>
                        <verbose>true</verbose>
                        <overwrite>true</overwrite>
                    </configuration>
            </plugin>

    3.新建一个文件generatorConfig.xml

 generatorConfig.xml文件:

<?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="C:\Users\Aiver\.m2\repository\mysql\mysql-connector-java\8.0.21\mysql-connector-java-8.0.21.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/springbootmall?useUnicode=true&amp;characterEncoding=UTF-8&amp;zeroDateTimeBehavior=convertToNull"
      userId="root"
      password="root">
      <property name="nullCatalogMeansCurrent" value="true"/>
    </jdbcConnection>
    <!-- 非必需,类型处理器,在数据库类型和java类型之间的转换控制-->
    <javaTypeResolver>
      <property name="forceBigDecimals" value="false"/>
    </javaTypeResolver>
    <!--生成Model类存放位置-->
    <javaModelGenerator targetPackage="com.hw.springbootmall.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.hw.springbootmall.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>

 4.数据库脚本建库

在最后一节

 5.使用插件自动生成dao等文件

打开侧边栏的maven,双击下面插件即可

 自动生成下面dao和pojo数据传输对象,以及mapper的配置文件,xml配置文件。

 关于逆向工程:

1、生成逆向文件是需要mybatis-generator插件与generatorConfig.xml 文件的。

2、不需要同学自己进行书写,参考课程中的generatorConfig.xml 文件即可。

3、使用了MyBatist项目可以通过该方式进行生成逆向文件。

6、链接数据库

在配置文件application.properties中加入下面代码即可连通数据库,注意时区,serverTimezone=UTC或者上海、亚洲

server.port=8088

spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/springbootMall?serverTimezone=UTC&useUnicode=true&characterEncoding\
  =utf-8&useSSL=true
spring.datasource.username=root
spring.datasource.password=root

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

以查询用户测试数据库连通是否成功

查看逆向文件自动生成的mapper以及mapper的xml

 

 编写 Service层及其实现类

/**
 * @Author: Aiver
 * @Date: 2023/02/03~~22:08
 * @Description:用户service接口
 */
public interface UserService {
    User findUser();
}
/**
 * @Author: Aiver
 * @Date: 2023/02/03~~22:28
 * @Description:UserServiceImpl实现类
 */
@Service
public class UserServiceImpl implements UserService {

    @Autowired
    UserMapper userMapper;
    @Override
    public User findUser() {
        return userMapper.selectByPrimaryKey(3);
    }
}

 编写controller层

/**
 * @Author: Aiver
 * @Date: 2023/02/03~~22:06
 * @Description:用户controller
 */
@Controller
public class UserController {
    @Autowired
    UserService userService;
    @GetMapping("/findUser")
    @ResponseBody
    public User findUser(){
        return userService.findUser();
    }
}

主启动类加上

//告诉mybatis关于mapper文件的位置,扫描改包下面所有类
@MapperScan(basePackages = "com.hw.springbootmall.model.dao")

@SpringBootApplication
//告诉mybatis关于mapper文件的位置,扫描改包下面所有类
@MapperScan(basePackages = "com.hw.springbootmall.model.dao")
public class SpringbootMallApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringbootMallApplication.class, args);
    }
}

 run主启动类,然后测试成功

 Service实现类这里的Mapper有红色下划线,原因是

自动生成的mapper接口没有注解@Repository,idea不知道哪里寻找mapper类。

 解决方法就是在所有mapper接口手动加上@Repository

 7.配置log4j2日志组件

 排除原有日志依赖,引入log4j2依赖

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>



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

复制log4j2.xml到resources目录下 。

log4j2.xml文件:

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="fatal">
  <Properties>
<!--    日志文件存放位置-->
    <Property name="baseDir" value="D:\IdeaImoocVedio\chapter04\springboot-mall-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>

8、Aop统一处理web请求日志 

依赖引入

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

创建filter包和WebLogAspect类

package com.hw.springbootmall.filter;

WebLogAspect类的编写:

package com.hw.springbootmall.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;

/**
 * @Author: Aiver
 * @Date: 2023/02/04~~10:15
 * @Description:打印请求和响应信息
 */
@Component
@Aspect
public class WebLogAspect {
//    LoggerFactory.

    private final Logger logger = LoggerFactory.getLogger(WebLogAspect.class);

    @Pointcut("execution(public * com.hw.springbootmall.controller.*.*(..))")
    public void webLog(){

    }
    @Before("webLog()")
    public void recordBeforeWebLog(JoinPoint joinPoint){
        //收到请求并记录请求内容,JoinPoint记录的是关于类的信息
        logger.info("类是 "+joinPoint.getSignature().getDeclaringTypeName());
        logger.info("类签名信息"+joinPoint.getSignature().getName());
        logger.info("args参数"+String.valueOf(joinPoint.getArgs()));
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = requestAttributes.getRequest();
        logger.info("URL是"+request.getRequestURL().toString());
        logger.info("HTTP method是"+request.getMethod());
        logger.info("远程IP是"+request.getRemoteAddr());
        logger.info("本地IP是"+request.getLocalAddr());

    }
    //返回时候已经生成响应了
    @AfterReturning(returning = "response",pointcut = "webLog()")
    public void doAfterReturning(Object response) throws JsonProcessingException {
        //处理完请求,返回内容
        logger.info("Response是"+new ObjectMapper().writeValueAsString(response));
    }
}

测试,刷新请求,控制台打印信息如下

四、用户模块开发

 

1.API统一返回对象(工具类)与注册接口开发

a.API统一返回对象

关于泛型

 

 新建common包,手写类如下

package com.hw.springbootmall.common;

import com.hw.springbootmall.exception.ExceptionEnum;

/**
 * @Author: Aiver
 * @Date: 2023/02/05~~8:50
 * @Description:统一API返回对象
 */
public class ApiResponseObj<T> {
    private static final int OK_STATUS=100;//成功状态码
    private static final String OK_MESSAGE="WELLDONE";//成功状信息

    private Integer status;//状态码
    private String message;//信息
    private T data;//数据

    public ApiResponseObj(Integer status, String message, T data) {
        this.status = status;
        this.message = message;
        this.data = data;
    }

    public ApiResponseObj(Integer status, String message) {
        this.status = status;
        this.message = message;
    }

    //用作返回成功状态码与成功信息
    public ApiResponseObj() {
        this(OK_STATUS,OK_MESSAGE);
    }

    //成功返回,仅仅状态码和信息
    public static <T> ApiResponseObj success(){
        return new  ApiResponseObj();
    }
    //成功返回,除状态码和信息,还返回具体数据
    public static <T> ApiResponseObj<T> success(T result){
        ApiResponseObj<Object> responseObj = new ApiResponseObj<>();
        responseObj.setData(result);
        return (ApiResponseObj<T>) responseObj;
    }
    //错误状态码和信息返回
    public static <T> ApiResponseObj error(ExceptionEnum exceptionEnum){
        return new ApiResponseObj(exceptionEnum.getStatus(),exceptionEnum.getMessage());
    }

    @Override
    public String toString() {
        return "ApiResponseObj{" +
                "status=" + status +
                ", message='" + message + '\'' +
                ", data=" + data +
                '}';
    }
    public static int getOkStatus(){
        return OK_STATUS;
    }
    public static String getOkMessage(){
        return OK_MESSAGE;
    }
    public Integer getStatus() {
        return status;
    }

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

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public T getData() {
        return data;
    }

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

}

新建exception包,手写类如下

package com.hw.springbootmall.exception;

/**
 * @Author: Aiver
 * @Date: 2023/02/05~~10:09
 * @Description:异常枚举类
 */
public enum ExceptionEnum {

    REQUIRED_USER_NAME(101,"用户名为空,重新填写");
    //异常状态码
    private Integer status;
    //异常信息
    private String message;

    ExceptionEnum(Integer status, String message) {
        this.status = status;
        this.message = message;
    }

    public Integer getStatus() {
        return status;
    }

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

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }
}

b.注册接口开发 

在mapper接口添加方法

selectByName

 在mapper配置文件添加SQL查询语句标签

 在Service层的Service接口添加方法

 ServiceImpl实现类手写,异常类自定义再下一小节:

package com.hw.springbootmall.service.impl;

import com.hw.springbootmall.exception.ExceptionEnum;
import com.hw.springbootmall.exception.MallExeception;
import com.hw.springbootmall.model.dao.UserMapper;
import com.hw.springbootmall.model.pojo.User;
import com.hw.springbootmall.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * @Author: Aiver
 * @Date: 2023/02/03~~22:28
 * @Description:UserServiceImpl实现类
 */
@Service
public class UserServiceImpl implements UserService {

    @Autowired
    UserMapper userMapper;
    @Override
    public User findUser() {
        return userMapper.selectByPrimaryKey(3);
    }

    @Override
    public void userRegister(String username, String password) throws MallExeception {
        //查询数据库是否事先已经有用户名,不允许重名
        User userResult = userMapper.selectByName(username);
        if (userResult != null) {
            //抛出用户已存在异常
            throw new MallExeception(ExceptionEnum.NAME_EXISTED);
        }else {
            //用户事先不存在,新用户写入数据库
            User user = new User();
            user.setUsername(username);
            user.setPassword(password);
            int count = userMapper.insertSelective(user);//该方法是仅仅插入不为空的字段,而不是插入所有字段。如果字段为空就不插入
            if(count == 0){
                throw new MallExeception(ExceptionEnum.INSERT_FAILED);
            }

        }

    }
}

Controller层类手写:

package com.hw.springbootmall.controller;

import com.hw.springbootmall.common.ApiResponseObj;
import com.hw.springbootmall.exception.ExceptionEnum;
import com.hw.springbootmall.exception.MallExeception;
import com.hw.springbootmall.model.pojo.User;
import com.hw.springbootmall.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;

/**
 * @Author: Aiver
 * @Date: 2023/02/03~~22:06
 * @Description:用户controller
 */
@Controller
@RequestMapping("/user")
public class UserController {
    @Autowired
    UserService userService;
    @GetMapping("/findUser")
    @ResponseBody
    public User findUser(){
        return userService.findUser();
    }

    @PostMapping("/userRegister")
    @ResponseBody
    public ApiResponseObj userRegister(@RequestParam(value = "username") String username,@RequestParam(value =
            "password") String password) throws MallExeception {
        //用户名是空
        if (StringUtils.isEmpty(username)){
            return ApiResponseObj.error(ExceptionEnum.REQUIRED_USER_NAME);
        }
        //密码是空
        if (StringUtils.isEmpty(password)){
            return ApiResponseObj.error(ExceptionEnum.REQUIRED_USER_PASSWORD);
        }
        //密码长度不小于8位
        if(password.length()<8){
            return ApiResponseObj.error(ExceptionEnum.REQUIRED_USER_PASSWORD_LENGTH);
        }
        userService.userRegister(username,password);
        return ApiResponseObj.success();

    }
}

2.自定义异常类

Controller层直接抛出 ExceptionEnum 异常枚举类 给ApiResponseObj统一响应对象(该对象响应网页发来的请求,并返回相关信息)

Service层不能直接抛出异常枚举类给网页,只能向上抛给Controller层,所以用到自定义的MallExeception统一异常类。

MallExeception统一异常类和 ExceptionEnum 异常枚举类 关系如下

ExceptionEnum 异常枚举类:

package com.hw.springbootmall.exception;

/**
 * @Author: Aiver
 * @Date: 2023/02/05~~10:09
 * @Description:异常枚举类,与API统一返回对象类有关,单独拿出来status和message
 */
public enum ExceptionEnum {

    REQUIRED_USER_NAME(101,"用户名为空,重新填写"),
    REQUIRED_USER_PASSWORD(102,"密码为空,重新填写"),
    REQUIRED_USER_PASSWORD_LENGTH(103,"密码长度不能小于8位,男人不能设太短!!重新设♂密码"),
    NAME_EXISTED(104,"用户名已经存在辣!兄弟"),
    INSERT_FAILED(105,"插♂入数据失败,检查一下再输入"),

    USER_NAME_TEST(1,10086,"枚举类多个构造器");
    private Integer num;
    //异常状态码
    private Integer status;
    //异常信息
    private String message;

    ExceptionEnum(Integer status, String message) {
        this.status = status;
        this.message = message;
    }

    ExceptionEnum(Integer num, Integer status, String message) {
        this.num = num;
        this.status = status;
        this.message = message;
    }

    public Integer getStatus() {
        return status;
    }

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

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }
}

MallExeception统一异常类:

package com.hw.springbootmall.exception;

/**
 * @Author: Aiver
 * @Date: 2023/02/05~~16:49
 * @Description:统一异常
 */
public class MallExeception extends Exception{
    private final Integer status;
    private final String message;
    public MallExeception(Integer status,String message){
        this.status = status;
        this.message = message;
    }
    public MallExeception(ExceptionEnum exceptionEnum){
        this(exceptionEnum.getStatus(),exceptionEnum.getMessage());
    }

    public Integer getStatus() {
        return status;
    }

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

 测试注册功能:

故意错误填写注册名和密码

 正确填写注册用户名和密码:

 

3.GlobalExceptionHandler编写与MD5算法加密

接上面重名测试结果,这个有些东西暴露给黑客就不好了。所以编写一个GlobalExceptionHandler类统一处理异常,方便在Service层直接抛出异常

 编写异常统一处理类

package com.hw.springbootmall.exception;

import com.hw.springbootmall.common.ApiResponseObj;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

/**
 * @Author: Aiver
 * @Date: 2023/02/05~~22:48
 * @Description:处理统一异常的handler
 */
@ControllerAdvice
public class GlobalExceptionHandler {
    //系统异常处理
    @ExceptionHandler(Exception.class)
    @ResponseBody
    public Object handlerException(Exception exception){
        return ApiResponseObj.error(ExceptionEnum.SYSTEM_ERROR);
    }
    //业务异常处理
    @ExceptionHandler(MallExeception.class)
    @ResponseBody
    public Object handlerMallException(MallExeception mallExeception){
        return ApiResponseObj.error(mallExeception.getStatus(),mallExeception.getMessage());
    }
}

异常枚举类中加一下2开头的系统异常状态码和信息。1开头都是和业务相关的状态码和信息

测试一下

 MD5加密:

在util包创建MD5工具类:

package com.hw.springbootmall.util;

import com.hw.springbootmall.common.Quantity;
import org.apache.tomcat.util.codec.binary.Base64;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

/**
 * @Author: Aiver
 * @Date: 2023/02/05~~23:18
 * @Description:MD5加密工具类
 */
public class MD5Utils {
    //对参数password进行MD5算法加密,返回加密后结果
    public static String lockMD5Password(String password) throws NoSuchAlgorithmException {
        MessageDigest md5Digest = MessageDigest.getInstance("MD5");
        String saltPassword = password + Quantity.SALT;
        byte[] bytes = md5Digest.digest(saltPassword.getBytes());
        return Base64.encodeBase64String(bytes);
    }

    //测试MD5加密后的结果
    public static void main(String[] args) {
        String md5Password = null;
        try {
            md5Password = MD5Utils.lockMD5Password("2345");
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException(e);
        }
        //打印MD5加密后的结果
        System.out.println(md5Password);
    }
}

创建常量类,存放constant常量盐值

package com.hw.springbootmall.common;

/**
 * @Author: Aiver
 * @Date: 2023/02/05~~23:39
 * @Description:常量类
 */
public class Quantity {
    public static final String SALT="328hdweuui,.";
}

4.登录接口开发

 Controller层:

 /**
     * 
     * @param username
     * @param password
     * @param httpSession
     * @return 返回用户信息给网页时候要将密码擦除
     * @throws MallExeception
     */
    @PostMapping("/userLogin")
    @ResponseBody
    public ApiResponseObj userLogin(@RequestParam(value = "username") String username,
                                    @RequestParam(value = "password") String password,
                                    HttpSession httpSession) throws MallExeception {
        //用户名是空
        if (StringUtils.isEmpty(username)){
            return ApiResponseObj.error(ExceptionEnum.REQUIRED_USER_NAME);
        }
        //密码是空
        if (StringUtils.isEmpty(password)){
            return ApiResponseObj.error(ExceptionEnum.REQUIRED_USER_PASSWORD);
        }
        User loginUser = userService.login(username, password);
        //返回用户信息给网页时候要将密码擦除
        loginUser.setPassword(null);
        //返回用户给网页时候将user存储到session,key-value形式
        httpSession.setAttribute(Quantity.MALL_USER,loginUser);
        return ApiResponseObj.success(loginUser);

    }

ServiceImpl层

    @Override
    public User login(String username, String password) throws MallExeception {
        User userLogin = null;
        try {
            String md5Password = MD5Utils.lockMD5Password(password);
            userLogin = userMapper.selectWhenLogin(username, md5Password);
            if(userLogin == null){
                throw new MallExeception(ExceptionEnum.LOGIN_FAILED);
            }
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException(e);
        }finally {
            return userLogin;
        }
    }

 mapper层及其SQL标签

//多个入参就要加注解@Param
    User selectWhenLogin(@Param(value = "username") String username,@Param(value = "password") String password);
 <select id="selectWhenLogin" parameterType="map" resultMap="BaseResultMap">
    select <include refid="Base_Column_List"></include>
    from imooc_mall_user
    where username = #{username,jdbcType=VARCHAR}
    and password = #{password,jdbcType=VARCHAR}
  </select>

 先不急测试,继续其余接口开发

5.其余接口开发

更新签名接口

Controller层

    @PostMapping("/userUpdate")
    @ResponseBody
    public ApiResponseObj updateSignature(HttpSession httpSession,@RequestParam(value = "signature") String signature) throws MallExeception {
        //从session回话中获取更新前的对象
        User user = (User)httpSession.getAttribute(Quantity.MALL_USER);
//        判断对象是否为null
        if(user == null){
            //对象是null,返回错误信息
            return ApiResponseObj.error(ExceptionEnum.REQUIRED_USER_LOGIN);
        }
        User newUser = new User();
        newUser.setId(user.getId());
        newUser.setPersonalizedSignature(signature);
        userService.updateSignature(newUser);
        return ApiResponseObj.success();
    }

ServiceImpl层

    @Override
    public void updateSignature(User user) throws MallExeception {
        //更新个性签名
        //这个updateByPrimaryKeySelective方法就是会判断只有字段不为空才会更新的,
        // 而从Controller传过来的updateUser除了id和signature这两个属性不为空,其余均是null。
        // 所以就只更新id和signature。而id的获取是session回话中原来的旧的user的id。
        // 所以id更新后也还是原来的id。就其实只是更新signature
        int updateCount = userMapper.updateByPrimaryKeySelective(user);
        if(updateCount>1){
            throw new MallExeception(ExceptionEnum.UPDATE_FAILED);
        }
    }

mapper的SQL更新标签updateByPrimaryKeySelective标签

 现在对登录和更新签名接口测试

先注册个新用户

 然后登陆,可以看到密码已经被抹去,而更新签名也是没写,null

 现在更新写一下个性签名,基尼太美

登出接口开发,这个很简单,就是清除session。在session回话中去掉登陆对象user就可以,用removeAttribute方法

    @PostMapping("/logout")
    @ResponseBody
    public ApiResponseObj logout(HttpSession httpSession){
        httpSession.removeAttribute(Quantity.MALL_USER);
        return ApiResponseObj.success();
    }

 登陆对象以setAttribute方法储存在session回话中

 管理员登陆接口开发

controller层

    @PostMapping("/adminLogin")
    @ResponseBody
    public ApiResponseObj adminLogin(@RequestParam(value = "username") String username,
                                    @RequestParam(value = "password") String password,
                                    HttpSession httpSession) throws MallExeception {
        //用户名是空
        if (StringUtils.isEmpty(username)){
            return ApiResponseObj.error(ExceptionEnum.REQUIRED_USER_NAME);
        }
        //密码是空
        if (StringUtils.isEmpty(password)){
            return ApiResponseObj.error(ExceptionEnum.REQUIRED_USER_PASSWORD);
        }
        User loginUser = userService.login(username, password);
        boolean flag = userService.isAdmin(loginUser);
        if(flag){
            //是管理员那么就保存到数据库
            //返回管理员用户信息给网页时候要将密码擦除
            loginUser.setPassword(null);
            //返回管理员用户给网页时候将user存储到session,key-value形式
            httpSession.setAttribute(Quantity.MALL_USER,loginUser);
            return ApiResponseObj.success(loginUser);
        }else{
            return ApiResponseObj.error(ExceptionEnum.NOT_ADMIN_LOGIN);
        }
    }

serviceImpl层:

    @Override
    public boolean isAdmin(User user){
        boolean flag = user.getRole().equals(2);
        return flag;
    }

 测试就免了截图

6.用户模块关键技术点

五、商品目录分类模块开发

1.功能与接口设计

2.添加目录接口(上)

编写添加目录请求类,和pojo类相似,只不过没有pojo那样有目录表的全部字段,只有部分字段

package com.hw.springbootmall.model.request;

/**
 * @Author: Aiver
 * @Date: 2023/02/06~~22:58
 * @Description:添加目录的请求类
 */
public class InsertCategoryReq {
    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;
    }
}

手写controller类

package com.hw.springbootmall.controller;

import com.hw.springbootmall.common.ApiResponseObj;
import com.hw.springbootmall.common.Quantity;
import com.hw.springbootmall.exception.ExceptionEnum;
import com.hw.springbootmall.exception.MallExeception;
import com.hw.springbootmall.model.pojo.User;
import com.hw.springbootmall.model.request.InsertCategoryReq;
import com.hw.springbootmall.service.CategoryService;
import com.hw.springbootmall.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpSession;

/**
 * @Author: Aiver
 * @Date: 2023/02/06~~22:55
 * @Description:目录Controller
 */
@RestController
public class CategoryController {
    @Autowired
    UserService userService;
    @Autowired
    CategoryService categoryService;

    @PostMapping("/insertCategory")
    public ApiResponseObj insertCategory(HttpSession httpSession, InsertCategoryReq insertCategoryReq) throws MallExeception {
        //判断要插入的参数也就是传入的参数是否为空
        if(insertCategoryReq.getName()==null || insertCategoryReq.getType()==null || insertCategoryReq.getOrderNum()==null||insertCategoryReq.getParentId()==null){
//            return MallExeception(ExceptionEnum.ARGS_IS_NULL);
            return ApiResponseObj.error(ExceptionEnum.ARGS_IS_NULL);
        }
        //从session回话获取当前登陆的用户,并且判断其是否为空,是否是管理员
        User user = (User) httpSession.getAttribute(Quantity.MALL_USER);
        if(user == null){
            //用户为空
            return ApiResponseObj.error(ExceptionEnum.REQUIRED_USER_LOGIN);
        }
        //是否是管理员
        boolean isAdmin = userService.isAdmin(user);
        if(!isAdmin){
            //不是管理员
            return ApiResponseObj.error(ExceptionEnum.NOT_ADMIN_LOGIN);
        }else{
            //是管理员
            categoryService.insertCategory(insertCategoryReq);
            return ApiResponseObj.success();
        }


    }

}

3.添加目录分类接口(下)

手写Service类:

package com.hw.springbootmall.service.impl;

import com.hw.springbootmall.exception.ExceptionEnum;
import com.hw.springbootmall.exception.MallExeception;
import com.hw.springbootmall.model.dao.CategoryMapper;
import com.hw.springbootmall.model.pojo.Category;
import com.hw.springbootmall.model.request.InsertCategoryReq;
import com.hw.springbootmall.service.CategoryService;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * @Author: Aiver
 * @Date: 2023/02/06~~23:41
 * @Description:目录分类Service实现类
 */
@Service
public class CategoryServiceImpl implements CategoryService {
    //查询数据库中目录表原先是否有与插入目录重名目录的数据
    //主键是id,自动生成mapper里面的的selectByPrimaryKey方法用不了
    //    手写一个selectByName的方法于mapper
//    @Autowired
//    CartMapper cartMapper;

    @Autowired
    CategoryMapper categoryMapper;

    /**
     *添加目录
     * @param insertCategoryReq
     * @throws MallExeception
     */
    @Override
    public void insertCategory(InsertCategoryReq insertCategoryReq) throws MallExeception {
        Category categoryNew = new Category();
        //将参数insertCategoryReq的4个属性赋值给categoryOld,下面是比较慢的做法
//        categoryNew.setName(insertCategoryReq.getName());
//        categoryNew.setType(insertCategoryReq.getType());
//        categoryNew.setParentId(insertCategoryReq.getParentId());
//        categoryNew.setOrderNum(insertCategoryReq.getOrderNum());
        //这里更快,男人一定要快!
        BeanUtils.copyProperties(insertCategoryReq,categoryNew);
        //写一个mapper查询数据库中目录表原先是否有要插入的目录对象
        //cartMapper.selectByPrimaryKey()
        Category categoryOld = categoryMapper.selectByName(insertCategoryReq.getName());
        if(categoryOld != null){
//            数据库中原先就已经存在目录对象了
            throw new  MallExeception(ExceptionEnum.NAME_EXISTED);
        }else {
            int insertCount= categoryMapper.insertSelective(categoryNew);
            if(insertCount == 0){
//                throw  new MallExeception(ExceptionEnum.INSERT_FAILED);
                throw new MallExeception(ExceptionEnum.ADD_CATEGORY_FAILED);
            }
        }
    }
}

mapper:

<select id="selectByName" parameterType="java.lang.String"  resultMap="BaseResultMap">
    select
    <include refid="Base_Column_List" />
    from imooc_mall_category
    where name = #{name,jdbcType=VARCHAR}
  </select>

 测试

目录的Controller这里加上一个注解@RequestBody

 一开始会提示用户未登录,注意在postman里面是body,而且选raw以及JSON。

 那么就先登陆,mumu5,role是2,是管理员,可以登陆

 这个我重启项目再登陆了,URL加了个前缀category

测试成功。跑去数据库里看看,也没有问题。

 

4.@Valid注解优雅校验

全局统一异常处理

//参数无效异常处理
    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseBody
    public ApiResponseObj handleMethodArgumentNotValidException(MethodArgumentNotValidException argumentNotValidException){
        logger.error("MethodArgumentNotValidException来的",argumentNotValidException);
        return handleBindingResult(argumentNotValidException.getBindingResult());
    }
    private ApiResponseObj handleBindingResult(BindingResult bindingResult){
        //把参数异常处理为对外暴露的提示
        ArrayList<String> list = new ArrayList<>();
        boolean hasErrors = bindingResult.hasErrors();
        if (hasErrors) {
            //有参数不合规则
            for (ObjectError allError : bindingResult.getAllErrors()) {
                String defaultMessage = allError.getDefaultMessage();
                boolean add = list.add(defaultMessage);
            }
        }
//        没有一个参数
        if (list.size()==0) {
            return ApiResponseObj.error(ExceptionEnum.NO_PARA);
        }
        //status状态码是ExceptionEnum的,message消息则是上面的bindingResult的allError的getDefaultMessage
        return (ApiResponseObj) ApiResponseObj.error(ExceptionEnum.PARA_INVALID.getStatus(),list.toString());
    }

加上注解@valid

 测试成功,截图一次,其余测试不截图了

5.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>

在主启动类加上注解

在config包创建两个类

package com.hw.springbootmall.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:8088/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("mall商品")
                .description("springboot电商项目")
                .termsOfServiceUrl("")
                .build();
    }
}
package com.hw.springbootmall.config;

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

/**
 * @Author: Aiver
 * @Date: 2023/02/08~~7:43
 * @Description:
 */
@Configuration
public class mallWebMvcConfig 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/");
    }
}

 在目录controller层加注解

    @ApiOperation("后台数据添加商品目录")

改一下版本,原先的感觉太高了,

测试不成功,控制台一大堆报错,看得我很烦。后面找到原来是springboot版本过于高了。

org.springframework.boot
spring-boot-starter-parent
2.2.1.RELEASE

  改版本后测试成功如下

6.更新目录接口

controller:

Service:

package com.hw.springbootmall.service.impl;

import com.hw.springbootmall.exception.ExceptionEnum;
import com.hw.springbootmall.exception.MallExeception;
import com.hw.springbootmall.model.dao.CategoryMapper;
import com.hw.springbootmall.model.pojo.Category;
import com.hw.springbootmall.model.request.InsertCategoryReq;
import com.hw.springbootmall.model.request.UpdateCategoryReq;
import com.hw.springbootmall.service.CategoryService;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * @Author: Aiver
 * @Date: 2023/02/06~~23:41
 * @Description:目录分类Service实现类
 */
@Service
public class CategoryServiceImpl implements CategoryService {
    //查询数据库中目录表原先是否有与插入目录重名目录的数据
    //主键是id,自动生成mapper里面的的selectByPrimaryKey方法用不了
    //    手写一个selectByName的方法于mapper
//    @Autowired
//    CartMapper cartMapper;

    @Autowired
    CategoryMapper categoryMapper;

    /**
     *添加目录
     * @param insertCategoryReq
     * @throws MallExeception
     */
    @Override
    public void insertCategory(InsertCategoryReq insertCategoryReq) throws MallExeception {
        Category categoryNew = new Category();
        //将参数insertCategoryReq的4个属性赋值给categoryOld,下面是比较慢的做法
//        categoryNew.setName(insertCategoryReq.getName());
//        categoryNew.setType(insertCategoryReq.getType());
//        categoryNew.setParentId(insertCategoryReq.getParentId());
//        categoryNew.setOrderNum(insertCategoryReq.getOrderNum());
        //这里更快,男人一定要快!
        BeanUtils.copyProperties(insertCategoryReq,categoryNew);
        //写一个mapper查询数据库中目录表原先是否有要插入的目录对象
        //cartMapper.selectByPrimaryKey()不适合用,自己手写一个mapper查询:selectByName
        Category categoryOld = categoryMapper.selectByName(insertCategoryReq.getName());
        if(categoryOld != null){
//            数据库中原先就已经存在目录对象了
            throw new  MallExeception(ExceptionEnum.NAME_EXISTED);
        }else {
            int insertCount= categoryMapper.insertSelective(categoryNew);
            if(insertCount == 0){
//                throw  new MallExeception(ExceptionEnum.INSERT_FAILED);
                throw new MallExeception(ExceptionEnum.ADD_CATEGORY_FAILED);
            }
        }
    }

    @Override
    public void updateCategory(Category category) throws MallExeception {
        Category categoryOld = categoryMapper.selectByName(category.getName());
        /*代码中是根据name进行查询的,此时如果查询到结果时,存在两种可能性:
        1.查询到的是自身的信息;
        2.前台传入的name数据与数据库中已有的数据发生了冲突。
        而是否冲突,是需要根据id来进行判断的,例如下面的情况:
        1.前台传入的数据中,不需要对name值进行更改,而是更改其他属性,此时在数据库中一定可以查询到name相同的数据。
        2.前台传入的数据中,对name进行了更改,但是这个name值在数据库中已经被其他id的数据使用了。
        前台修改的name可能碰到拿到数据库一样的(maybe类似哈希碰撞小概率事件),而这时候就要加上id进一步确定
        所以只是name值相同,不能确定是否是对自身进行更改,需要使用id来进一步判断。*/
        if(categoryOld != null && !categoryOld.getId().equals(category.getId())){
            throw new MallExeception(ExceptionEnum.NAME_EXISTED);
        }
        int updateCount = categoryMapper.updateByPrimaryKeySelective(category);
        if (updateCount == 0) {
            throw  new MallExeception(ExceptionEnum.UPDATE_FAILED);
        }
    }
    @Override
    public void updateCategory(UpdateCategoryReq updateCategoryReq) throws MallExeception {
        Category categoryNew = new Category();
        BeanUtils.copyProperties(updateCategoryReq,categoryNew);
        Category categoryOld = categoryMapper.selectByName(updateCategoryReq.getName());
        if(categoryOld != null && categoryOld.getId().equals(updateCategoryReq.getId())){
            //原先数据库有一样名字的目录而且原先的id和更新请求的id一样,那么就可以进行更新
            int updateCount = categoryMapper.updateByPrimaryKeySelective(categoryNew);
            if (updateCount==0) {
                throw new MallExeception(ExceptionEnum.UPDATE_FAILED);
            }
        }else{
            //其他情况就不更新了,管它呢
            throw new MallExeception(ExceptionEnum.NAME_EXISTED);
        }
    }
    @Override
    public void updateCategory(UpdateCategoryReq updateCategoryReq,Category category) throws MallExeception {
        BeanUtils.copyProperties(updateCategoryReq,category);
        Category categoryOld = categoryMapper.selectByName(updateCategoryReq.getName());
        if(categoryOld != null && categoryOld.getName().equals(category.getName())){
            int updateCount = categoryMapper.updateByPrimaryKeySelective(category);
            if (updateCount==0) {
                throw new MallExeception(ExceptionEnum.UPDATE_FAILED);
            }
        }else{
            throw new MallExeception(ExceptionEnum.NAME_EXISTED);
        }
    }
}

 mapper:

<select id="selectByName" parameterType="java.lang.String"  resultMap="BaseResultMap">
    select
    <include refid="Base_Column_List" />
    from imooc_mall_category
    where name = #{name,jdbcType=VARCHAR}
  </select>

 测试咯

修改前数据库

 更新请求,注意URL就是127.0.0.1,和管理员登陆的一样。用localhost不行这也太坑了,坑了一下午。

7.统一校验管理员身份

filter拦截器层

package com.hw.springbootmall.filter;

import com.hw.springbootmall.common.ApiResponseObj;
import com.hw.springbootmall.common.Quantity;
import com.hw.springbootmall.exception.ExceptionEnum;
import com.hw.springbootmall.model.pojo.Category;
import com.hw.springbootmall.model.pojo.User;
import com.hw.springbootmall.model.request.UpdateCategoryReq;
import com.hw.springbootmall.service.UserService;
import com.sun.deploy.net.HttpResponse;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;

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;

/**
 * @Author: Aiver
 * @Date: 2023/02/08~~16:48
 * @Description:统一校验管理员登陆拦截器
 */
public class AdminFilter implements Filter {
    @Autowired
    UserService userService;
    @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 httpServletRequest = (HttpServletRequest) servletRequest;
        HttpSession httpSession = httpServletRequest.getSession();
        User user = (User)httpSession.getAttribute(Quantity.MALL_USER);
        if (user==null) {
            //用户不存在于session,说明未登录
//            return ApiResponseObj.error(ExceptionEnum.REQUIRED_USER_LOGIN);
//            HttpResponse httpResponse = (HttpResponse) servletResponse;
            HttpServletResponse response = (HttpServletResponse) servletResponse;
            PrintWriter printWriter = new HttpServletResponseWrapper(response).getWriter();
            printWriter.write("{\n" +
                    "    \"status\": 107,\n" +
                    "    \"message\": \"REQUIRED_USER_LOGIN\",\n" +
                    "    \"data\": null\n" +
                    "}");
            printWriter.flush();
            printWriter.close();
            //return表示方法执行到这就结束了
            return;
        }
        boolean isAdmin = userService.isAdmin(user);
        if(isAdmin){
            //已经登陆,是管理员
//            Category category = new Category();
//            BeanUtils.copyProperties(UpdateCategoryReq,category);
//            categoryService.updateCategory(updateCategoryReq);
//            return ApiResponseObj.success();
            //是管理员就开始放行
            filterChain.doFilter(servletRequest,servletResponse);
        }else{
            //不是管理员
//            return ApiResponseObj.error(ExceptionEnum.NOT_ADMIN_LOGIN);
            HttpServletResponse response = (HttpServletResponse) servletResponse;
            PrintWriter printWriter = new HttpServletResponseWrapper(response).getWriter();
            printWriter.write("{\n" +
                    "    \"status\": 109,\n" +
                    "    \"message\": \"NOT_ADMIN_LOGIN\",\n" +
                    "    \"data\": null\n" +
                    "}");
            printWriter.flush();
            printWriter.close();
            return;
        }
    }

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

config配置层

package com.hw.springbootmall.config;

import com.hw.springbootmall.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;
import javax.servlet.FilterRegistration;

/**
 * @Author: Aiver
 * @Date: 2023/02/08~~19:03
 * @Description:Admin过滤器配置
 */
@Configuration
public class AdminFilterConfig {
    @Bean
    public AdminFilter getAdminFilter(){
        return new AdminFilter();
    }
    @Bean(name = "adminFilterConf")
    public FilterRegistrationBean adminFilterConf(){
        FilterRegistrationBean<Filter> registrationBean = new FilterRegistrationBean<>();
        registrationBean.setFilter(getAdminFilter());
        registrationBean.addUrlPatterns("/admin/category/*");
        registrationBean.addUrlPatterns("/admin/product/*");
        registrationBean.addUrlPatterns("/admin/order/*");
        registrationBean.setName("adminFilterConf");
        return  registrationBean;
    }
}

在category目录的controller层新增有一个方法,删除目录功能

@ApiOperation("后台数据删除商品目录")
    @PostMapping("/deleteCategory")
    public ApiResponseObj deleteCategory(){
        return null;
    }

  测试一下

8.删除目录接口与分页功能

删除controller

@ApiOperation("后台数据删除商品目录")
    @PostMapping("/deleteCategory")
    public ApiResponseObj deleteCategory(@RequestParam Integer id) throws MallExeception {
        categoryService.deleteCategory(id);
        return ApiResponseObj.success();
    }

service

@Override
    public void deleteCategory(Integer id) throws MallExeception {
        Category category = categoryMapper.selectByPrimaryKey(id);
        if(category != null){
            //数据库中存在删除的对象
            int deleteCount = categoryMapper.deleteByPrimaryKey(id);
            if (deleteCount==0) {
                throw new MallExeception(ExceptionEnum.DELETE_FAILED);
            }
        }else{
            //数据库不存在删除对象,返回错误
            throw new MallExeception(ExceptionEnum.DELETE_FAILED);
        }
    }

 简单测试没问题就行

到了分页功能

新建VO层的VO类,就是复制pojo类,然后加上下面属性,以及他的get和set方法。

private List<CategoryVO> childCategory = new ArrayList<>();
package com.hw.springbootmall.vo;

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

public class CategoryVO {
    private List<CategoryVO> childCategory = new ArrayList<>();
    private Integer id;

    private String name;

    private Integer type;

    private Integer parentId;

    private Integer orderNum;

    private Date createTime;

    private Date updateTime;

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

    public void setChildCategory(List<CategoryVO> childCategory) {
        this.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 == null ? null : name.trim();
    }

    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;
    }
}

引入依赖pagehelper

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

发现下载一直报错,就算换了各种版本

重新这样子引入依赖就行了

<!-- 分页插件pagehelper -->
<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper</artifactId>
    <version>5.0.0</version>
</dependency>
<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper-spring-boot-autoconfigure</artifactId>
    <version>1.2.3</version>
</dependency>
<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper-spring-boot-starter</artifactId>
    <version>1.2.3</version>
</dependency>
<!-- 分页插件pagehelper -->

controller

@ApiOperation("后台数据列出商品目录")
    @PostMapping("/listCategory")
    public ApiResponseObj listCategory(@RequestParam Integer pageNum,@RequestParam Integer pageSize){
        PageInfo pageInfo = categoryService.listForAdmin(pageNum, pageSize);
        return ApiResponseObj.success(pageInfo);

service

/**
     * 分页查询
     * @param pageNum
     * @param pageSize
     * @return 返回PageInfo
     */
    @Override
    public PageInfo<Category> listForAdmin(Integer pageNum, Integer pageSize){
        //排序优先级,先是type后是order_num
        PageHelper.startPage(pageNum,pageSize,"type,order_num");
//        手写一个底层selectList的简单mapper查询
        List<Category> categories = categoryMapper.selectList();
//        返回给前台是一个PageInfo
        PageInfo<Category> pageInfo = new PageInfo<>(categories);
        return pageInfo;

Category的mapper 接口及其SQL查询标签

List<Category> selectList();
<select id="selectList" resultMap="BaseResultMap">
    select <include refid="Base_Column_List"></include>
    from imooc_mall_category
  </select>

 启动项目,登陆管理员并测试成功

JSON在线校验,更直观

9.用户目录分类列表接口(递归实现)

在用户的controller层

@ApiOperation("后台数据列出商品目录,给用户看")
    @ResponseBody
    @PostMapping("/listCategoryForUser")
    public ApiResponseObj listCategoryForUser(){
        List<CategoryVO> categoryVOS = categoryService.listForUser();
        return ApiResponseObj.success(categoryVOS);
    }

在目录category的service实现类

/**
     * 分页查询给用户看
     * @return
     */
    @Override
    public List<CategoryVO> listForUser(){
        ArrayList<CategoryVO> arrayList = new ArrayList<>();
//        System.out.println(arrayList.toString());
//        调用下面的递归查询方法
        recursiveFindCategory(0,arrayList);
        //查看
//        System.out.println(arrayList.toString());
        return arrayList;
    }
    private void recursiveFindCategory(Integer parentId,List<CategoryVO> categoryVOList){
        //手写mapper的查询selectByParentId
        List<Category> categoryList = categoryMapper.selectByParentId(parentId);
        //判断categoryList是不是空
//        categoryList ==null,这样子判断不够全面
//        CollectionUtils.isEmpty()的方法判断更全面,return collection == null || collection.isEmpty();
        if(!CollectionUtils.isEmpty(categoryList)){
            for (int i = 0; i < categoryList.size(); i++) {
                Category category = categoryList.get(i);
                CategoryVO categoryVO = new CategoryVO();
                BeanUtils.copyProperties(category,categoryVO);
//                categoryList.add(categoryVO);
                categoryVOList.add(categoryVO);
                recursiveFindCategory(categoryVO.getId(),categoryVO.getChildCategory());
            }
        }
    }

mapper

List<Category> selectByParentId(Integer parentId);

<select id="selectByParentId" parameterType="integer" resultMap="BaseResultMap">
    select <include refid="Base_Column_List"></include>
    from imooc_mall_category
    where parent_id=#{parentId}
  </select>

 测试可以

10.Redis缓存技术提高响应

前提是下载安装Redis并运行启动Redis

https://github.com/MicrosoftArchive/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>

配置文件

​ 主启动类加注解,启动缓存功能

缓存是为了提高用户查看目录列表查询速度,所以在该方法上加注解 

 缓存配置类,写死的

package com.hw.springbootmall.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;
    }
}

 查询的对象的类必须实现序列化接口

​ 

 测试速度提高了很多

  

11.商品分类模块总结

六、商品模块开发

1.添加商品接口

新建Controller类

package com.hw.springbootmall.controller;

import com.hw.springbootmall.common.ApiResponseObj;
import com.hw.springbootmall.exception.MallExeception;
import com.hw.springbootmall.model.request.AddProductReq;
import com.hw.springbootmall.service.ProductService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import javax.validation.Valid;

/**
 * @Author: Aiver
 * @Date: 2023/02/09~~16:22
 * @Description:后台商品管理Controller
 */
@RestController
public class ProductAdminController {
    @Autowired
    ProductService productService;
    @PostMapping("/admin/product/add")
    public ApiResponseObj addProductForAdmin(@Valid @RequestBody AddProductReq addProductReq) throws MallExeception {
        productService.add(addProductReq);
        return ApiResponseObj.success();
    }
}

新建Request类,复制pojo并加上或者删除一些字段

package com.hw.springbootmall.model.request;

import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import java.util.Date;

/**
 * @author Aiver
 */
public class AddProductReq {

    @NotNull(message = "商品名称不能是空")
    private String name;
    @NotNull(message = "商品图片不能是空")
    private String image;

    private String detail;
    @NotNull(message = "商品分类不能是空")
    private Integer categoryId;

    @NotNull(message = "商品价格不能是空")
    @Min(value = 1,message = "商品价格最小是一分钱")
    private Integer price;

    @NotNull(message = "商品库存不能是空")
    @Max(value = 10000,message = "商品库存最多是一万")
    private Integer stock;

    private Integer status;

    

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name == null ? null : name.trim();
    }

    public String getImage() {
        return image;
    }

    public void setImage(String image) {
        this.image = image == null ? null : image.trim();
    }

    public String getDetail() {
        return detail;
    }

    public void setDetail(String detail) {
        this.detail = detail == null ? null : detail.trim();
    }

    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;
    }
    
}

Service实现类

下面的

//int count = productMapper.insert(product);
改为
int count = productMapper.insertSelective(product);
package com.hw.springbootmall.service.impl;

import com.hw.springbootmall.exception.ExceptionEnum;
import com.hw.springbootmall.exception.MallExeception;
import com.hw.springbootmall.model.dao.ProductMapper;
import com.hw.springbootmall.model.pojo.Product;
import com.hw.springbootmall.model.request.AddProductReq;
import com.hw.springbootmall.service.ProductService;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * @Author: Aiver
 * @Date: 2023/02/09~~19:08
 * @Description:product的service层
 */
@Service
public class ProductServiceImpl implements ProductService {
    @Autowired
    ProductMapper productMapper;
    @Override
    public void add(AddProductReq addProductReq) throws MallExeception {
        Product product = new Product();
        BeanUtils.copyProperties(addProductReq, product);
        Product productOld = productMapper.mySelectByName(product.getName());
        if (productOld != null) {
            //说明原先已经有重名的商品了
            throw new MallExeception(ExceptionEnum.NAME_EXISTED);
        }
        int count = productMapper.insert(product);
        if (count==0) {
            throw new MallExeception(ExceptionEnum.INSERT_FAILED);
        }
    }
}

不然会有如下报错

 mapper

<select id="mySelectByName" parameterType="string" resultType="com.hw.springbootmall.model.pojo.Product">
      select <include refid="Base_Column_List"></include>
      from imooc_mall_product
      where name = #{name,jdbcTye=VARCHAR}
    </select>

 从报错信息中可以看到,是在ProductMapper.xml中出现了拼写错误,位于#{name}中的“jdbcType”错误的写为了“jdbcTye”。

package com.hw.springbootmall.model.dao;

import com.hw.springbootmall.model.pojo.Product;
import org.springframework.stereotype.Repository;

@Repository
public interface ProductMapper {
    int deleteByPrimaryKey(Integer id);

    int insert(Product record);

    int insertSelective(Product record);

    Product selectByPrimaryKey(Integer id);

    int updateByPrimaryKeySelective(Product record);

    int updateByPrimaryKey(Product record);

    Product mySelectByName(String name);
}

 调试成功

到这里还有一个图片信息没有添加上去,所以接下来搞搞图片上传。

2.图片上传接口

 定义图片上传地址,我是本机的E:\test\MallUploadPicture

在常量类: 

后面发现注入不了,原来是静态变量,修改如下

package com.hw.springbootmall.common;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

/**
 * @Author: Aiver
 * @Date: 2023/02/05~~23:39
 * @Description:常量类
 */
@Component
public class Quantity {
    public static final String SALT="328hdweuui,.";
    public static final String MALL_USER="MALL_USER";
    public static  String UPLOAD_FILE_DIR;

    @Value("${UPLOAD_FILE_DIR}")
    public void setUploadFileDir(String uploadFileDir){
        UPLOAD_FILE_DIR = uploadFileDir;
    }
}

  springboot的属性配置文件,错误实例,会一直说创建文件夹失败。

把路径错误识别为

E:\testMallUploadPicture

但是实际上正确的路径是

E:\test\MallUploadPicture

还没完全正确示例

完完全全正确示例

同时也在常量类修改,修改前

修改后,没完全正确示例

 完完全全正确示例

 测试成功

本地文件夹也有图片了

3.静态资源映射开发

在配置类加上一行即可

 然后测试成功,浏览器可以访问本地资源,图片可以正常回显了。

4.更新和删除商品接口

更新接口

更新的Request类,注意有id了,而且get和set方法也要记得补全。

package com.hw.springbootmall.model.request;

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

/**
 * @author Aiver
 */
public class UpdateProductReq {

    @NotNull
    private Integer id;
    private String name;
    private String image;

    private String detail;
    private Integer categoryId;

    @Min(value = 1,message = "商品价格最小是一分钱")
    private Integer price;

    @Max(value = 10000,message = "商品库存最多是一万")
    private Integer stock;

    private Integer status;

    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 == null ? null : name.trim();
    }

    public String getImage() {
        return image;
    }

    public void setImage(String image) {
        this.image = image == null ? null : image.trim();
    }

    public String getDetail() {
        return detail;
    }

    public void setDetail(String detail) {
        this.detail = detail == null ? null : detail.trim();
    }

    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;
    }
    
}

 controller层

/**
     * 更新功能
     * @param updateProductReq
     * @return
     * @throws MallExeception
     */
    @ApiOperation("后台更新商品")
    @PostMapping("/admin/product/updateProductForAdmin")
    public ApiResponseObj updateProductForAdmin(@Valid @RequestBody UpdateProductReq updateProductReq) throws MallExeception {
            productService.update(updateProductReq);
            return  ApiResponseObj.success();
    }

service层:

@Override
    public void update(UpdateProductReq updateProductReq) throws MallExeception {
        Product product = new Product();
        BeanUtils.copyProperties(updateProductReq, product);
        //使用查询名称方法而不是查询主键
        Product productOld = productMapper.mySelectByName(product.getName());
        //Product productOld = productMapper.selectByPrimaryKey(product.getId());
        if(productOld != null && !productOld.equals(product.getId())){
            //商品同名存在而且id不一样就不能继续更新
            throw new MallExeception(ExceptionEnum.NAME_EXISTED);
        }
        //注意这里的updateByPrimaryKeySelective传入的参数不是是Request类updateProductReq,而是pojo类的Product即 product
        int updateCount = productMapper.updateByPrimaryKeySelective(product);
        if (updateCount == 0) {
            throw new MallExeception(ExceptionEnum.UPDATE_FAILED);
        }
    }

测试成功

 删除接口,容易

@ApiOperation("后台删除商品")
    @PostMapping("/admin/product/deleteProductForAdmin")
    public ApiResponseObj deleteProductForAdmin(@RequestParam Integer id) throws MallExeception {
        productService.delete(id);
        return ApiResponseObj.success();
    }
@Override
    public void delete(Integer id) throws MallExeception {
        Product product = productMapper.selectByPrimaryKey(id);
        if (product == null) {
            //商品不存在,删除失败
            throw new MallExeception(ExceptionEnum.DELETE_FAILED);
        }
        int count = productMapper.deleteByPrimaryKey(id);
        if (count == 0) {
            throw new MallExeception(ExceptionEnum.DELETE_FAILED);
        }
    }

5.批量上下架商品接口,sql语句有难度!

mapper最难

<!--  foreach表示循环-->
<!--  collection表示指定迭代的数据源-->
<!--  注:collection ="",这个参数是mapper接口方法里面传过来的集合参数-->
<!--  item是变量值-->
<!--  open是该语句以什么开始-->
<!--  separator是指分隔符,每组数据之间使用逗号分隔-->
<!--  close是以什么结束-->
<!--  #{}就是获取数据-->
    <update id="batchUpdateSellStatus">
      update imooc_mall_product
      set status=#{sellStatus}
      where id in
      <foreach collection="ids" close=")" item="id" open="(" separator=",">
        #{id}
      </foreach>

    </update>
@Override
    public void batchUpdateSellStatus(Integer[] ids, Integer sellStatus){
        int count = productMapper.batchUpdateSellStatus(ids, sellStatus);
    }
@ApiOperation("后台批量上下架商品")
    @PostMapping("/admin/product/batchUpdateSellStatus")
    public ApiResponseObj batchUpdateSellStatus(@RequestParam Integer[] ids,@RequestParam Integer sellStatus) throws MallExeception {
        productService.batchUpdateSellStatus(ids, sellStatus);
        return ApiResponseObj.success();
    }

 测试注意 2,3数组之间是英文输入法的逗号,如果是中文输入法那么就不行,会误以为是String的。如下报错

02:10 19:37:56.269] [ERROR] [com.hw.springbootmall.exception.GlobalExceptionHandler] - 系统异常来的org.springframework.web.method.annotation.MethodArgumentTypeMismatchException: Failed to convert value of type 'java.lang.String' to required type 'java.lang.Integer[]'; nested exception is java.lang.NumberFormatException: For input string: "2,3"

6.后台商品列表和商品详情接口

7.前台商品列表接口(上)

新建一个前台商品Controller,而service就不要新建了,和后台商品controller共用就行

package com.hw.springbootmall.controller;

import com.hw.springbootmall.common.ApiResponseObj;
import com.hw.springbootmall.model.pojo.Product;
import com.hw.springbootmall.service.ProductService;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

/**
 * @Author: Aiver
 * @Date: 2023/02/10~~21:01
 * @Description:前台商品Controller
 */
@RestController
public class ProductUserController {
    @Autowired
    ProductService productService;

    @ApiOperation(value = "商品详情")
    @RequestMapping("/product/user/detail")
    public ApiResponseObj detail(@RequestParam Integer id){
        Product product = productService.detail(id);
        return ApiResponseObj.success(product);
    }
}

 service

@Override
    public Product detail(Integer id){
        Product product = productMapper.selectByPrimaryKey(id);
        return product;
    }

测试成功

8.前台商品列表接口(下)

非常非常难,接口有搜索排序等功能,应该单独写一篇出来讲讲,放在我主页其他文章。

新建ProductListReq类

9.商品模块测试与总结

七、购物车模块开发

付费内容

八、订单模块开发

 

付费内容

九、上线部署

付费内容

十、一些疑问与解答

疑问1

 

解答1 

 缺少ApiRestResponse是因为在Controller中返回时,Spring Boot会默认使用Jackson将Controller中返回的对象转换为json,所以缺少ApiRestResponse。但在success与error中返回的是ApiRestResponse对象,也就是会输出toString()方法中的内容。如下所示

 

 疑问2

为什么会单独拆出来InsertCategoryReq?

解答2

这一点体现了Java中一个类实现一种单一功能,比如规则校验@valid时候,注解不能在pojo普通Java对象上。有时候是add目录,而有时候是update目录等等,那么字段上面的注解就写不了了,因为add和update的规则可能不同。

疑问3 

127.0.0.1变成localhost,更新目录接口就只会一直提示没有登陆。被坑了一下午。

解答3

127.0.0.1和localhost不同源

错误4 type写错tye

 错误5 productMapper.insert 和productMapper.insertSelective

//int count = productMapper.insert(product);
        int count = productMapper.insertSelective(product);

疑问6

  1. 更新为什么不用id去查对应的商品,而是用商品名称去查?并且在用商品名称查询后还是需要是验证同名而id不同的校验。

  2. 新增时是在service里面做的 BeanUtils.copyProperties(),更新又是在controller里面做的BeanUtils.copyProperties(),为什么要这么做?这两个不是同一个性质的东西吗?出于什么考虑这么做呢

  3. 为什么一个在service中做copyProperties的操作,一个在controller中做copyProperties的操作?这两个有什么区别

解答6

问题一:在业务逻辑中,是不允许有同名商品的。而商品名是由前台传递过来的,那么此时:

1.如果根据商品名没有查询到任何商品对象,那么此商品名一定与数据库其他商品信息不重名;

2.如果根据商品名查询到了对应的对象,那么此时就存在重名的可能性。此时就需要去判断,这里到底是“重名”还是“同一个商品”,这里就需要根据id再处理一次。

此处如果直接根据id去查询,那么确实能够保证在修改商品信息,不过就无法保证商品名不重复了。

问题二:这里使用BeanUtils类的copyProperties是为了简化属性的赋值过程。这里UpdateProductReq中的字段和Product中的字段并不是完全一样的,那么属性间的赋值就需要大量的调用UpdateProductReq中的get方法和Product中的set方法。为了简化这个过程,直接使用了工具方法copyProperties。

问题三:

此处规范的做法是统一将copyProperties的操作放在Service中,而不是在Controller中。

在实际项目中,如果严格按照Controller-Service-Dao的结构书写代码时,Controller的作用仅应当为以下三点:

1.接收请求参数;

2.传递数据,调用Service方法;

3.返回视图数据。

由于Controller只是传递数据,而不应当处理数据,所有数据的处理,包括对数据的封装或者再封装,都应当在对应的Service中进行。

所以课程中如果严格按照开发规范处理,应当都在Service中进行处理。

但是实际开发时,确实有很多情况下数据也会在Controller中进行处理,例如Service的接口制定时参数类型与Controller中获取的数据类型不一致,但是Service已经开发完成时。此时就会违背上面的原则,在Controller中对数据进行加工处理。

综上,建议尽量遵守规则,除非没有其他解决方案,不要在Controller中进行数据的处理。

十一、数据库创建SQL语句


/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8 */;
SET NAMES utf8mb4;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;


# Dump of table imooc_mall_cart
# ------------------------------------------------------------

DROP TABLE IF EXISTS `imooc_mall_cart`;

CREATE TABLE `imooc_mall_cart` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '购物车id',
  `product_id` int(11) NOT NULL COMMENT '商品id',
  `user_id` int(11) NOT NULL COMMENT '用户id',
  `quantity` int(11) NOT NULL DEFAULT '1' COMMENT '商品数量',
  `selected` int(11) NOT NULL DEFAULT '1' COMMENT '是否已勾选:0代表未勾选,1代表已勾选',
  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4  COMMENT='购物车';



# Dump of table imooc_mall_category
# ------------------------------------------------------------

DROP TABLE IF EXISTS `imooc_mall_category`;

CREATE TABLE `imooc_mall_category` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
  `name` varchar(32) NOT NULL DEFAULT '' COMMENT '分类目录名称',
  `type` int(11) NOT NULL COMMENT '分类目录级别,例如1代表一级,2代表二级,3代表三级',
  `parent_id` int(11) NOT NULL COMMENT '父id,也就是上一级目录的id,如果是一级目录,那么父id为0',
  `order_num` int(11) NOT NULL COMMENT '目录展示时的排序',
  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4  COMMENT='商品分类 ';

LOCK TABLES `imooc_mall_category` WRITE;
/*!40000 ALTER TABLE `imooc_mall_category` DISABLE KEYS */;

INSERT INTO `imooc_mall_category` (`id`, `name`, `type`, `parent_id`, `order_num`, `create_time`, `update_time`)
VALUES
	(3,'新鲜水果',1,0,1,'2019-12-18 01:17:00','2019-12-28 17:11:26'),
	(4,'橘子橙子',2,3,1,'2019-12-18 01:17:00','2019-12-28 16:25:10'),
	(5,'海鲜水产',1,0,2,'2019-12-18 01:17:00','2019-12-28 16:25:20'),
	(6,'精选肉类',1,0,3,'2019-12-18 01:17:00','2019-12-28 16:25:21'),
	(7,'螃蟹',2,5,1,'2019-12-18 01:17:00','2019-12-28 16:25:15'),
	(8,'鱼类',2,5,2,'2019-12-18 01:17:00','2019-12-28 16:25:16'),
	(9,'冷饮冻食',1,0,4,'2019-12-20 13:45:28','2019-12-28 16:25:22'),
	(10,'蔬菜蛋品',1,0,5,'2019-12-20 13:45:28','2019-12-28 16:25:23'),
	(11,'草莓',2,3,2,'2019-12-18 01:17:00','2019-12-28 15:44:42'),
	(12,'奇异果',2,3,3,'2019-12-18 01:17:00','2019-12-28 16:25:12'),
	(13,'海参',2,5,3,'2019-12-18 01:17:00','2019-12-28 16:25:17'),
	(14,'车厘子',2,3,4,'2019-12-18 01:17:00','2019-12-28 16:25:12'),
	(15,'火锅食材',2,27,5,'2019-12-18 01:17:00','2020-02-11 00:42:33'),
	(16,'牛羊肉',2,6,1,'2019-12-18 01:17:00','2019-12-28 16:25:18'),
	(17,'冰淇淋',2,9,1,'2019-12-18 01:17:00','2019-12-28 16:25:18'),
	(18,'蔬菜综合',2,10,1,'2019-12-18 01:17:00','2020-02-11 00:48:27'),
	(19,'果冻橙',3,4,1,'2019-12-18 01:17:00','2020-02-11 00:37:02'),
	(27,'美味菌菇',1,0,7,'2019-12-20 13:45:28','2020-02-10 23:20:36'),
	(28,'其他水果',2,3,4,'2019-12-18 01:17:00','2019-12-28 16:25:12');

/*!40000 ALTER TABLE `imooc_mall_category` ENABLE KEYS */;
UNLOCK TABLES;


# Dump of table imooc_mall_order
# ------------------------------------------------------------

DROP TABLE IF EXISTS `imooc_mall_order`;

CREATE TABLE `imooc_mall_order` (
  `id` int(64) NOT NULL AUTO_INCREMENT COMMENT '主键id',
  `order_no` varchar(128) NOT NULL DEFAULT '' COMMENT '订单号(非主键id)',
  `user_id` int(64) NOT NULL COMMENT '用户id',
  `total_price` int(64) NOT NULL COMMENT '订单总价格',
  `receiver_name` varchar(32) NOT NULL COMMENT '收货人姓名快照',
  `receiver_mobile` varchar(32) NOT NULL COMMENT '收货人手机号快照',
  `receiver_address` varchar(128) NOT NULL DEFAULT '' COMMENT '收货地址快照',
  `order_status` int(10) NOT NULL DEFAULT '10' COMMENT '订单状态: 0用户已取消,10未付款(初始状态),20已付款,30已发货,40交易完成',
  `postage` int(10) DEFAULT '0' COMMENT '运费,默认为0',
  `payment_type` int(4) NOT NULL DEFAULT '1' COMMENT '支付类型,1-在线支付',
  `delivery_time` timestamp NULL DEFAULT NULL COMMENT '发货时间',
  `pay_time` timestamp NULL DEFAULT NULL COMMENT '支付时间',
  `end_time` timestamp NULL DEFAULT NULL COMMENT '交易完成时间',
  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4  COMMENT='订单表;';



# Dump of table imooc_mall_order_item
# ------------------------------------------------------------

DROP TABLE IF EXISTS `imooc_mall_order_item`;

CREATE TABLE `imooc_mall_order_item` (
  `id` int(64) NOT NULL AUTO_INCREMENT COMMENT '主键id',
  `order_no` varchar(128) NOT NULL DEFAULT '' COMMENT '归属订单id',
  `product_id` int(11) NOT NULL COMMENT '商品id',
  `product_name` varchar(100) NOT NULL DEFAULT '' COMMENT '商品名称',
  `product_img` varchar(128) NOT NULL DEFAULT '' COMMENT '商品图片',
  `unit_price` int(11) NOT NULL COMMENT '单价(下单时的快照)',
  `quantity` int(10) NOT NULL DEFAULT '1' COMMENT '商品数量',
  `total_price` int(11) NOT NULL DEFAULT '0' COMMENT '商品总价',
  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4  COMMENT='订单的商品表 ';



# Dump of table imooc_mall_product
# ------------------------------------------------------------

DROP TABLE IF EXISTS `imooc_mall_product`;

CREATE TABLE `imooc_mall_product` (
  `id` int(64) NOT NULL AUTO_INCREMENT COMMENT '商品主键id',
  `name` varchar(100) NOT NULL COMMENT '商品名称',
  `image` varchar(500) NOT NULL DEFAULT '' COMMENT '产品图片,相对路径地址',
  `detail` varchar(500) CHARACTER SET utf8mb4 DEFAULT '' COMMENT '商品详情',
  `category_id` int(11) NOT NULL COMMENT '分类id',
  `price` int(11) NOT NULL COMMENT '价格,单位-分',
  `stock` int(11) NOT NULL COMMENT '库存数量',
  `status` int(6) NOT NULL DEFAULT '1' COMMENT '商品上架状态:0-下架,1-上架',
  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4  COMMENT='商品表';

LOCK TABLES `imooc_mall_product` WRITE;
/*!40000 ALTER TABLE `imooc_mall_product` DISABLE KEYS */;

INSERT INTO `imooc_mall_product` (`id`, `name`, `image`, `detail`, `category_id`, `price`, `stock`, `status`, `create_time`, `update_time`)
VALUES
	(2,'澳洲进口大黑车厘子大樱桃包甜黑樱桃大果多汁 500g 特大果','http://111.231.103.117:8081/images/chelizi2.jpg','商品毛重:1.0kg货号:608323093445原产地:智利类别:美早热卖时间:1月,11月,12月国产/进口:进口售卖方式:单品',14,50,100,1,'2019-12-18 16:08:15','2020-02-11 00:08:25'),
	(3,'茶树菇 美味菌菇 东北山珍 500g','http://111.231.103.117:8081/images/chashugu.jpg','商品名:茶树菇 商品特点:美味菌菇 东北山珍 500g',15,1000,6,1,'2019-12-18 16:10:50','2020-02-11 00:42:42'),
	(14,'Zespri佳沛 新西兰阳光金奇异果 6个装','http://111.231.103.117:8081/images/mihoutao2.jpg','商品编号:4635056商品毛重:0.71kg商品产地:新西兰类别:金果包装:简装国产/进口:进口原产地:新西兰',12,39,77,1,'2019-12-18 16:11:13','2020-02-10 23:36:48'),
	(17,'红颜奶油草莓 约重500g/20-24颗 新鲜水果','http://111.231.103.117:8081/images/caomei2.jpg','商品毛重:0.58kg商品产地:丹东/南通/武汉类别:红颜草莓包装:简装国产/进口:国产',11,99,84,1,'2019-12-18 16:11:13','2020-02-10 23:37:48'),
	(21,'智利原味三文鱼排(大西洋鲑)240g/袋 4片装','http://111.231.103.117:8081/images/sanwenyu2.jpg','商品毛重:260.00g商品产地:中国大陆保存状态:冷冻国产/进口:进口包装:简装类别:三文鱼海水/淡水:海水烹饪建议:煎炸,蒸菜,烧烤原产地:智利',8,499,1,1,'2019-12-28 15:13:07','2020-02-10 23:38:46'),
	(22,'即食海参大连野生辽刺参 新鲜速食 特级生鲜海产 60~80G','http://111.231.103.117:8081/images/haishen.jpg','商品毛重:1.5kg商品产地:中国大陆贮存条件:冷冻重量:50-99g国产/进口:国产适用场景:养生滋补包装:袋装原产地:辽宁年限:9年以上等级:特级食品工艺:冷冻水产热卖时间:9月类别:即食海参固形物含量:70%-90%特产品类:大连海参售卖方式:单品',13,699,3,1,'2019-12-28 15:16:29','2020-02-11 00:04:29'),
	(23,'澳大利亚直采鲜橙 精品澳橙12粒 单果130-180g','http://111.231.103.117:8081/images/chengzi.jpg','商品毛重:2.27kg商品产地:澳大利亚类别:脐橙包装:简装国产/进口:进口原产地:澳大利亚',4,12,12,1,'2019-12-28 16:02:13','2020-02-11 00:40:15'),
	(24,'智利帝王蟹礼盒装4.4-4.0斤/只 生鲜活鲜熟冻大螃蟹','http://111.231.103.117:8081/images/diwangxie.jpg','商品毛重:3.0kg商品产地:智利大闸蟹售卖方式:公蟹重量:2000-4999g套餐份量:5人份以上国产/进口:进口海水/淡水:海水烹饪建议:火锅,炒菜,烧烤,刺身,加热即食包装:简装原产地:智利保存状态:冷冻公单蟹重:5.5两及以上分类:帝王蟹特产品类:其它售卖方式:单品',7,222,222,1,'2019-12-28 16:06:34','2020-02-11 00:05:05'),
	(25,'新疆库尔勒克伦生无籽红提 国产新鲜红提葡萄 提子 5斤装','http://111.231.103.117:8081/images/hongti.jpg','商品毛重:2.5kg商品产地:中国大陆货号:XZL201909002重量:2000-3999g套餐份量:2人份国产/进口:国产是否有机:非有机单箱规格:3个装,4个装,5个装类别:红提包装:简装原产地:中国大陆售卖方式:单品',28,222,222,1,'2019-12-28 16:06:34','2020-02-11 00:44:05'),
	(26,'越南进口红心火龙果 4个装 红肉中果 单果约330-420g','http://111.231.103.117:8081/images/hongxinhuolongguo.jpg','商品毛重:1.79kg商品产地:越南重量:1000-1999g类别:红心火龙果包装:简装国产/进口:进口',28,222,222,1,'2019-12-28 16:06:34','2020-02-11 00:44:11'),
	(27,'内蒙古羔羊肉串 500g/袋(约20串)鲜冻羊肉串 BBQ烧烤食材','http://111.231.103.117:8081/images/yangrouchuan.jpg','商品毛重:0.585kg商品产地:内蒙古巴彦淖尔市保存状态:冷冻重量:500-999g套餐份量:3人份国产/进口:国产烹饪建议:烧烤原产地:内蒙古品种:其它热卖时间:4月,5月,6月,7月,8月,9月,10月,11月,12月饲养方式:圈养类别:羊肉串包装:简装套餐周期:12个月',16,222,222,1,'2019-12-28 16:06:34','2020-02-11 00:11:30'),
	(28,'玛琪摩尔新西兰进口冰淇淋大桶装','http://111.231.103.117:8081/images/bingqilin.jpg','商品毛重:1.04kg商品产地:新西兰国产/进口:进口包装:量贩装',17,222,222,1,'2019-12-28 16:06:34','2020-02-11 00:10:40'),
	(29,'西兰花沙拉菜 350g 甜玉米粒 青豆豌豆 胡萝卜冷冻方便蔬菜','http://111.231.103.117:8081/images/shalacai.jpg','商品毛重:370.00g商品产地:浙江宁波重量:500g以下套餐份量:家庭装类别:速冻玉米/豌豆包装:简装烹饪建议:炒菜,炖菜,煎炸,蒸菜售卖方式:单品',18,222,222,1,'2019-12-28 16:06:34','2020-02-11 00:34:01'),
	(36,'四川果冻橙 吹弹可破','http://111.231.103.117:8081/images/guodongcheng.jpg','商品毛重:370.00g商品产地:四川 重量:1000g',19,222,222,1,'2019-12-28 16:06:34','2020-02-11 00:38:14'),
	(37,'进口牛油果 中果6粒装 单果约130-160g ','http://111.231.103.117:8081/images/niuyouguo.jpg','商品名称:京觅进口牛油果 6个装商品编号:3628240商品毛重:1.2kg商品产地:秘鲁、智利、墨西哥重量:1000g以下国产/进口:进口',28,222,222,1,'2019-12-28 16:06:34','2020-02-11 00:47:42'),
	(38,'中街1946网红雪糕冰淇淋','http://111.231.103.117:8081/images/bingqilin2.jpg','商品名称:中街1946网红雪糕冰淇淋乐享系列半巧*5牛乳*5阿棕*2冰激凌冷饮冰棍冰棒商品编号:52603405444店铺: 中街1946官方旗舰店商品毛重:1.3kg商品产地:中国大陆国产/进口:国产包装:量贩装售卖方式:组合',17,222,222,1,'2019-12-28 16:06:34','2020-02-11 00:50:54'),
	(39,'福建六鳌红薯5斤','http://111.231.103.117:8081/images/hongshu.jpg','商品名称:京觅福建六鳌红薯5斤商品编号:4087121商品毛重:2.8kg商品产地:福建省漳浦县六鳌镇重量:2500g及以上烹饪建议:煎炸,蒸菜,烧烤包装:简装分类:地瓜/红薯售卖方式:单品',18,40,222,1,'2019-12-28 16:06:34','2020-02-11 00:51:59'),
	(40,'胡萝卜','http://111.231.103.117:8081/images/huluobo.jpg','商品名称:绿鲜知胡萝卜商品编号:4116192商品毛重:1.07kg商品产地:北京包装:简装分类:萝卜烹饪建议:火锅,炒菜,炖菜',18,222,222,1,'2019-12-28 16:06:34','2020-02-11 00:53:25'),
	(41,'羊肉卷 内蒙羔羊肉 鲜嫩 500g/袋 首农出品 羊排肉卷 火锅食材','http://111.231.103.117:8081/images/yangroujuan.jpg','商品名称:首食惠羊排片商品编号:4836347商品毛重:0.51kg商品产地:辽宁省大连市保存状态:冷冻品种:其它国产/进口:进口饲养方式:散养类别:羊肉片/卷包装:简装烹饪建议:火锅,炒菜,炖菜原产地:新西兰',16,222,222,1,'2019-12-28 16:06:34','2020-02-11 00:48:03'),
	(42,'甜玉米 切好 香甜','http://111.231.103.117:8081/images/tianyumi.jpg','品牌: 绿鲜知(greenseer)\n商品名称:绿鲜知甜玉米商品编号:4983604商品毛重:1.1kg商品产地:云南玉溪类别:玉米',18,240,222,1,'2019-12-28 16:06:34','2020-02-11 00:52:19');

/*!40000 ALTER TABLE `imooc_mall_product` ENABLE KEYS */;
UNLOCK TABLES;


# Dump of table imooc_mall_user
# ------------------------------------------------------------

DROP TABLE IF EXISTS `imooc_mall_user`;

CREATE TABLE `imooc_mall_user` (
  `id` int(64) NOT NULL AUTO_INCREMENT COMMENT '用户id',
  `username` varchar(32) NOT NULL DEFAULT '' COMMENT '用户名',
  `password` varchar(50) NOT NULL COMMENT '用户密码,MD5加密',
  `personalized_signature` varchar(50) NOT NULL DEFAULT '' COMMENT '个性签名',
  `role` int(4) NOT NULL DEFAULT '1' COMMENT '角色,1-普通用户,2-管理员',
  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4  COMMENT='用户表 ';

LOCK TABLES `imooc_mall_user` WRITE;
/*!40000 ALTER TABLE `imooc_mall_user` DISABLE KEYS */;

INSERT INTO `imooc_mall_user` (`id`, `username`, `password`, `personalized_signature`, `role`, `create_time`, `update_time`)
VALUES
	(1,'1','1','3',1,'2019-12-16 02:37:33','2020-02-09 18:41:12'),
	(2,'xiaomu','AWRuqaxc6iryhHuA4OnFag==','更新了我的签名',2,'2019-12-17 15:11:32','2020-02-10 09:52:12'),
	(3,'你好','AWRuqaxc6iryhHuA4OnFag==','',1,'2019-12-20 13:41:03','2020-02-10 09:52:15'),
	(4,'111','G72IZGCCcBXl1gXtRCUiUQ==','',1,'2019-12-27 19:34:56','2019-12-27 19:34:56'),
	(5,'444','uFfu1clAXB8rmASKrlBnkg==','cecc',1,'2019-12-27 19:38:03','2019-12-28 01:04:06'),
	(6,'你好2','JdVa0oOqQAr0ZMdtcTwHrQ==','',1,'2020-02-08 17:47:06','2020-02-08 17:47:06'),
	(7,'你好3','JdVa0oOqQAr0ZMdtcTwHrQ==','',1,'2020-02-08 17:49:15','2020-02-08 17:49:15'),
	(8,'你好4','12345678','',1,'2020-02-09 19:49:54','2020-02-09 19:49:54'),
	(9,'xiaomu2','AWRuqaxc6iryhHuA4OnFag==','祝你今天好心情',2,'2020-02-09 20:39:47','2020-02-11 00:56:02');

/*!40000 ALTER TABLE `imooc_mall_user` ENABLE KEYS */;
UNLOCK TABLES;



/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;

  • 6
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值