Spring Boot电商项目

  • 一.概述
    • 1.电商项目整体介绍
      • (1)前台模块具体分析
      • (2)后台模块具体分析
      • (3) 项目演示
    • 2 . 项目开发所需工具准备
  • 二. 数据库设计于项目初始化
    • 1.表设计
    • 2. 项目的初始化
    • 3.配置log4j2日志组件
    • 4. AOP统一处理web请求日志
  • 三.用户模块的开发
    • 1.用户模块整体介绍
    • 2.API统一返回对象
    • 3.注册接口的开发
    • 4.对密码进行MD5保护
    • 5.登录、登出、更新接口的开发
  • 四.商品分类管理模块的开发
    • 1.分类模块地主要功能
    • 2.后台新增分类功能的实现
    • 3. 使用@Valid注解进行参数校验
    • 4.Swagger自动生成API文档
    • 5.后台更新分类目录接口的开发
    • 6. 统一校验管理员身份
    • 7.删除分类目录接口
    • 8.查询用户分类列表接口的开发
    • 9.利用Redis缓存加速
  • 五. 商品模块的开发
    • 1. 后台新增商品的接口以及图片上传接口的开发
    • 2.更新和删除商品的接口
    • 3. 批量上下架商品的接口
  • 六.购物车模块
    • 1.部分业务流程
    • 2. 核心代码
  • 七.订单模块的开发
    • 1.下单流程

前言:这是我学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就可以了:

项目创建好之后,我们需要导入两个额外的依赖:

        <!-- https://mvnrepository.com/artifact/org.mybatis.spring.boot/mybatis-spring-boot-starter --><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>1.3.2</version></dependency><!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java --><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下面创建一个这个插件的配置文件generatorConfig.xml,这个文件不用去记,用的时候复制改一下参数就行了:
