目录
5、订单表,订单号属于商家内部数据不公开,是单独拿出来,不能是主键ID,否则订单号是主键逐渐递增就很容易暴露订单数量了。
1.新建一个spring Inaliaze工程项目,spring选2的主流版本,Java8.还有就是加上spring web。
3.新建一个文件generatorConfig.xml编辑
3.GlobalExceptionHandler编写与MD5算法加密
错误5 productMapper.insert 和productMapper.insertSelective
一、工具准备与技术选型
工具准备: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&characterEncoding=UTF-8&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
-
更新为什么不用id去查对应的商品,而是用商品名称去查?并且在用商品名称查询后还是需要是验证同名而id不同的校验。
-
新增时是在service里面做的 BeanUtils.copyProperties(),更新又是在controller里面做的BeanUtils.copyProperties(),为什么要这么做?这两个不是同一个性质的东西吗?出于什么考虑这么做呢
-
为什么一个在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 */;