IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> Spring Boot电商项目 -> 正文阅读

[Java知识库]Spring Boot电商项目

前言:这是我学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下面创建一个这个插件的配置文件,这个文件不用去记,用的时候复制改一下参数就行了:
但是要注意几点,就是先要把对应版本的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>
  <!-- 配置文件,放在resource目录下即可 -->
  <!--数据库驱动个人配置-->
  <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"/>
    <!--可以使用``包括字段名,避免字段名与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 {
    @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>
    <!--日志文件的存放位置,要在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"/>
      <PatternLayout
        pattern="[%d{MM:dd HH:mm:ss.SSS}] [%level] [%logger{36}] - %msg%n"/>
    </Console>

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

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

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

  </Loggers>
</Configuration>

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();
        //打印请求的url
        log.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;
    // 响应的数据data
    private 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方法
    @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的作用就是拦截异常的
@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);
        //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_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 {
        //注册校验
        //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 {
    @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 {
        //注册校验
        //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")
    @ResponseBody
    public 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")
    @ResponseBody
    public 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")
    @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 {
        //登录校验
        //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 {
    @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();
        }
        //userMapper的insertSelective方法会先判断插入数据的字段是否为空,如果不是空,才对他进行插入。insert方法则全部插入
        //方法返回插入成功的条数
        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){
        //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_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;

/**
 * 商品分类目录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 {

    @Autowired
    CategoryMapper categoryMapper;

    @Override
    public 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 {

    @Autowired
    UserService userService;

    @Autowired
    CateGoryService cateGoryService;

    //新增目录
    // 传入一个Httpsession,判断用户是否登录了
    @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的作用就是拦截异常的
@ControllerAdvice
public class GlobalExceptionHandler {
    private final Logger log= LoggerFactory.getLogger(GlobalExceptionHandler.class);
    
    //处理@Valid处理的异常
    @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 {
            //@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文档
    @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())){
                //如果通过传入的信息查到的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 {
    @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;

/**
 * 描述:AdminFilter的配置
 */
@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分页插件:

<!-- 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里面的方法:

    @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。
先引入两个我们要用到的依赖:

        <!-- 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 {

    @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
 */
@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=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());
        //如果同名不同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);
        }
    }

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

/**
 * 描述: 购物车Controller
 */
@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 {
        //内部获取用户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 {
    @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.下单流程

在这里插入图片描述

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-04-07 22:29:52  更:2022-04-07 22:32:23 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 4:50:50-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码