但是要注意几点,就是先要把对应版本的mysql-connector-java复制到resource目录下面,然后配置classPathEntry的location为当前电脑的maven目录。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfigurationPUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN""http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration><!-- 配置文件,放在resource目录下即可 --><!--数据库驱动个人配置--><classPathEntrylocation="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"/><!--可以使用``包括字段名,避免字段名与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/shop?useUnicode=true&amp;characterEncoding=UTF-8&amp;zeroDateTimeBehavior=convertToNull"userId="root"password="zc20020106"><property name="nullCatalogMeansCurrent" value="true"/></jdbcConnection><!-- 非必需,类型处理器,在数据库类型和java类型之间的转换控制--><javaTypeResolver><property name="forceBigDecimals" value="false"/></javaTypeResolver><!--生成Model类存放位置--><javaModelGenerator targetPackage="com.haiexijun.mall.model.pojo"targetProject="src/main/java"><!-- 是否允许子包,即targetPackage.schemaName.tableName --><property name="enableSubPackages" value="true"/><!-- 是否对类CHAR类型的列的数据进行trim操作 --><property name="trimStrings" value="true"/><!-- 建立的Model对象是否 不可改变  即生成的Model对象不会有 setter方法,只有构造方法 --><property name="immutable" value="false"/></javaModelGenerator><!--生成mapper映射文件存放位置--><sqlMapGenerator targetPackage="mappers" targetProject="src/main/resources"><property name="enableSubPackages" value="true"/></sqlMapGenerator><!--生成Dao类存放位置--><javaClientGenerator type="XMLMAPPER" targetPackage="com.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 {@AutowiredUserMapper userMapper;@Overridepublic 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 {@AutowiredUserService userService;@GetMapping("/test")@ResponseBodypublic 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的配置文件log4j2.xml,如下:

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="fatal"><Properties><!--日志文件的存放位置,要在linux中创建,在windows里面对应C盘用户目录下的logs目录,value可自己更改--><Property name="baseDir" value="${sys:user.home}/logs"/></Properties><Appenders><Console name="Console" target="SYSTEM_OUT"><!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch) --><ThresholdFilter level="info" onMatch="ACCEPT"onMismatch="DENY"/><PatternLayoutpattern="[%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>

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();//打印请求的urllog.info("URL : " + request.getRequestURL().toString());//打印请求方法log.info("HTTP_METHOD : " +request.getMethod());//打印请求的Ip地址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;// 响应的数据dataprivate T data;//响应成功为10000。这个状态数是我们自己定义的。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;}// 为了方便调试,打印出来,故重写toString方法@Overridepublic 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;}@Overridepublic 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的作用就是拦截异常的
@ControllerAdvice
public class GlobalExceptionHandler {private final Logger log= LoggerFactory.getLogger(GlobalExceptionHandler.class);//系统错误的异常@ExceptionHandler(Exception.class)@ResponseBodypublic Object handleException(Exception e){log.error("Default Exception : " +e);return ApiRestResponse.error(MallExceptionEnum.SYSTEM_ERROR);}//处理自定义的异常@ExceptionHandler(MallException.class)@ResponseBodypublic 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中对其进行实现:

    @Overridepublic 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);//userMapper的insertSelective方法会先判断插入数据的字段是否为空,如果不是空,才对他进行插入。insert方法则全部插入//方法返回插入成功的条数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_userwhere 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 {@AutowiredUserService userService;@GetMapping("/test")@ResponseBodypublic User personalPage(){return userService.getUser();}@PostMapping("/register")@ResponseBodypublic ApiRestResponse register(@RequestParam("userName") String userName, @RequestParam("password") String password) throws MallException {//注册校验//StringUtils是springboot提供的字符串判断类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;/*** 描述:MD5加密工具*/
public class MD5Utils {public static String GetMD5String(String strValue) throws NoSuchAlgorithmException {// Java为我们提供了MD5算法MessageDigest md5=MessageDigest.getInstance("MD5");//Base64用tomcat提供的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 {@AutowiredUserService userService;//测试接口@GetMapping("/test")@ResponseBodypublic User personalPage(){return userService.getUser();}//用户测试接口@PostMapping("/register")@ResponseBodypublic ApiRestResponse register(@RequestParam("userName") String userName, @RequestParam("password") String password) throws MallException {//注册校验//StringUtils是springboot提供的字符串判断类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")@ResponseBodypublic ApiRestResponse login(@RequestParam("userName") String userName, @RequestParam("password") String password, HttpSession session) throws MallException {//登录校验//StringUtils是springboot提供的字符串判断类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")@ResponseBodypublic ApiRestResponse updateUserInfo(HttpSession session,@RequestParam("signature") String signature) throws MallException {User currentUser= (User) session.getAttribute(Constant.MALL_USER);if(currentUser==null){//如果为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")@ResponseBodypublic ApiRestResponse logout(HttpSession session){session.removeAttribute(Constant.MALL_USER);return ApiRestResponse.success();}//管理员登录接口@PostMapping("/adminLogin")@ResponseBodypublic ApiRestResponse adminLogin(@RequestParam("userName") String userName, @RequestParam("password") String password, HttpSession session) throws MallException {//登录校验//StringUtils是springboot提供的字符串判断类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 {@AutowiredUserMapper userMapper;@Overridepublic User getUser() {//测试用return userMapper.selectByPrimaryKey(4);}@Overridepublic 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();}//userMapper的insertSelective方法会先判断插入数据的字段是否为空,如果不是空,才对他进行插入。insert方法则全部插入//方法返回插入成功的条数int count= userMapper.insertSelective(user);// 下面判断一下是否插入成功if (count==0){throw new MallException(MallExceptionEnum.INSERT_FAILED);}}@Overridepublic 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;}@Overridepublic void updateInformation(User user) throws MallException {//用于更新个性签名int updateCount=userMapper.updateByPrimaryKeySelective(user);if (updateCount>1){throw new MallException(MallExceptionEnum.UPDATE_FAILED);}}@Overridepublic boolean checkAdminRole(User user){//2是管理员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);//这里传入了两个以上的参数,要用到@para注解,一个参数则不用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_userwhere id = #{id,jdbcType=INTEGER}</select><delete id="deleteByPrimaryKey" parameterType="java.lang.Integer">delete from mall_userwhere 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_userset 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_userwhere username=#{userName,jdbcType=VARCHAR}</select><select id="selectLogin" parameterType="map" resultMap="BaseResultMap">select<include refid="Base_Column_List"/>from mall_userwhere 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_categorywhere 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;/*** 商品分类目录Service*/
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 {@AutowiredCategoryMapper categoryMapper;@Overridepublic void add(AddCategoryReq addCateGoryReq) throws MallException {Category category=new Category();//通过spring提供的BeanUtils.copyProperties(),把addCateGoryReq拷贝到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*/
@Controller
public class CategoryController {@AutowiredUserService userService;@AutowiredCateGoryService cateGoryService;//新增目录// 传入一个Httpsession,判断用户是否登录了@PostMapping("/admin/category/add")@ResponseBodypublic 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的作用就是拦截异常的
@ControllerAdvice
public class GlobalExceptionHandler {private final Logger log= LoggerFactory.getLogger(GlobalExceptionHandler.class);//处理@Valid处理的异常@ExceptionHandler(MethodArgumentNotValidException.class)@ResponseBodypublic 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 {//@Valid的参数异常return ApiRestResponse.error(MallExceptionEnum.REQUEST_PARAM_ERROR.getCode(),list.toString());}}}

这样就可以了。

4.Swagger自动生成API文档

下面我们简单学习一下如何使用Swagger自动生成API文档,这里只做简单的使用,并没有详细的教学。如果要看更详细的教程,请移步其他Swagger的入门教程。而且现在国内也有其他比Swagger好用的接口框架,不一定要用Swagger。

我先引入Swagger的相关依赖:

        <!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger2 --><dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger2</artifactId><version>2.9.2</version></dependency><!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger-ui --><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 {//访问http://localhost:8083/swagger-ui.html可以看到API文档@Beanpublic 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 {@Overridepublic 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")@ResponseBodypublic 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方法:

    @Overridepublic void update(Category updateCategory) throws MallException {//校验分类目录的名字是否有冲突if(updateCategory.getName()!=null){Category categoryOld=categoryMapper.selectByName(updateCategory.getName());if (categoryOld !=null && !categoryOld.getId().equals(updateCategory.getId())){//如果通过传入的信息查到的id存在,则抛出显示不允许重名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 {@AutowiredUserService userService;@Overridepublic void init(FilterConfig filterConfig) throws ServletException {Filter.super.init(filterConfig);}@Overridepublic 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;}}@Overridepublic 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;/*** 描述:AdminFilter的配置*/
@Configuration
public class AdminFilterConfig {@Beanpublic 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方法:

    @Overridepublic 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")@ResponseBodypublic ApiRestResponse deleteCategory(@RequestParam("id") Integer id) throws MallException {cateGoryService.delete(id);return ApiRestResponse.success();}

8.查询用户分类列表接口的开发

我们先来开发一下后台的查询用户分类列表接口:

分页先要引入pagehelper分页插件:

<!-- https://mvnrepository.com/artifact/com.github.pagehelper/pagehelper-spring-boot-starter -->
<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里面的方法:

    @Overridepublic 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")@ResponseBodypublic 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_categorywhere parent_id=#{parentId}</select>

然后就编写service类的两个方法:

    @Overridepublic 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")@ResponseBodypublic ApiRestResponse listCategoryForCustomer(){List<CategoryVO> categoryVOS =cateGoryService.listCategoryForCustomer();return ApiRestResponse.success(categoryVOS);}

9.利用Redis缓存加速

考虑到目录变化其实不是很频繁,所以使用Redis缓存来提高我们整体的效率。这一节主要学习用Spring-boot来集成redis。
先引入两个我们要用到的依赖:

        <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-redis --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-cache --><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 {@Beanpublic 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*/
@Controller
@Api(tags = "后台商品管理相关接口",description = "有后台添加商品的接口、图片上传的接口、更新和删除商品的接口、批量上下架商品的接口、后台商品列表和商品详情的接口")
public class ProductAdminController {@AutowiredProductService productService;@PostMapping("/admin/product/add")@ApiOperation("后台添加商品")@ResponseBodypublic 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=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 {@AutowiredProductMapper productMapper;@Overridepublic 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层:

    @Overridepublic void update(Product updateProduct) throws MallException {Product productOld=productMapper.selectByName(updateProduct.getName());//如果同名不同id,不能进行修改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);}}@Overridepublic 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层:

    @Overridepublic void batchUpdateSellStatus(Integer[] ids,Integer sellStatus){productMapper.batchUpdateSellStatus(ids,sellStatus);}

mapper.xml:

  <update id="batchUpdateSellStatus">update mall_productset 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;/*** 描述: 购物车Controller*/
@RestController
@Api(tags = "购物车相关的接口")
@RequestMapping("/cart")
public class CartController {@AutowiredCartService cartService;@PostMapping("/add")@ApiOperation("商品加入购物车")public ApiRestResponse add(@RequestParam Integer productId,@RequestParam Integer count) throws MallException {//内部获取用户id防止越权操作别人的Integer userId= UserFilter.currentUser.getId();List<CartVO> cartVOList= cartService.add(userId,productId,count);return ApiRestResponse.success(cartVOList);}@ApiOperation("获取购物车列表")@GetMapping("/list")public ApiRestResponse list(){//内部获取用户id防止越权操作别人的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 {//内部获取用户id防止越权操作别人的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 {//内部获取用户id防止越权操作别人的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 {//内部获取用户id防止越权操作别人的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 {//内部获取用户id防止越权操作别人的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的实现类*/
@Service
public class CartServiceImpl implements CartService {@AutowiredProductMapper productMapper;@AutowiredCartMapper cartMapper;@Overridepublic 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);}}@Overridepublic 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;}@Overridepublic 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);}@Overridepublic 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);}@Overridepublic 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);}@Overridepublic 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_cartwhere id = #{id,jdbcType=INTEGER}</select><delete id="deleteByPrimaryKey" parameterType="java.lang.Integer">delete from mall_cartwhere 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_cartset 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_cartwhere user_id=#{userId} and product_id=#{productId}</select><select id="selectList" resultType="com.haiexijun.mall.vo.CartVO" parameterType="java.lang.Integer">selectc.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 productImagefrom mall_cart cleft join mall_product p on p.id =c.product_idwhere c.user_id=#{userId}and p.status=1</select><update id="selectOrNot" parameterType="map">update mall_cartset selected=#{selected}where user_id=#{userId}<if test="productId!=null">and product_id=#{productId}</if></update></mapper>

七.订单模块的开发

1.下单流程

Spring Boot电商项目相关推荐

  1. Spring Boot电商项目57:订单模块六:【前台:生成支付二维码】接口;(支付url的拼凑;利用zxing生成二维码;二维码图片的存储;真实地址与可访问地址的转换;)

    说明: (1)本篇博客主要内容是:开发[前台:生成支付二维码]接口: (2)本篇博客需要注意的点有: ● 支付url的拼凑: ● 利用zxing生成二维码: ● 二维码图片的存储:真实地址与可访问地址 ...

  2. Spring Boot电商项目:概述;

    说明: (1)强调:该Spring Boot电商项目中,会有大量的以前接触过的内容:但是,当我们遇到的时候,也会重新啰嗦.重复解释:以达到该专栏形成一个比较好的闭环的目的: 目录 一:Spring B ...

  3. Spring Boot电商项目59:订单模块八:【后台:订单列表】接口;

     说明: (1)本篇博客的主要内容是开发[后台:订单列表]接口: (2)本篇博客没什么难点:只需要注意:[前台:订单列表]接口是查询当前登录用户的订单数据:[后台:订单列表]接口是查询所有用户的订单数 ...

  4. Spring Boot电商项目17:用户模块六:注册接口开发之:使用【GlobalExceptionHandler】来全局统一处理异常;(涉及了@ControllerAdvice等注解)

    说明: (1)为什么写这篇博客?:在[Spring Boot电商项目15:用户模块四:注册接口开发:]中,在Service层中遇到了[用户名重复]的情况,然后Service层把这个情况做成了一个异常, ...

  5. 一套仿阿里完整版Spring Boot电商项目,前后端分离+权限管理系统

    项目简介 1.项目背景 2. B2B 模式 3. B2C 模式 ==(商城模式)*== 4. C2B 模式 5. C2C 模式 6. O2O 模式 2.商城架构图 2.1.前后端分离 基础版 2.3. ...

  6. Spring Boot电商项目52:订单模块一:订单模块介绍;(模块介绍;效果演示;数据库设计;9个接口介绍;)

    说明: (1)本篇博客的内容:本篇博客,简单梳理下[订单模块]的基本内容: (2)声明:[订单模块]是一个比较复杂的模块,其中涉及了订单的状态的改变和订单的流转:所以,本篇博客,只是简单介绍了[订单模 ...

  7. spring boot电商系统前端界面设计与浏览器兼容性研究 毕业设计-附源码231058

    摘  要 随着科学技术的飞速发展,各行各业都在努力与现代先进技术接轨,通过科技手段提高自身的优势:对于电商系统前端界面设计与浏览器兼容性研究当然也不能排除在外,随着网络技术的不断成熟,带动了电商系统前 ...

  8. 推荐几个9月爆火的 GitHub 电商项目 赶紧收藏

    原文链接:https://mp.weixin.qq.com/s/pBZR6n8gxl19LAIBsH6XPg 逛逛GitHub. 每天推荐一个好玩的 GitHub 开源项目. 01. 新蜂电商 第一个 ...

  9. springboot项目实战_2019学习进阶之路:高并发+性能优化+Spring boot等大型项目实战...

    Java架构师主要需要做哪些工作呢? 负责设计和搭建软件系统架构(平台.数据库.接口和应用架构等),解决开发中各种系统架构问题. 优化现有系统的性能,解决软件系统平台关键技术问题攻关.核心功能模块设计 ...

最新文章

  1. 科技公司狂挖高校AI学者:涸泽而渔还是产学双赢
  2. 基于 Kubernetes 的微服务项目设计与实现
  3. Azure Stack-1807 版本 配置10分钟、自动部署6小时-我的ASDK第7次实践
  4. mysql数据库驱动_JDBC 加载mysql数据库驱动
  5. hive中如何控制mapper的数量
  6. ios开发时,在Xcode中添加多个targets进行版本控制
  7. Simulink框图和S-函数
  8. 计算机发展史较为重大的事件,图说:15件计算机发展史中的重大事件(7)
  9. python表单验证_python表单验证封装
  10. Linux安装tomcat并使用+热部署
  11. spark RDD transformation与action函数整理
  12. xtrabackup 2.4.3 BUG
  13. ADSL常见问题 经典故障
  14. 酉矩阵(幺正矩阵、unitary matrix)
  15. select 获取选择的值
  16. 还在用老办法扫描?纸质文档变电子文档,手机这个功能一键搞定!
  17. 电商直播的直播类型有哪些?
  18. 解决基于html5video标签多个视频同时播放的问题
  19. python pandas数据清洗:sample()函数
  20. 小米手机3,小米手机4 官方移动稳定版最新 ROM V6.7.1.0.KXDCNCH

热门文章

  1. APP极光消息推送无法接收,部分手机设置
  2. 英语基础语法(10) 直接引语和间接引语
  3. Office-Word如何取消修订模式
  4. mysql. 表和视图同名_MySQL基础(4) | 视图
  5. java elgamal_ElGamal算法进行加密和解密的基本原理及实现
  6. 挑战一晚上从零入门lua语言,直接对标Python快速上手
  7. 基于小波时频图和2D-CNN的滚动轴承故障检测
  8. 基于python,控制微信自动登录并发送消息给指定联系人
  9. 12.9 - 每日一题 - 408
  10. 【Java 18】网络编程 - 概述、网络编程要素、IP和端口号、网络协议、TCP、UDP、URL