前言:这是我学SpringBoot以来第一个实际应用性的项目,还是有必要写个笔记记录一下,以便之后复习和部分知识重复利用。
一.概述
我上一篇博客写了关于SpringBoot的基本使用和入门,但是那篇博客的知识点还是过于基础,只能算入个门。SpringBoot有很多其他的高级知识点我还没有学习和使用。今天就借此机会来学习一下SpringBoot的那些高级的用法和与springboot配套的一些高级知识。开发一个电商应用,在这个电商项目的开发过程中来学习那些高级知识。
1.电商项目整体介绍
项目亮点:电商项目是互联网的主流技术,并且本项目的代码规范简洁,充分优化,流程完整,电商功能丰富,前后端分离。
首先,整个项目是分为前台和后台,这也是我们通常的一个系统架构。前台指的是用户所看到的页面,包括首页和商品详情等等的。那我们就从用户的视角出发,看看前台包含哪些内容。前台有五个模块,第一个是用户模块,第二个是商品分类模块,第三个模块是商品信息模块,第四个是购物车模块,最后一个是订单模块。而在后台呢?后台的模块分类和前台比较类似,分为用户模块、商品分类模块、商品信息模块、订单模块。相比于前台而言,后台少了购物车模块,因为购物车通常在前台处理就可以了,前台的购物车把信息直接汇总到一个订单发送到后台,所以在后台中不需要购物车模块。
(1)前台模块具体分析
这一个一个的模块的内容又包含哪些呢?就让我们先从前台的用户模块进行看起。在用户模块中,会有注册功能、登录功能、更新签名、身份认证功能、登出功能。更新签名指用户更新个人信息,而身份认证指的是我们在登录之后才能对一些功能进行操作。然后就是商品分类模块,因为商品通常是会分为各种各样的类别的,如果不分类,找起来或者是归类起来都非常地不友好。比如最大的类别是食品,食品下面有水果,而水果类别之内可能会有进口水果类别,会这样一层一层的,在我们的项目中同样会采用多级目录的形式进行开发。在在涉及多级目录的时候,我们也会学到一个非常有用的知识点,就是递归查询。学习如何在查完了这级目录之后去查他的子目录。同样一个非常重要的知识点就是缓存,因为我们的商品分类通常而言它变化的几率比较小,或者说它变化的频率也比较低,比如说我们几天才会进行变动,甚至对于品类相对固定的电商而言,可能一个礼拜都不会对商品分类做出很大的调整。这种情况就适合来使用缓存了。所以我们在这里,我们将会使用Redis把当前商品分类的内容作为缓存给保存下来。这样一来,我们就不需要再每次用户访问的时候都去查询数据库,大大提升了效率。 接下来就是商品模块,这是一个非常重点的模块。因为在这个模块中我们将学到许多新的功能,比如说搜索功能,搜索功能是电商中一个比较有用的能力,比如我输入一个关键字,他就能给出包含关键字的商品,同时还要对商品进行排序,比如说最常见的是按价格排序。而商品在展示的时候最常见的就是以列表的形式展示。所以在这个功能中,将会介绍一个列表应该如何开发,有哪些注意点,展示给用户的时候应该屏蔽哪些信息,保留哪些信息。然后就是按目录展示,比如用户只想看进口水果,那相当于它显示的是水果目录下的某个商品,则相当与是应该筛选条件。以及就是商品详情功能。在购物车模块中,我们将会做以下的这些功能,首先我们可以把商品加入到购物车,会显示购物车列表,以及我们可能想买一个商品买多份,就要对他的数量进行更改,有可能不想要了就把他移除购物车。还有购物车商品的勾选和反选。全选中和全部不选择的功能。 在前台中,还有最后一个模块,就是订单模块,他也是我们非常重要的模块之一,里面有下单的功能,还会有详细的订单流程和订单详情和取消订单、生成支付二维码的功能,生成二维码后,我们通过扫码的形式进行支付。同时每个用户都可以看到自己所拥有的订单,以及可以确认收货。
(2)后台模块具体分析
在后台中,我们需要添加一个管理员模块,因为后台页面并不是每一个人都能登录的,只有符合管理员身份的用户才能登录,所以这里的身份认证和之前用户的身份认证是有所不同的。而后台的第二个模块就是商品分类模块。我们记得前面讲前台商品分类的时候,更多的是一种展示,是一种缓存,而在后台呢就不一样了。首先会对商品分类进行一个列表的开发,除此之外,我们对于管理员而言,还要有对这些列表进行增加、修改和删除的能力。这些会在之后进行更详细的介绍。 然后,就是后台的商品模块,后台的商品模块比前台要复杂,后台的商品除了商品列表之外,在后台我们需要有新增商品的功能,而新增商品的时候就肯定会涉及到图片的上传,图片的上传,图片的命名等。商品是可以更新和删除的、商品也有批量上下架的功能。后台还有一个模块就是订单模块,在后台的订单模块中,会有订单列表、地址信息、发货和订单完结。
(3) 项目演示
这里的演示是包含前台页面的,但是我后面的代码编写是不包含前端。这是因为工作中大多数都是前后端分离的,前端是通过我们的后端的接口文档来渲染加载页面的。
下面我们先来介绍一下后台管理的部分。 我们可以看到,在后台管理系统中输入默认的管理员密码就能登陆。登录后,会有几个页面,分别是商品管理、分类管理、订单管理和个人信息。 首先,在商品管理里面,有商品的相关信息,,我们可以修改商品是否上下架。这个页面也会有分页。还可以对商品信息进行修改。还可以点击右上角新增商品。 然后就是分类管理的页面。 之后是订单管理和个人信息的页面。 接下来,再稍微介绍一下前台的页面
2 . 项目开发所需工具准备
这一小节,把这个项目开发所需要的工具全部介绍一遍,有了这个工具之后,我们后续开发起来效率会得到很大的提高。
下面先要介绍IDEA常用的优质插件的准备:maven helper、Free MyBatis Tool(可以快速生成mapper xml文件、识别一些mapper中的语法错误)。
然后就是安装一下postman。如果实在不想安装,可以注册一个账号,网页版一样用。毕竟也不是每个人的电脑C盘都很大。
postman用来测试接口的。我们新建请求之前,可以新建一个Collection,这相当于一个文件夹。然后就可以在里面创建请求。之后就可以使用了。
二. 数据库设计于项目初始化
这一部分主要包含数据库表的设计,和技术选型、思路,技术选型的内容具体而言就是我们会选择哪一些工具和框架架构。最后我们会新建一个项目,对项目进行基本地初始化,整合一些框架,然后跑一个接口试试。
紧接着,我们会引入log4j2日志组件,用它来帮助我们记录日志。接下来还会做最后一个工作,使用AOP统一处理请求日志,会把一些请求信息记录下来。
1.表设计
这个项目的数据库和表也会提供好,下面对表稍微了解一下就好了。 这个数据名叫shop,共有6张表。 mall_user用户表: mall_category商品分类目录表: mall_product商品表: mall_cart 购物车表: mall_order订单表: 里面要注意,订单编号并没有用id来表示,因为这样做不安全,很容易让别人猜到订单量。order_status是订单的状态,0表示用户取消、10表示未付款(初始状态)、20表示已付款、30表示已发货、40表示交易完成。
mall_order_item订单项表: 为什么要创建应该item表呢?因为我们应该订单下面会有多个商品,所以要记录订单内的项。
2. 项目的初始化
项目的初始化主要分为以下的几个步骤,新建项目、mybatis-generator进行配置自动生成Dao层文件,跑通接口。 先打开idea,然后创建应该springboot项目,项目的名称为mall: 之后选择版本和依赖,依赖先spring web就可以了: 项目创建好之后,我们需要导入两个额外的依赖:
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.25</version>
</dependency>
添加完这两个依赖之后,我们还要再添加一个插件,这个插件就是自动生成文件的,叫mybatis-generator-maven-plugin
<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>
接下来,我们还要在resource下面创建一个这个插件的配置文件,这个文件不用去记,用的时候复制改一下参数就行了: 但是要注意几点,就是先要把对应版本的mysql-connector-java复制到resource目录下面,然后配置classPathEntry的location为当前电脑的maven目录。
<?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>
<classPathEntry
location="D:\apache-maven-3.8.2\MavenRepository\mysql\mysql-connector-java\8.0.25\mysql-connector-java-8.0.25.jar"/>
<context id="MysqlTables" targetRuntime="MyBatis3">
<property name="autoDelimitKeywords" value="true"/>
<property name="beginningDelimiter" value="`"/>
<property name="endingDelimiter" value="`"/>
<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/shop?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull"
userId="root"
password="zc20020106">
<property name="nullCatalogMeansCurrent" value="true"/>
</jdbcConnection>
<javaTypeResolver>
<property name="forceBigDecimals" value="false"/>
</javaTypeResolver>
<javaModelGenerator targetPackage="com.haiexijun.mall.model.pojo"
targetProject="src/main/java">
<property name="enableSubPackages" value="true"/>
<property name="trimStrings" value="true"/>
<property name="immutable" value="false"/>
</javaModelGenerator>
<sqlMapGenerator targetPackage="mappers" targetProject="src/main/resources">
<property name="enableSubPackages" value="true"/>
</sqlMapGenerator>
<javaClientGenerator type="XMLMAPPER" targetPackage="com.haiexijun.mall.model.dao"
targetProject="src/main/java">
<property name="enableSubPackages" value="true"/>
</javaClientGenerator>
<table schema="root" tableName="mall_cart" domainObjectName="Cart"
enableCountByExample="false"
enableUpdateByExample="false" enableDeleteByExample="false" enableSelectByExample="false"
selectByExampleQueryId="false">
</table>
<table tableName="mall_category" domainObjectName="Category" enableCountByExample="false"
enableUpdateByExample="false" enableDeleteByExample="false" enableSelectByExample="false"
selectByExampleQueryId="false">
</table>
<table tableName="mall_order" domainObjectName="Order" enableCountByExample="false"
enableUpdateByExample="false" enableDeleteByExample="false" enableSelectByExample="false"
selectByExampleQueryId="false">
</table>
<table tableName="mall_order_item" domainObjectName="OrderItem"
enableCountByExample="false"
enableUpdateByExample="false" enableDeleteByExample="false" enableSelectByExample="false"
selectByExampleQueryId="false">
</table>
<table tableName="mall_product" domainObjectName="Product" enableCountByExample="false"
enableUpdateByExample="false" enableDeleteByExample="false" enableSelectByExample="false"
selectByExampleQueryId="false">
</table>
<table tableName="mall_user" domainObjectName="User" enableCountByExample="false"
enableUpdateByExample="false" enableDeleteByExample="false" enableSelectByExample="false"
selectByExampleQueryId="false">
</table>
</context>
</generatorConfiguration>
之后就可以双击右边的maven里面的mybatis-generator生成代码了。这一步之前,必须要确保数据库和表都创建好了,并且配置文件也配置好了。 我们运行好后,会发现这个插件真的帮我们生成好了,点击绿色的箭头就可以切换到dao相对应的xml映射中: 我们接下来。再对application.properties这文件进行下基础的配置:
spring.datasource.name=shop
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/shop?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=zc20020106
server.port=8083
mybatis.mapper-locations=classpath:mappers/*.xml
下面我们做一个简单的查询来测试一下我们的项目和数据库方面是否初始化成功。
我们之前生成的dao包里的mappers并没有自动生成spring注解,我们可以手动给他们都加上@Repository注解。我们还要给我们应用的启动类添加一个@MapperScan(basePackages = “dao包名”)的注解。这个注解用于扫描mapper映射,指向我们的dao包。
package com.haiexijun.mall;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@MapperScan(basePackages = "com.haiexijun.mall.model.dao")
public class MallApplication {
public static void main(String[] args) {
SpringApplication.run(MallApplication.class, args);
}
}
下面就创建好controller包、service包,并编写如下的类和接口: service包下创建UserService接口,这个接口不用加注解:
package com.haiexijun.mall.service;
import com.haiexijun.mall.model.pojo.User;
public interface UserService {
public User getUser();
}
在service包下面再创建一个impl包,用于存放接口的实现类,下面是UserServiceImpl类,我们在这个接口里面添加@Service注解,并在类里面用@Autowired通过类型引入UserMapper,我们通过UserMapper的selectByPrimaryKey方法通过主键查询数据:
package com.haiexijun.mall.service.impl;
import com.haiexijun.mall.model.dao.UserMapper;
import com.haiexijun.mall.model.pojo.User;
import com.haiexijun.mall.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl implements UserService {
@Autowired
UserMapper userMapper;
@Override
public User getUser() {
return userMapper.selectByPrimaryKey(4);
}
}
最后我们在controller包下面创建好UserController,并在里面引入UserService,配置好请求参数。
package com.haiexijun.mall.controller;
import com.haiexijun.mall.model.pojo.User;
import com.haiexijun.mall.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class UserController {
@Autowired
UserService userService;
@GetMapping("/test")
@ResponseBody
public User personalPage(){
return userService.getUser();
}
}
之后,就可以运行项目了。浏览器中运行结果如下,成功查询到数据:
3.配置log4j2日志组件
log4j2日志组件是我们开发中必不可少的一个关键组成部分。日志的级别(从高到低)有:error、warn、info、debug、trace。 我们在安装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>
还要再引入log4j2的依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
这个依赖不用设置版本号,因为它会工具我们spring-boot的版本自动匹配版本号。
下面我们还要在resources目录下创建一个log4j2的配置文件,如下:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="fatal">
<Properties>
<Property name="baseDir" value="${sys:user.home}/logs"/>
</Properties>
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<ThresholdFilter level="info" onMatch="ACCEPT"
onMismatch="DENY"/>
<PatternLayout
pattern="[%d{MM:dd HH:mm:ss.SSS}] [%level] [%logger{36}] - %msg%n"/>
</Console>
<RollingFile name="debug_appender" fileName="${baseDir}/debug.log"
filePattern="${baseDir}/debug_%i.log.%d{yyyy-MM-dd}">
<Filters>
<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>
<RollingFile name="info_appender" fileName="${baseDir}/info.log"
filePattern="${baseDir}/info_%i.log.%d{yyyy-MM-dd}">
<Filters>
<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>
<RollingFile name="error_appender" fileName="${baseDir}/error.log"
filePattern="${baseDir}/error_%i.log.%d{yyyy-MM-dd}">
<Filters>
<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>
4. AOP统一处理web请求日志
这是对于系统健壮性的一种保证,在真实的项目中,基本上都会有这样的功能,我们会用filter,把每个请求都给打印出来,这样的良好习惯可以提高我们开发和调试的效率。我们这一节的目的就是创建filter,把我们的请求信息和返回信息给打印出来。
下面就来实操演示,究竟应该如何配置这个功能:
要想引入AOP的功能,我们首先要引入一个依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
有了这个依赖之后,我们先要去建立一个过滤器,来对我们的请求来做一个拦截和打印。我们新建一个filter包,里面用来存放过滤器。 我们在里面创建一个WebLogAspect类:
package com.haiexijun.mall.filter;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.mvc.condition.RequestConditionHolder;
import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
@Aspect
@Component
public class WebLogAspect {
private final Logger log = LoggerFactory.getLogger(WebLogAspect.class);
@Pointcut("execution(public * com.haiexijun.mall.controller.*.*(..))")
public void webLog(){
}
@Before("webLog()")
public void doBefore(JoinPoint joinPoint){
ServletRequestAttributes attributes= (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request=attributes.getRequest();
log.info("URL : " + request.getRequestURL().toString());
log.info("HTTP_METHOD : " +request.getMethod());
log.info("IP : " + request.getRemoteAddr());
log.info("CLASS_METHOD : " + joinPoint.getSignature().getDeclaringTypeName()+"."+joinPoint.getSignature().getName());
log.info("ARGS : "+ Arrays.toString(joinPoint.getArgs()));
}
@AfterReturning(returning = "res",pointcut = "webLog()")
public void doAfterReturning(Object res) throws JsonProcessingException {
log.info("RESPONSE : "+new ObjectMapper().writeValueAsString(res));
}
}
浏览器运行,访问一下/test接口,控制台打印了请求和响应的相关信息。
三.用户模块的开发
1.用户模块整体介绍
这个模块会有登录,注册,注册会有重名校验,用户名不允许重复。同时,为了更加安全,我们还会对密码进行加密存储。在登录的时候我们是用session,可以把用户登录的信息给保存下来。还会进行越权校验,比如说没有登陆的情况下,它不允许修改,以及只能修改自己用户的一些信息等。之后我们还会学习统一响应对象的概念,这个概念非常的重要,他会贯穿我们整个项目。而在构建这个对象的时候,我们也会对异常进行处理,会构建java异常体系。之后会对postman进行一些使用上的讲解。以及统一异常处理的功能,因为异常我们如果不对其进行处理的话会有安全风险。所以我们用过滤器对其统一处理。
接口设计,我们会用到一个接口文档,我们可以先用这个文档来开发,后面会学习如何自己生成接口文档。
2.API统一返回对象
我们要创建一个包叫common包,这个包的作用就是用来存储一些统一的、通用的类,里面创建一个ApiRestResponse类,这个类就是我们的统一响应对象。
package com.haiexijun.mall.common;
import com.haiexijun.mall.exception.MallExceptionEnum;
public class ApiRestResponse<T> {
private Integer status;
private String msg;
private T data;
private static final int OK_CODE =10000;
private static final String OK_MSG="SUCCESS";
public ApiRestResponse() {
this(OK_CODE,OK_MSG);
}
public ApiRestResponse(Integer status, String msg, T data) {
this.status = status;
this.msg = msg;
this.data = data;
}
public ApiRestResponse(Integer status, String msg) {
this.status = status;
this.msg = msg;
}
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
@Override
public String toString() {
return "ApiRestResponse{" +
"status=" + status +
", msg='" + msg + '\'' +
", data=" + data +
'}';
}
public static <T> ApiRestResponse<T> success(){
return new ApiRestResponse<T>();
}
public static <T> ApiRestResponse<T> success(T result){
ApiRestResponse<T> response=new ApiRestResponse<T>();
response.setData(result);
return response;
}
public static <T> ApiRestResponse<T> error(MallExceptionEnum ex){
return new ApiRestResponse<T>(ex.getCode(), ex.getMsg());
}
public static <T> ApiRestResponse<T> error(Integer code,String msg){
return new ApiRestResponse<T>(code,msg);
}
}
我们还要考虑到用户输入了错误的信息之后,会返回错误信息和异常。所以我们要创建一个异常处理类,创建exception包,先在里面创建一个异常枚举类,里面有一些异常的枚举。
package com.haiexijun.mall.exception;
public enum MallExceptionEnum {
NEED_USER_NAME(10001,"用户名不能为空"),
PASSWORD_TOO_SHORT(10003,"密码长度不能小于八位"),
NEED_USER_PASSWORD(10002,"用户密码不能为空"),
NAME_EXISTED(10004,"用户名已存在,注册失败"),
INSERT_FAILED(10005,"插入失败,请重试"),
SYSTEM_ERROR(20000,"系统异常");
Integer code;
String msg;
MallExceptionEnum(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
然后还要在里面创建一个异常类:
package com.haiexijun.mall.exception;
public class MallException extends Exception{
private final Integer code;
private final String message;
public MallException(Integer code, String message) {
this.code = code;
this.message = message;
}
public MallException(MallExceptionEnum exceptionEnum){
this(exceptionEnum.code, exceptionEnum.msg);
}
public Integer getCode() {
return code;
}
@Override
public String getMessage() {
return message;
}
}
关于异常的响应,我们系统接受到请求后,会对进行一系列操作,其中就可能会有异常产生,并返回,我们要对异常进行处理一下在返回给用户。这就是统一处理异常的类GlobalExceptionHandler。
package com.haiexijun.mall.exception;
import com.haiexijun.mall.common.ApiRestResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
@ControllerAdvice
public class GlobalExceptionHandler {
private final Logger log= LoggerFactory.getLogger(GlobalExceptionHandler.class);
@ExceptionHandler(Exception.class)
@ResponseBody
public Object handleException(Exception e){
log.error("Default Exception : " +e);
return ApiRestResponse.error(MallExceptionEnum.SYSTEM_ERROR);
}
@ExceptionHandler(MallException.class)
@ResponseBody
public Object MallException(MallException e){
log.error("MallException : " +e);
return ApiRestResponse.error(e.getCode(),e.getMessage());
}
}
3.注册接口的开发
我们注册就要进行插入操作,在UserService类中添加一个注册的方法:
void register(String userName,String password) throws MallException;
并在他的实现类UserServiceImpl中对其进行实现:
@Override
public void register(String userName, String password) throws MallException {
User result=userMapper.selectByName(userName);
if (result !=null){
throw new MallException(MallExceptionEnum.NAME_EXISTED);
}
User user=new User();
user.setUsername(userName);
user.setPassword(password);
int count= userMapper.insertSelective(user);
if (count==0){
throw new MallException(MallExceptionEnum.INSERT_FAILED);
}
}
我们注册涉及到要对比用户名是否有重复,所以,我们要在UserMapper类中添加一个新的查询方法,并在对应的xml文件进行配置:
User selectByName(String userName);
<select id="selectByName" parameterType="java.lang.String" resultMap="BaseResultMap">
select
<include refid="Base_Column_List"/>
from mall_user
where username=#{userName,jdbcType=VARCHAR}
</select>
最后就可以在UserController中编写注册的接口了:
package com.haiexijun.mall.controller;
import com.haiexijun.mall.common.ApiRestResponse;
import com.haiexijun.mall.exception.MallException;
import com.haiexijun.mall.exception.MallExceptionEnum;
import com.haiexijun.mall.model.pojo.User;
import com.haiexijun.mall.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.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class UserController {
@Autowired
UserService userService;
@GetMapping("/test")
@ResponseBody
public User personalPage(){
return userService.getUser();
}
@PostMapping("/register")
@ResponseBody
public ApiRestResponse register(@RequestParam("userName") String userName, @RequestParam("password") String password) throws MallException {
if(StringUtils.isEmpty(userName)){
return ApiRestResponse.error(MallExceptionEnum.NEED_USER_NAME) ;
}
if (StringUtils.isEmpty(password)){
return ApiRestResponse.error(MallExceptionEnum.NEED_USER_PASSWORD);
}
if (password.length()<8){
return ApiRestResponse.error(MallExceptionEnum.PASSWORD_TOO_SHORT);
}
userService.register(userName,password);
return ApiRestResponse.success();
}
}
4.对密码进行MD5保护
我们新建一个包util,是专门用来存放工具的包。里面创建一个类MD5Utils
package com.haiexijun.mall.util;
import com.haiexijun.mall.common.Constant;
import org.apache.tomcat.util.codec.binary.Base64;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class MD5Utils {
public static String GetMD5String(String strValue) throws NoSuchAlgorithmException {
MessageDigest md5=MessageDigest.getInstance("MD5");
return Base64.encodeBase64String(md5.digest((strValue+ Constant.SALT).getBytes()));
}
public static void main(String[] args) {
try {
System.out.println(GetMD5String("123456"));
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
}
}
我们直接这样加密其实很容易就能破解出来,所以要加一个盐值,上面我们也用到了盐值,它存放在了common包下面的Constant常量类中:
package com.haiexijun.mall.common;
public class Constant {
public static final String SALT="fb?rh=jh[v!ur';/~2``>";
}
然后在UserServiceImpl类中,对密码加密进行调用:
try {
user.setPassword(MD5Utils.GetMD5String(password));
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
到这里,就完成了注册接口的开发。
5.登录、登出、更新接口的开发
登录需要对登录的状态进行保存的,保存登录状态就要用到session。服务端把用户信息保存到session里面,以后用户还想来我们系统进行访问的话,我们就可以通过session把用户识别出来。登出的话就是把session的MALL_USER给移除掉就行。下面对整个用户模块的代码一次性列出好了。
UserController:
package com.haiexijun.mall.controller;
import com.haiexijun.mall.common.ApiRestResponse;
import com.haiexijun.mall.common.Constant;
import com.haiexijun.mall.exception.MallException;
import com.haiexijun.mall.exception.MallExceptionEnum;
import com.haiexijun.mall.model.pojo.User;
import com.haiexijun.mall.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.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpSession;
@Controller
public class UserController {
@Autowired
UserService userService;
@GetMapping("/test")
@ResponseBody
public User personalPage(){
return userService.getUser();
}
@PostMapping("/register")
@ResponseBody
public ApiRestResponse register(@RequestParam("userName") String userName, @RequestParam("password") String password) throws MallException {
if(StringUtils.isEmpty(userName)){
return ApiRestResponse.error(MallExceptionEnum.NEED_USER_NAME) ;
}
if (StringUtils.isEmpty(password)){
return ApiRestResponse.error(MallExceptionEnum.NEED_USER_PASSWORD);
}
if (StringUtils.isEmpty(userName)&&StringUtils.isEmpty(password)){
return ApiRestResponse.error(MallExceptionEnum.USER_NAME_AND_PASSWORD_IS_NULL);
}
if (password.length()<8){
return ApiRestResponse.error(MallExceptionEnum.PASSWORD_TOO_SHORT);
}
userService.register(userName,password);
return ApiRestResponse.success();
}
@PostMapping("/login")
@ResponseBody
public ApiRestResponse login(@RequestParam("userName") String userName, @RequestParam("password") String password, HttpSession session) throws MallException {
if(StringUtils.isEmpty(userName)){
return ApiRestResponse.error(MallExceptionEnum.NEED_USER_NAME) ;
}
if (StringUtils.isEmpty(password)){
return ApiRestResponse.error(MallExceptionEnum.NEED_USER_PASSWORD);
}
if (StringUtils.isEmpty(userName)&&StringUtils.isEmpty(password)){
return ApiRestResponse.error(MallExceptionEnum.USER_NAME_AND_PASSWORD_IS_NULL);
}
User user= userService.login(userName,password);
user.setPassword("************");
session.setAttribute(Constant.MALL_USER,user);
return ApiRestResponse.success(user);
}
@PostMapping("/user/update")
@ResponseBody
public ApiRestResponse updateUserInfo(HttpSession session,@RequestParam("signature") String signature) throws MallException {
User currentUser= (User) session.getAttribute(Constant.MALL_USER);
if(currentUser==null){
return ApiRestResponse.error(MallExceptionEnum.NEED_LOGIN);
}
User user=new User();
user.setId(currentUser.getId());
user.setPersonalizedSignature(signature);
userService.updateInformation(user);
return ApiRestResponse.success();
}
@PostMapping("user/logout")
@ResponseBody
public ApiRestResponse logout(HttpSession session){
session.removeAttribute(Constant.MALL_USER);
return ApiRestResponse.success();
}
@PostMapping("/adminLogin")
@ResponseBody
public ApiRestResponse adminLogin(@RequestParam("userName") String userName, @RequestParam("password") String password, HttpSession session) throws MallException {
if(StringUtils.isEmpty(userName)){
return ApiRestResponse.error(MallExceptionEnum.NEED_USER_NAME) ;
}
if (StringUtils.isEmpty(password)){
return ApiRestResponse.error(MallExceptionEnum.NEED_USER_PASSWORD);
}
if (StringUtils.isEmpty(userName)&&StringUtils.isEmpty(password)){
return ApiRestResponse.error(MallExceptionEnum.USER_NAME_AND_PASSWORD_IS_NULL);
}
User user= userService.login(userName,password);
if (userService.checkAdminRole(user)) {
user.setPassword("************");
session.setAttribute(Constant.MALL_USER,user);
return ApiRestResponse.success(user);
}else {
return ApiRestResponse.error(MallExceptionEnum.NEED_ADMIN);
}
}
}
UserService:
package com.haiexijun.mall.service;
import com.haiexijun.mall.exception.MallException;
import com.haiexijun.mall.model.pojo.User;
public interface UserService {
public User getUser();
void register(String userName,String password) throws MallException;
User login(String username, String password) throws MallException;
void updateInformation(User user) throws MallException;
boolean checkAdminRole(User user);
}
UserServiceImpl:
package com.haiexijun.mall.service.impl;
import com.haiexijun.mall.common.ApiRestResponse;
import com.haiexijun.mall.exception.MallException;
import com.haiexijun.mall.exception.MallExceptionEnum;
import com.haiexijun.mall.model.dao.UserMapper;
import com.haiexijun.mall.model.pojo.User;
import com.haiexijun.mall.service.UserService;
import com.haiexijun.mall.util.MD5Utils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.security.NoSuchAlgorithmException;
@Service
public class UserServiceImpl implements UserService {
@Autowired
UserMapper userMapper;
@Override
public User getUser() {
return userMapper.selectByPrimaryKey(4);
}
@Override
public void register(String userName, String password) throws MallException {
User result=userMapper.selectByName(userName);
if (result !=null){
throw new MallException(MallExceptionEnum.NAME_EXISTED);
}
User user=new User();
user.setUsername(userName);
try {
user.setPassword(MD5Utils.GetMD5String(password));
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
int count= userMapper.insertSelective(user);
if (count==0){
throw new MallException(MallExceptionEnum.INSERT_FAILED);
}
}
@Override
public User login(String username, String password) throws MallException {
String md5Password=null;
try {
md5Password=MD5Utils.GetMD5String(password);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
User user=userMapper.selectLogin(username,password);
if (user==null){
throw new MallException(MallExceptionEnum.WRONG_PASSWORD);
}
return user;
}
@Override
public void updateInformation(User user) throws MallException {
int updateCount=userMapper.updateByPrimaryKeySelective(user);
if (updateCount>1){
throw new MallException(MallExceptionEnum.UPDATE_FAILED);
}
}
@Override
public boolean checkAdminRole(User user){
return user.getRole().equals(2);
}
}
UserMapper:
package com.haiexijun.mall.model.dao;
import com.haiexijun.mall.model.pojo.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
@Repository
public interface UserMapper {
int deleteByPrimaryKey(Integer id);
int insert(User record);
int insertSelective(User record);
User selectByPrimaryKey(Integer id);
int updateByPrimaryKeySelective(User record);
int updateByPrimaryKey(User record);
User selectByName(String userName);
User selectLogin(@Param("userName") String userName,@Param("password") String password);
}
UserMapper.xml:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.haiexijun.mall.model.dao.UserMapper">
<resultMap id="BaseResultMap" type="com.haiexijun.mall.model.pojo.User">
<id column="id" jdbcType="INTEGER" property="id" />
<result column="username" jdbcType="VARCHAR" property="username" />
<result column="password" jdbcType="VARCHAR" property="password" />
<result column="personalized_signature" jdbcType="VARCHAR" property="personalizedSignature" />
<result column="role" jdbcType="INTEGER" property="role" />
<result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
<result column="update_time" jdbcType="TIMESTAMP" property="updateTime" />
</resultMap>
<sql id="Base_Column_List">
id, username, `password`, personalized_signature, `role`, create_time, update_time
</sql>
<select id="selectByPrimaryKey" parameterType="java.lang.Integer" resultMap="BaseResultMap">
select
<include refid="Base_Column_List" />
from mall_user
where id = #{id,jdbcType=INTEGER}
</select>
<delete id="deleteByPrimaryKey" parameterType="java.lang.Integer">
delete from mall_user
where id = #{id,jdbcType=INTEGER}
</delete>
<insert id="insert" parameterType="com.haiexijun.mall.model.pojo.User">
insert into mall_user (id, username, `password`,
personalized_signature, `role`, create_time,
update_time)
values (#{id,jdbcType=INTEGER}, #{username,jdbcType=VARCHAR}, #{password,jdbcType=VARCHAR},
#{personalizedSignature,jdbcType=VARCHAR}, #{role,jdbcType=INTEGER}, #{createTime,jdbcType=TIMESTAMP},
#{updateTime,jdbcType=TIMESTAMP})
</insert>
<insert id="insertSelective" parameterType="com.haiexijun.mall.model.pojo.User">
insert into mall_user
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="id != null">
id,
</if>
<if test="username != null">
username,
</if>
<if test="password != null">
`password`,
</if>
<if test="personalizedSignature != null">
personalized_signature,
</if>
<if test="role != null">
`role`,
</if>
<if test="createTime != null">
create_time,
</if>
<if test="updateTime != null">
update_time,
</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="id != null">
#{id,jdbcType=INTEGER},
</if>
<if test="username != null">
#{username,jdbcType=VARCHAR},
</if>
<if test="password != null">
#{password,jdbcType=VARCHAR},
</if>
<if test="personalizedSignature != null">
#{personalizedSignature,jdbcType=VARCHAR},
</if>
<if test="role != null">
#{role,jdbcType=INTEGER},
</if>
<if test="createTime != null">
#{createTime,jdbcType=TIMESTAMP},
</if>
<if test="updateTime != null">
#{updateTime,jdbcType=TIMESTAMP},
</if>
</trim>
</insert>
<update id="updateByPrimaryKeySelective" parameterType="com.haiexijun.mall.model.pojo.User">
update mall_user
<set>
<if test="username != null">
username = #{username,jdbcType=VARCHAR},
</if>
<if test="password != null">
`password` = #{password,jdbcType=VARCHAR},
</if>
<if test="personalizedSignature != null">
personalized_signature = #{personalizedSignature,jdbcType=VARCHAR},
</if>
<if test="role != null">
`role` = #{role,jdbcType=INTEGER},
</if>
<if test="createTime != null">
create_time = #{createTime,jdbcType=TIMESTAMP},
</if>
<if test="updateTime != null">
update_time = #{updateTime,jdbcType=TIMESTAMP},
</if>
</set>
where id = #{id,jdbcType=INTEGER}
</update>
<update id="updateByPrimaryKey" parameterType="com.haiexijun.mall.model.pojo.User">
update mall_user
set username = #{username,jdbcType=VARCHAR},
`password` = #{password,jdbcType=VARCHAR},
personalized_signature = #{personalizedSignature,jdbcType=VARCHAR},
`role` = #{role,jdbcType=INTEGER},
create_time = #{createTime,jdbcType=TIMESTAMP},
update_time = #{updateTime,jdbcType=TIMESTAMP}
where id = #{id,jdbcType=INTEGER}
</update>
<select id="selectByName" parameterType="java.lang.String" resultMap="BaseResultMap">
select
<include refid="Base_Column_List"/>
from mall_user
where username=#{userName,jdbcType=VARCHAR}
</select>
<select id="selectLogin" parameterType="map" resultMap="BaseResultMap">
select
<include refid="Base_Column_List"/>
from mall_user
where username=#{userName,jdbcType=VARCHAR}
and password=#{password,jdbcType=VARCHAR}
</select>
</mapper>
常量类Constant:
package com.haiexijun.mall.common;
public class Constant {
public static final String SALT="fb?rh=jh[v!ur';/~2``>";
public static final String MALL_USER="mall_user";
}
异常枚举类:
package com.haiexijun.mall.exception;
public enum MallExceptionEnum {
NEED_USER_NAME(10001,"用户名不能为空"),
PASSWORD_TOO_SHORT(10003,"密码长度不能小于八位"),
NEED_USER_PASSWORD(10002,"用户密码不能为空"),
NAME_EXISTED(10004,"用户名已存在,注册失败"),
INSERT_FAILED(10005,"插入失败,请重试"),
WRONG_PASSWORD(10007,"密码错误"),
NEED_LOGIN(10008,"用户未登录"),
USER_NAME_AND_PASSWORD_IS_NULL(10006,"用户名和密码不能为空"),
UPDATE_FAILED(10009,"更新失败"),
NEED_ADMIN(10010,"无管理员权限"),
SYSTEM_ERROR(20000,"系统异常");
Integer code;
String msg;
MallExceptionEnum(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
到这里,我们用户模块就基本开发好了。
四.商品分类管理模块的开发
1.分类模块地主要功能
第一个,我们先要实现分类数据的设置,这个设置包括通常增删改。除了增删改之外,分类模块的一大重点就在于查找。查找的主要难点就在于他的分类的父一级目录查找和递归查找。比如说车厘子的父目录为进口水果,而进口水果的父目录为新鲜水果。而对于列表而言,他的顺序相反,他是先从形象水果开始递归。先查询到进口水果,再往下一层到车厘子。并且前后台展现是不一样的,后台商品平铺,前台要有层级分类。
2.后台新增分类功能的实现
我们先来实现后台新增分类。 要创建的代码和类如下,没有什么新知识点的类我就不进行解释了。
先要创建一个request包,用来存放请求参数的封装的类。里面新增一个AddCategoryReq类:
package com.haiexijun.mall.model.request;
public class AddCategoryReq {
private String name;
private Integer type;
private Integer parentId;
private Integer orderNum;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getType() {
return type;
}
public void setType(Integer type) {
this.type = type;
}
public Integer getParentId() {
return parentId;
}
public void setParentId(Integer parentId) {
this.parentId = parentId;
}
public Integer getOrderNum() {
return orderNum;
}
public void setOrderNum(Integer orderNum) {
this.orderNum = orderNum;
}
}
CategoryMapper接口中新增一个selectByName方法,用来后面CategoryServiceImpl校验要添加的目录是否存在。
package com.haiexijun.mall.model.dao;
import com.haiexijun.mall.model.pojo.Category;
import org.springframework.stereotype.Repository;
@Repository
public interface CategoryMapper {
int deleteByPrimaryKey(Integer id);
int insert(Category record);
int insertSelective(Category record);
Category selectByPrimaryKey(Integer id);
int updateByPrimaryKeySelective(Category record);
int updateByPrimaryKey(Category record);
Category selectByName(String name);
}
这个方法的xml映射为:
<select id="selectByName" parameterType="java.lang.String" resultMap="BaseResultMap">
select
<include refid="Base_Column_List"/>
from mall_category
where name=#{name,jdbcType=VARCHAR}
</select>
然后下面是CategoryService和CategoryServiceImpl的相关代码:
package com.haiexijun.mall.service;
import com.haiexijun.mall.exception.MallException;
import com.haiexijun.mall.model.request.AddCategoryReq;
public interface CateGoryService {
void add(AddCategoryReq addCateGoryReq) throws MallException;
}
package com.haiexijun.mall.service.impl;
import com.haiexijun.mall.exception.MallException;
import com.haiexijun.mall.exception.MallExceptionEnum;
import com.haiexijun.mall.model.dao.CategoryMapper;
import com.haiexijun.mall.model.pojo.Category;
import com.haiexijun.mall.model.request.AddCategoryReq;
import com.haiexijun.mall.service.CateGoryService;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class CateGoryServiceImpl implements CateGoryService {
@Autowired
CategoryMapper categoryMapper;
@Override
public void add(AddCategoryReq addCateGoryReq) throws MallException {
Category category=new Category();
BeanUtils.copyProperties(addCateGoryReq,category);
Category categoryOld= categoryMapper.selectByName(addCateGoryReq.getName());
if (categoryOld!=null){
throw new MallException(MallExceptionEnum.NAME_EXISTED);
}
int count= categoryMapper.insertSelective(category);
if (count==0){
throw new MallException(MallExceptionEnum.CREATE_FAILED);
}
}
}
下面就是CategoryController的相关代码:
package com.haiexijun.mall.controller;
import com.haiexijun.mall.common.ApiRestResponse;
import com.haiexijun.mall.common.Constant;
import com.haiexijun.mall.exception.MallException;
import com.haiexijun.mall.exception.MallExceptionEnum;
import com.haiexijun.mall.model.pojo.User;
import com.haiexijun.mall.model.request.AddCategoryReq;
import com.haiexijun.mall.service.CateGoryService;
import com.haiexijun.mall.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpSession;
@Controller
public class CategoryController {
@Autowired
UserService userService;
@Autowired
CateGoryService cateGoryService;
@PostMapping("/admin/category/add")
@ResponseBody
public ApiRestResponse addCategory(HttpSession session,@RequestBody AddCategoryReq addCategoryReq){
if (addCategoryReq.getName()==null||addCategoryReq.getOrderNum()==null||addCategoryReq.getType()==null||addCategoryReq.getParentId()==null){
return ApiRestResponse.error(MallExceptionEnum.PARAM_NOT_NULL);
}
User currentUser=(User) session.getAttribute(Constant.MALL_USER);
boolean adminRole=userService.checkAdminRole(currentUser);
if (adminRole){
try {
cateGoryService.add(addCategoryReq);
} catch (MallException e) {
e.printStackTrace();
}
return ApiRestResponse.success();
}else {
return ApiRestResponse.error(MallExceptionEnum.NEED_ADMIN);
}
}
}
3. 使用@Valid注解进行参数校验
之前我们controller里面是一个一个参数进行校验是否传入空值,这一点都不优雅。下面学习几个常用的用于参数校验的注解。 当然,@Valid校验肯定不止这么点注解,我们用到去查就行了。 首先第一个就是@Valid注解,一旦我们给某一个传入的参数加上这一个注解之后,那就意味着他需要校验。当加上这个注解之后,我们再去给他的字段加下面的3个注解。
下面我们来实际操作一下,这些注解如何使用,我们把之前校验目录传参是否为空进行改造一下: 先要导入一个依赖:
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.1.0.Final</version>
</dependency>
一定要导入这个依赖不然会报错。
之后,就要在controller的请求参数前添加@Valid注解:
public ApiRestResponse addCategory(HttpSession session,@Valid @RequestBody AddCategoryReq addCategoryReq){····省略···}
我们还要在AddCategoryReq这个请求参数类中添加其他一些校验注解:
package com.haiexijun.mall.model.request;
import javax.validation.constraints.Max;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
public class AddCategoryReq {
@Size(min = 2,max = 5,message = "name应该大于2位小于5位")
@NotNull(message = "name不能为null")
private String name;
@NotNull(message = "type不能为null")
@Max(value = 3,message = "type最大值为3")
private Integer type;
@NotNull(message = "parentId不能为null")
private Integer parentId;
@NotNull(message = "order不能为null")
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;
}
}
既然@Valid会帮助我们检查出参数的错误并抛出异常,但是异常结果会直接返回为系统异常。我们先要在GlobalExceptionHandler类中编写他的异常返回:
package com.haiexijun.mall.exception;
import com.haiexijun.mall.common.ApiRestResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.properties.bind.BindResult;
import org.springframework.validation.BindingResult;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.naming.Binding;
import java.util.ArrayList;
import java.util.List;
@ControllerAdvice
public class GlobalExceptionHandler {
private final Logger log= LoggerFactory.getLogger(GlobalExceptionHandler.class);
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseBody
public ApiRestResponse HandleMethodArgumentNotValidException(MethodArgumentNotValidException e){
log.error("MethodArgumentNotValidException : "+e);
return handleBindingResult(e.getBindingResult());
}
private ApiRestResponse handleBindingResult(BindingResult result){
List<String> list=new ArrayList<String>();
if (result.hasErrors()) {
List<ObjectError> allErrors= result.getAllErrors();
for (int i=0;i<allErrors.size();i++){
ObjectError objectError=allErrors.get(i);
String message= objectError.getDefaultMessage();
list.add(message);
}
}
if (list.size()==0){
return ApiRestResponse.error(MallExceptionEnum.REQUEST_PARAM_ERROR);
}else {
return ApiRestResponse.error(MallExceptionEnum.REQUEST_PARAM_ERROR.getCode(),list.toString());
}
}
}
这样就可以了。
4.Swagger自动生成API文档
下面我们简单学习一下如何使用Swagger自动生成API文档,这里只做简单的使用,并没有详细的教学。如果要看更详细的教程,请移步其他Swagger的入门教程。而且现在国内也有其他比Swagger好用的接口框架,不一定要用Swagger。
我先引入Swagger的相关依赖:
<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>
然后,要在MallApplication这个启动类的类名上面添加一个@EnableSwagger2注解和@EnableWebMvc注解。
之后,我们要对其进行简单的配置,我们新建一个包,叫做config,这个包专门用来存储我们的配置文件的。 在里面创建一个SpringFoxConfig类:
package com.haiexijun.mall.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
@Configuration
public class SpringFoxConfig {
@Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.any())
.paths(PathSelectors.any())
.build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("慕慕生鲜")
.description("")
.termsOfServiceUrl("")
.build();
}
}
之后还要写一个配置类:
package com.haiexijun.mall.config;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@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(“接口描述”)注解就行了,如@ApiOperation(“后台新增商品分类目录”)。
之后我们运行项目,然后访问http://localhost:8083/swagger-ui.html就可以查看到我们编写的接口文档了。我们还可以在里面输入参数调试。
5.后台更新分类目录接口的开发
Controller的方法:
@ApiOperation("后台更新分类目录")
@PostMapping("/admin/category/update")
@ResponseBody
public ApiRestResponse updateCategory(@Valid @RequestBody UpdateCategoryReq updateCategoryReq,HttpSession session) throws MallException {
User currentUser=(User) session.getAttribute(Constant.MALL_USER);
if (currentUser==null){
return ApiRestResponse.error(MallExceptionEnum.NEED_LOGIN);
}
boolean adminRole=userService.checkAdminRole(currentUser);
if (adminRole){
Category category=new Category();
BeanUtils.copyProperties(updateCategoryReq,category);
cateGoryService.update(category);
return ApiRestResponse.success();
}else {
return ApiRestResponse.error(MallExceptionEnum.NEED_ADMIN);
}
}
对应的Service方法:
@Override
public void update(Category updateCategory) throws MallException {
if(updateCategory.getName()!=null){
Category categoryOld=categoryMapper.selectByName(updateCategory.getName());
if (categoryOld !=null && !categoryOld.getId().equals(updateCategory.getId())){
throw new MallException(MallExceptionEnum.NAME_EXISTED);
}
}
int count=categoryMapper.updateByPrimaryKeySelective(updateCategory);
if (count==0){
throw new MallException(MallExceptionEnum.UPDATE_FAILED);
}
}
6. 统一校验管理员身份
之前每一个接口都要写相同的代码来验证管理员,如果有非常多的接口要实现的话,那就会有很多的重复代码。这一节优化一下之前的验证管理员的代码。 我们要新建一个过滤器AdminFilter,编写如下的代码
package com.haiexijun.mall.filter;
import com.haiexijun.mall.common.ApiRestResponse;
import com.haiexijun.mall.common.Constant;
import com.haiexijun.mall.exception.MallExceptionEnum;
import com.haiexijun.mall.model.pojo.User;
import com.haiexijun.mall.service.UserService;
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;
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 request= (HttpServletRequest) servletRequest;
HttpSession session=request.getSession();
User currentUser=(User) session.getAttribute(Constant.MALL_USER);
if (currentUser==null){
PrintWriter out =new HttpServletResponseWrapper((HttpServletResponse) servletResponse).getWriter();
out.write("{\n" +
" \"status\": 10008,\n" +
" \"msg\": \"用户未登录\",\n" +
" \"data\": null\n" +
"}");
out.flush();
out.close();
return;
}
boolean adminRole=userService.checkAdminRole(currentUser);
if (adminRole){
filterChain.doFilter(servletRequest,servletResponse);
}else {
PrintWriter out =new HttpServletResponseWrapper((HttpServletResponse) servletResponse).getWriter();
out.write("{\n" +
" \"status\": 10010,\n" +
" \"msg\": \"无管理员权限\",\n" +
" \"data\": null\n" +
"}");
out.flush();
out.close();
return;
}
}
@Override
public void destroy() {
Filter.super.destroy();
}
}
写好filter后,下一步就是配置使用这个filter,我们创建一个AdminFilterConfig.
package com.haiexijun.mall.config;
import com.haiexijun.mall.filter.AdminFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AdminFilterConfig {
@Bean
public AdminFilter adminFilter(){
return new AdminFilter();
}
@Bean(name = "adminFilterConf")
public FilterRegistrationBean adminFilterConfig(){
FilterRegistrationBean filterRegistrationBean=new FilterRegistrationBean();
filterRegistrationBean.setFilter(adminFilter());
filterRegistrationBean.addUrlPatterns("/admin/category/*");
filterRegistrationBean.addUrlPatterns("/admin/product/*");
filterRegistrationBean.addUrlPatterns("/admin/order/*");
filterRegistrationBean.setName("adminFilterConfig");
return filterRegistrationBean;
}
}
7.删除分类目录接口
server新增加一个delete方法:
@Override
public void delete(Integer id) throws MallException {
Category categoryOld=categoryMapper.selectByPrimaryKey(id);
if (categoryOld==null){
throw new MallException(MallExceptionEnum.DELETE_FAILED);
}
int count=categoryMapper.deleteByPrimaryKey(id);
if (count==0){
throw new MallException(MallExceptionEnum.DELETE_FAILED);
}
}
然后编写controller:
@ApiOperation("后台删除目录")
@PostMapping("/admin/category/delete")
@ResponseBody
public ApiRestResponse deleteCategory(@RequestParam("id") Integer id) throws MallException {
cateGoryService.delete(id);
return ApiRestResponse.success();
}
8.查询用户分类列表接口的开发
我们先来开发一下后台的查询用户分类列表接口:
分页先要引入pagehelper分页插件:
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.3.0</version>
</dependency>
然后再application.properties文件中添加一行配置:
spring.main.allow-circular-references=true
之后就是编写相关代码就行了: CategoryMapper新增一个selectList方法,然后给这个方法编写对应的xml映射:
<select id="selectList" resultMap="BaseResultMap">
select
<include refid="Base_Column_List"/>
from mall_category
</select>
之后就是要新建一个vo包,里面新建一个CategoryVO类,类里面只不过是比Category类多了一个List类型的属性:
package com.haiexijun.mall.vo;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
public class CategoryVO {
private Integer id;
private String name;
private Integer type;
private Integer parentId;
private Integer orderNum;
private Date createTime;
private Date updateTime;
private List<CategoryVO> childCategory = new ArrayList<>();
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;
}
}
之后就是Service里面的方法:
@Override
public PageInfo listForAdmin(Integer pageNum,Integer pageSize){
PageHelper.startPage(pageNum,pageSize,"type,order_num");
List<Category> categoryList=categoryMapper.selectList();
PageInfo pageInfo=new PageInfo(categoryList);
return pageInfo;
}
controller的方法:
@ApiOperation("查询后台目录列表")
@PostMapping("/admin/category/list")
@ResponseBody
public ApiRestResponse listCategoryForAdmin(@RequestParam Integer pageNum,@RequestParam Integer pageSize){
PageInfo pageInfo =cateGoryService.listForAdmin(pageNum,pageSize);
return ApiRestResponse.success(pageInfo);
}
这就编写好后台的目录分页查询的接口了。
下面要来编写用于前台展示的目录分页的接口: 要用到递归查询,下面是代码: CategoryMapper新增一个selectCategoriesByParentId方法,并编写对应的xml映射:
<select id="selectCategoriesByParentId" parameterType="int" resultMap="BaseResultMap">
select
<include refid="Base_Column_List"/>
from mall_category
where parent_id=#{parentId}
</select>
然后就编写service类的两个方法:
@Override
public List<CategoryVO> listCategoryForCustomer(){
ArrayList<CategoryVO> categoryVOList = new ArrayList<CategoryVO>();
recursivelyFindCategories(categoryVOList,0);
return categoryVOList;
}
private void recursivelyFindCategories(List<CategoryVO> categoryVOList,Integer parentId){
List<Category> categoryList = categoryMapper.selectCategoriesByParentId(parentId);
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);
categoryVOList.add(categoryVO);
recursivelyFindCategories(categoryVO.getChildCategory(),categoryVO.getId());
}
}
}
最后controller的方法:
@ApiOperation("查询前台目录列表")
@PostMapping("/category/list")
@ResponseBody
public ApiRestResponse listCategoryForCustomer(){
List<CategoryVO> categoryVOS =cateGoryService.listCategoryForCustomer();
return ApiRestResponse.success(categoryVOS);
}
9.利用Redis缓存加速
考虑到目录变化其实不是很频繁,所以使用Redis缓存来提高我们整体的效率。这一节主要学习用Spring-boot来集成redis。 先引入两个我们要用到的依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
在引入好依赖之后,我们还要在application.properties文件中对redis进行配置,添加如下的配置:
spring.redis.host=192.168.172.129
spring.redis.port=6379
spring.redis.password=zc20020106
之后要在springboot的启动类的类名上添加如下的注解:
@EnableCaching
之后还要在我们希望用缓存的那个serviceImpl类的方法上面加上一个注解:
@Override
@Cacheable(value = "listCategoryForCustomer")
public List<CategoryVO> listCategoryForCustomer(){
ArrayList<CategoryVO> categoryVOList = new ArrayList<CategoryVO>();
recursivelyFindCategories(categoryVOList,0);
return categoryVOList;
}
之后还要建一个redis的配置类CachingConfig:
package com.haiexijun.mall.config;
import java.time.Duration;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory;
@Configuration
@EnableCaching
public class CachingConfig {
@Bean
public RedisCacheManager redisCacheManager(RedisConnectionFactory connectionFactory) {
RedisCacheWriter redisCacheWriter = RedisCacheWriter
.lockingRedisCacheWriter(connectionFactory);
RedisCacheConfiguration cacheConfiguration = RedisCacheConfiguration.defaultCacheConfig();
cacheConfiguration = cacheConfiguration.entryTtl(Duration.ofSeconds(30));
RedisCacheManager redisCacheManager = new RedisCacheManager(redisCacheWriter,
cacheConfiguration);
return redisCacheManager;
}
}
还要给之前的CategoryVO类实现Serializable接口:
public class CategoryVO implements Serializable {················}
五. 商品模块的开发
1. 后台新增商品的接口以及图片上传接口的开发
先来了解一下图片上传,图片名我们采用的是UUID(Universally Unique Identifier),中文名为通用唯一识别码。使用UUID可以防止图片重名的问题,以及可以防止别人爬图。他的生成规则:日期和时间、MAC地址、HashCode 、随机数等。
相关代码如下: controller:
package com.haiexijun.mall.controller;
import com.haiexijun.mall.common.ApiRestResponse;
import com.haiexijun.mall.common.Constant;
import com.haiexijun.mall.exception.MallException;
import com.haiexijun.mall.exception.MallExceptionEnum;
import com.haiexijun.mall.model.pojo.Product;
import com.haiexijun.mall.model.request.addProductReq;
import com.haiexijun.mall.service.ProductService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import javax.validation.Valid;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.UUID;
@Controller
@Api(tags = "后台商品管理相关接口",description = "有后台添加商品的接口、图片上传的接口、更新和删除商品的接口、批量上下架商品的接口、后台商品列表和商品详情的接口")
public class ProductAdminController {
@Autowired
ProductService productService;
@PostMapping("/admin/product/add")
@ApiOperation("后台添加商品")
@ResponseBody
public ApiRestResponse addProduct(@Valid @RequestBody addProductReq addProductReq) throws MallException {
productService.add(addProductReq);
return ApiRestResponse.success();
}
@PostMapping("/admin/product/file")
@ResponseBody
@ApiOperation("商品图片上传")
public ApiRestResponse upload(HttpServletRequest httpServletRequest,@RequestParam("file") MultipartFile file) throws MallException {
String fileName= file.getOriginalFilename();
String suffixName= fileName.substring(fileName.lastIndexOf("."));
UUID uuid=UUID.randomUUID();
String newFileName=uuid.toString()+suffixName;
File fileDirectory= new File(Constant.FILE_UPLOAD_DIR);
File destFile= new File(Constant.FILE_UPLOAD_DIR+newFileName);
if (!fileDirectory.exists()){
if (!fileDirectory.mkdir()){
throw new MallException(MallExceptionEnum.MKDIR_FAILED);
}
}
try {
file.transferTo(destFile);
} catch (IOException e) {
e.printStackTrace();
}
try {
return ApiRestResponse.success(getHost(new URI(httpServletRequest.getRequestURL()+""))+"/images/"+newFileName);
} catch (URISyntaxException e) {
return ApiRestResponse.error(MallExceptionEnum.UPLOAD_FAILED);
}
}
private URI getHost(URI uri){
URI effectiveURI;
try {
effectiveURI=new URI(uri.getScheme(),uri.getUserInfo(),uri.getHost(),uri.getPort(),null,null,null);
} catch (URISyntaxException e) {
effectiveURI=null;
}
return effectiveURI;
}
}
service层:
package com.haiexijun.mall.service.impl;
import com.haiexijun.mall.exception.MallException;
import com.haiexijun.mall.exception.MallExceptionEnum;
import com.haiexijun.mall.model.dao.ProductMapper;
import com.haiexijun.mall.model.pojo.Product;
import com.haiexijun.mall.model.request.addProductReq;
import com.haiexijun.mall.service.ProductService;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class ProductServiceImpl implements ProductService {
@Autowired
ProductMapper productMapper;
@Override
public void add(addProductReq addProductReq) throws MallException {
Product product=new Product();
BeanUtils.copyProperties(addProductReq,product);
Product productOld=productMapper.selectByName(addProductReq.getName());
if (productOld!=null){
throw new MallException(MallExceptionEnum.NAME_EXISTED);
}
int count= productMapper.insertSelective(product);
if (count==0){
throw new MallException(MallExceptionEnum.CREATE_FAILED);
}
}
}
其他层这里不多写了。
图片上传的地址我们要在application.properties配置文件中进行配置:
# 上传图片的路径,根据部署情况,可自行修改
file.upload_dir=D:\\
然后还要在MallWebMvcConfig这个配置类中对图片的资源映射配置,addResourceHandlers方法里面添加代码:
registry.addResourceHandler("/images/**").addResourceLocations(
"file:"+ Constant.FILE_UPLOAD_DIR);
2.更新和删除商品的接口
controller层:
@PostMapping("/admin/product/update")
@ResponseBody
@ApiOperation("后台更新商品信息")
public ApiRestResponse updateProduct(@Valid @RequestBody UpdateProductReq updateProductReq) throws MallException {
Product product = new Product();
BeanUtils.copyProperties(updateProductReq,product);
productService.update(product);
return ApiRestResponse.success();
}
@PostMapping("/admin/product/delete")
@ResponseBody
@ApiOperation("后台删除商品信息")
public ApiRestResponse deleteProduct(@RequestParam Integer id) throws MallException {
productService.delete(id);
return ApiRestResponse.success();
}
service层:
@Override
public void update(Product updateProduct) throws MallException {
Product productOld=productMapper.selectByName(updateProduct.getName());
if (productOld!=null&&!productOld.getId().equals(updateProduct.getId())){
throw new MallException(MallExceptionEnum.NAME_EXISTED) ;
}
int count=productMapper.updateByPrimaryKeySelective(updateProduct);
if (count==0){
throw new MallException(MallExceptionEnum.UPDATE_FAILED);
}
}
@Override
public void delete(Integer id) throws MallException {
Product productOld=productMapper.selectByPrimaryKey(id);
if (productOld==null){
throw new MallException(MallExceptionEnum.DELETE_FAILED) ;
}
int count=productMapper.deleteByPrimaryKey(id);
if (count==0){
throw new MallException(MallExceptionEnum.DELETE_FAILED);
}
}
3. 批量上下架商品的接口
对于Mybatis而言,它的一个能力就是遍历List。比如说我们传进去一个列表,那想把这里面的所有符合这个列表Id的都进行上下架状态的更新。我们要实现的话,就要在where语句中进行拼接。 controller层:
@PostMapping("/admin/product/batchUpdateSellStatus")
@ResponseBody
@ApiOperation("后台批量上下架")
public ApiRestResponse batchUpdateSellStatus(@RequestParam Integer[] ids,@RequestParam Integer sellStatus){
productService.batchUpdateSellStatus(ids, sellStatus);
return ApiRestResponse.success();
}
service层:
@Override
public void batchUpdateSellStatus(Integer[] ids,Integer sellStatus){
productMapper.batchUpdateSellStatus(ids,sellStatus);
}
mapper.xml:
<update id="batchUpdateSellStatus">
update mall_product
set status=#{sellStatus}
where id in
<foreach collection="ids" close=")" item="id" open="(" separator=",">
#{id}
</foreach>
</update>
六.购物车模块
1.部分业务流程
2. 核心代码
controller层:
package com.haiexijun.mall.controller;
import com.haiexijun.mall.common.ApiRestResponse;
import com.haiexijun.mall.exception.MallException;
import com.haiexijun.mall.filter.UserFilter;
import com.haiexijun.mall.service.CartService;
import com.haiexijun.mall.vo.CartVO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@Api(tags = "购物车相关的接口")
@RequestMapping("/cart")
public class CartController {
@Autowired
CartService cartService;
@PostMapping("/add")
@ApiOperation("商品加入购物车")
public ApiRestResponse add(@RequestParam Integer productId,@RequestParam Integer count) throws MallException {
Integer userId= UserFilter.currentUser.getId();
List<CartVO> cartVOList= cartService.add(userId,productId,count);
return ApiRestResponse.success(cartVOList);
}
@ApiOperation("获取购物车列表")
@GetMapping("/list")
public ApiRestResponse list(){
Integer userId= UserFilter.currentUser.getId();
List<CartVO> carList =cartService.list(userId);
return ApiRestResponse.success(carList);
}
@ApiOperation("更新购物车")
@PostMapping("/update")
public ApiRestResponse update(@RequestParam Integer productId,@RequestParam Integer count) throws MallException {
Integer userId= UserFilter.currentUser.getId();
List<CartVO> cartVOList=cartService.update(userId,productId,count);
return ApiRestResponse.success(cartVOList);
}
@ApiOperation("删除购物车")
@PostMapping("/delete")
public ApiRestResponse update(@RequestParam Integer productId) throws MallException {
Integer userId= UserFilter.currentUser.getId();
List<CartVO> cartVOList=cartService.delete(userId,productId);
return ApiRestResponse.success(cartVOList);
}
@ApiOperation("单个选中/不选中购物车的商品")
@PostMapping("/select")
public ApiRestResponse select(@RequestParam Integer productId,@RequestParam Integer selected) throws MallException {
Integer userId= UserFilter.currentUser.getId();
List<CartVO> cartVOList=cartService.selectOrNot(userId,productId,selected);
return ApiRestResponse.success(cartVOList);
}
@ApiOperation("全部选中/不选中购物车的商品")
@PostMapping("/selectAll")
public ApiRestResponse selectAll(@RequestParam Integer selected) throws MallException {
Integer userId= UserFilter.currentUser.getId();
List<CartVO> cartVOList=cartService.selectAll(userId,selected);
return ApiRestResponse.success(cartVOList);
}
}
service层:
package com.haiexijun.mall.service.impl;
import com.haiexijun.mall.common.Constant;
import com.haiexijun.mall.exception.MallException;
import com.haiexijun.mall.exception.MallExceptionEnum;
import com.haiexijun.mall.model.dao.CartMapper;
import com.haiexijun.mall.model.dao.ProductMapper;
import com.haiexijun.mall.model.pojo.Cart;
import com.haiexijun.mall.model.pojo.Product;
import com.haiexijun.mall.service.CartService;
import com.haiexijun.mall.vo.CartVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class CartServiceImpl implements CartService {
@Autowired
ProductMapper productMapper;
@Autowired
CartMapper cartMapper;
@Override
public List<CartVO> add(Integer userId, Integer productId, Integer count) throws MallException {
validProduct(productId,count);
Cart cart=cartMapper.selectCartByUserIdAndProductId(userId,productId);
if (cart==null){
cart=new Cart();
cart.setProductId(productId);
cart.setUserId(userId);
cart.setQuantity(count);
cart.setSelected(Constant.Cart.CHECKED);
cartMapper.insertSelective(cart);
}else {
count=count+cart.getQuantity();
Cart cartNew=new Cart();
cartNew.setQuantity(count);
cartNew.setId(cart.getId());
cartNew.setProductId(cart.getProductId());
cartNew.setUserId(cart.getUserId());
cartNew.setSelected(Constant.Cart.CHECKED);
cartMapper.updateByPrimaryKeySelective(cartNew);
}
return this.list(userId);
}
private void validProduct(Integer productId,Integer count) throws MallException {
Product product=productMapper.selectByPrimaryKey(productId);
if (product==null||product.getStatus().equals(Constant.SaleStatus.NOT_SELL)){
throw new MallException(MallExceptionEnum.NOT_SELL);
}
if (count>product.getStock()){
throw new MallException(MallExceptionEnum.NOT_ENOUGH);
}
}
@Override
public List<CartVO> list(Integer userId){
List<CartVO> cartVOS= cartMapper.selectList(userId);
for (int i = 0; i < cartVOS.size(); i++) {
CartVO cartVO=cartVOS.get(i);
cartVO.setTotalPrice(cartVO.getPrice()*cartVO.getQuantity());
}
return cartVOS;
}
@Override
public List<CartVO> update(Integer userId, Integer productId, Integer count) throws MallException {
validProduct(productId,count);
Cart cart=cartMapper.selectCartByUserIdAndProductId(userId,productId);
if (cart==null){
throw new MallException(MallExceptionEnum.UPDATE_FAILED);
}else {
Cart cartNew=new Cart();
cartNew.setQuantity(count);
cartNew.setId(cart.getId());
cartNew.setProductId(cart.getProductId());
cartNew.setUserId(cart.getUserId());
cartNew.setSelected(Constant.Cart.CHECKED);
cartMapper.updateByPrimaryKeySelective(cartNew);
}
return this.list(userId);
}
@Override
public List<CartVO> delete(Integer userId, Integer productId) throws MallException {
Cart cart=cartMapper.selectCartByUserIdAndProductId(userId,productId);
if (cart==null){
throw new MallException(MallExceptionEnum.DELETE_FAILED);
}else {
cartMapper.deleteByPrimaryKey(cart.getId());
}
return this.list(userId);
}
@Override
public List<CartVO> selectOrNot(Integer userId, Integer productId, Integer selected) throws MallException {
Cart cart=cartMapper.selectCartByUserIdAndProductId(userId,productId);
if (cart==null){
throw new MallException(MallExceptionEnum.UPDATE_FAILED);
}else {
cartMapper.selectOrNot(userId,productId,selected);
}
return this.list(userId);
}
@Override
public List<CartVO> selectAll(Integer userId,Integer selected){
cartMapper.selectOrNot(userId,null,selected);
return this.list(userId);
}
}
dao层:
package com.haiexijun.mall.model.dao;
import com.haiexijun.mall.model.pojo.Cart;
import com.haiexijun.mall.vo.CartVO;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface CartMapper {
int deleteByPrimaryKey(Integer id);
int insert(Cart record);
int insertSelective(Cart record);
Cart selectByPrimaryKey(Integer id);
int updateByPrimaryKeySelective(Cart record);
int updateByPrimaryKey(Cart record);
Cart selectCartByUserIdAndProductId(@Param("userId") Integer userId,@Param("productId") Integer productId);
List<CartVO> selectList(@Param("userId") Integer userId);
Integer selectOrNot(@Param("userId") Integer userId,@Param("productId") Integer productId,@Param("selected") Integer selected);
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.haiexijun.mall.model.dao.CartMapper">
<resultMap id="BaseResultMap" type="com.haiexijun.mall.model.pojo.Cart">
<id column="id" jdbcType="INTEGER" property="id" />
<result column="product_id" jdbcType="INTEGER" property="productId" />
<result column="user_id" jdbcType="INTEGER" property="userId" />
<result column="quantity" jdbcType="INTEGER" property="quantity" />
<result column="selected" jdbcType="INTEGER" property="selected" />
<result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
<result column="update_time" jdbcType="TIMESTAMP" property="updateTime" />
</resultMap>
<sql id="Base_Column_List">
id, product_id, user_id, quantity, selected, create_time, update_time
</sql>
<select id="selectByPrimaryKey" parameterType="java.lang.Integer" resultMap="BaseResultMap">
select
<include refid="Base_Column_List" />
from mall_cart
where id = #{id,jdbcType=INTEGER}
</select>
<delete id="deleteByPrimaryKey" parameterType="java.lang.Integer">
delete from mall_cart
where id = #{id,jdbcType=INTEGER}
</delete>
<insert id="insert" parameterType="com.haiexijun.mall.model.pojo.Cart">
insert into mall_cart (id, product_id, user_id,
quantity, selected, create_time,
update_time)
values (#{id,jdbcType=INTEGER}, #{productId,jdbcType=INTEGER}, #{userId,jdbcType=INTEGER},
#{quantity,jdbcType=INTEGER}, #{selected,jdbcType=INTEGER}, #{createTime,jdbcType=TIMESTAMP},
#{updateTime,jdbcType=TIMESTAMP})
</insert>
<insert id="insertSelective" parameterType="com.haiexijun.mall.model.pojo.Cart">
insert into mall_cart
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="id != null">
id,
</if>
<if test="productId != null">
product_id,
</if>
<if test="userId != null">
user_id,
</if>
<if test="quantity != null">
quantity,
</if>
<if test="selected != null">
selected,
</if>
<if test="createTime != null">
create_time,
</if>
<if test="updateTime != null">
update_time,
</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="id != null">
#{id,jdbcType=INTEGER},
</if>
<if test="productId != null">
#{productId,jdbcType=INTEGER},
</if>
<if test="userId != null">
#{userId,jdbcType=INTEGER},
</if>
<if test="quantity != null">
#{quantity,jdbcType=INTEGER},
</if>
<if test="selected != null">
#{selected,jdbcType=INTEGER},
</if>
<if test="createTime != null">
#{createTime,jdbcType=TIMESTAMP},
</if>
<if test="updateTime != null">
#{updateTime,jdbcType=TIMESTAMP},
</if>
</trim>
</insert>
<update id="updateByPrimaryKeySelective" parameterType="com.haiexijun.mall.model.pojo.Cart">
update mall_cart
<set>
<if test="productId != null">
product_id = #{productId,jdbcType=INTEGER},
</if>
<if test="userId != null">
user_id = #{userId,jdbcType=INTEGER},
</if>
<if test="quantity != null">
quantity = #{quantity,jdbcType=INTEGER},
</if>
<if test="selected != null">
selected = #{selected,jdbcType=INTEGER},
</if>
<if test="createTime != null">
create_time = #{createTime,jdbcType=TIMESTAMP},
</if>
<if test="updateTime != null">
update_time = #{updateTime,jdbcType=TIMESTAMP},
</if>
</set>
where id = #{id,jdbcType=INTEGER}
</update>
<update id="updateByPrimaryKey" parameterType="com.haiexijun.mall.model.pojo.Cart">
update mall_cart
set product_id = #{productId,jdbcType=INTEGER},
user_id = #{userId,jdbcType=INTEGER},
quantity = #{quantity,jdbcType=INTEGER},
selected = #{selected,jdbcType=INTEGER},
create_time = #{createTime,jdbcType=TIMESTAMP},
update_time = #{updateTime,jdbcType=TIMESTAMP}
where id = #{id,jdbcType=INTEGER}
</update>
<select id="selectCartByUserIdAndProductId" parameterType="map" resultMap="BaseResultMap">
select
<include refid="Base_Column_List"/>
from mall_cart
where user_id=#{userId} and product_id=#{productId}
</select>
<select id="selectList" resultType="com.haiexijun.mall.vo.CartVO" parameterType="java.lang.Integer">
select
c.id as id,
p.id as productId,
c.user_id as userId,
c.selected as selected,
c.quantity as quantity,
p.price as price,
p.name as productName,
p.image as productImage
from mall_cart c
left join mall_product p on p.id =c.product_id
where c.user_id=#{userId}
and p.status=1
</select>
<update id="selectOrNot" parameterType="map">
update mall_cart
set selected=#{selected}
where user_id=#{userId}
<if test="productId!=null">
and product_id=#{productId}
</if>
</update>
</mapper>
七.订单模块的开发
1.下单流程
|