苍穹外卖实践1
5509字约18分钟
2026-01-22
开发规范
一张数据表对应一个Mapper
DTO类:前端发送过来的数据参数类型
刚开始无法登录的问题:https://juejin.cn/post/7528313969150558254
将 webservers 改为服务端的 ip(本地为 localhost:8080)
项目介绍
关于本项目的技术选型, 我们将会从 用户层、网关层、应用层、数据层 这几个方面进行介绍,主要用于展示项目中使用到的技术框架和中间件等。

1). 用户层
本项目中在构建系统管理后台的前端页面,我们会用到H5、Vue.js、ElementUI、apache echarts(展示图表)等技术。而在构建移动端应用时,我们会使用到微信小程序。
2). 网关层
Nginx是一个服务器,主要用来作为Http服务器,部署静态资源,访问性能高。在Nginx中还有两个比较重要的作用: 反向代理和负载均衡, 在进行项目部署时,要实现Tomcat的负载均衡,就可以通过Nginx来实现。
3). 应用层
SpringBoot: 快速构建Spring项目, 采用 "约定优于配置" 的思想, 简化Spring项目的配置开发。
SpringMVC:SpringMVC是spring框架的一个模块,springmvc和spring无需通过中间整合层进行整合,可以无缝集成。
Spring Task: 由Spring提供的定时任务框架。
httpclient: 主要实现了对http请求的发送。
Spring Cache: 由Spring提供的数据缓存框架
JWT: 用于对应用程序上的用户进行身份验证的标记。
阿里云OSS: 对象存储服务,在项目中主要存储文件,如图片等。
Swagger: 可以自动的帮助开发人员生成接口文档,并对接口进行测试。
POI: 封装了对Excel表格的常用操作。
WebSocket: 一种通信网络协议,使客户端和服务器之间的数据交换更加简单,用于项目的来单、催单功能实现。
4). 数据层
MySQL: 关系型数据库, 本项目的核心业务数据都会采用MySQL进行存储。
Redis: 基于key-value格式存储的内存数据库, 访问速度快, 经常使用它做缓存。
Mybatis: 本项目持久层将会使用Mybatis开发。
pagehelper: 分页插件。
spring data redis: 简化java代码操作Redis的API。
5). 工具
git: 版本控制工具, 在团队协作中, 使用该工具对项目中的代码进行管理。
maven: 项目构建工具。
junit:单元测试工具,开发人员功能实现完毕后,需要通过junit对功能进行单元测试。
postman: 接口测工具,模拟用户发起的各类HTTP请求,获取对应的响应结果。
项目各个目录的作用

nginx
反向代理

# 反向代理,处理管理端发送的请求
location /api/ {
proxy_pass http://localhost:8080/admin/;
#proxy_pass http://webservers/admin/;
}
# 反向代理,处理用户端发送的请求
location /user/ {
proxy_pass http://localhost:8080/user/;
}负载均衡


upstream webservers{
server 127.0.0.1:8080 weight=90 ;
#server 127.0.0.1:8088 weight=10 ;
}md5 加密
String password = employeeLoginDTO.getPassword();
password = DigestUtils.md5DigestAsHex(password.getBytes());注意点:md5DigestAsHex()只能接受byte类型的数据
Swagger接口测试
5.1 介绍
Swagger 是一个规范和完整的框架,用于生成、描述、调用和可视化 RESTful 风格的 Web 服务(https://swagger.io/)。 它的主要作用是:
使得前后端分离开发更加方便,有利于团队协作
接口的文档在线自动生成,降低后端开发人员编写接口文档的负担
功能测试
Spring已经将Swagger纳入自身的标准,建立了Spring-swagger项目,现在叫Springfox。通过在项目中引入Springfox ,即可非常简单快捷的使用Swagger。
knife4j是为Java MVC框架集成Swagger生成Api文档的增强解决方案,前身是swagger-bootstrap-ui,取名kni4j是希望它能像一把匕首一样小巧,轻量,并且功能强悍!
目前,一般都使用knife4j框架。
5.2 使用步骤
导入 knife4j 的maven坐标
在pom.xml中添加依赖
<dependency> <groupId>com.github.xiaoymin</groupId> <artifactId>knife4j-spring-boot-starter</artifactId> </dependency>在配置类中加入 knife4j 相关配置
WebMvcConfiguration.java
/** * 通过knife4j生成接口文档 * @return */ @Bean public Docket docket() { ApiInfo apiInfo = new ApiInfoBuilder() .title("苍穹外卖项目接口文档") .version("2.0") .description("苍穹外卖项目接口文档") .build(); Docket docket = new Docket(DocumentationType.SWAGGER_2) .apiInfo(apiInfo) .select() // 接口路径 .apis(RequestHandlerSelectors.basePackage("com.sky.controller")) .paths(PathSelectors.any()) .build(); return docket; }设置静态资源映射,否则接口文档页面无法访问
WebMvcConfiguration.java
/** * 设置静态资源映射 * @param registry */ protected void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/doc.html").addResourceLocations("classpath:/META-INF/resources/"); registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/"); }访问测试
接口文档访问路径为 http://ip:port/doc.html ---> http://localhost:8080/doc.html

接口测试:测试登录功能

Swagger常用注解
swagger类:用来生成项目api接口可视化网页
在pom.xml中添加依赖
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
</dependency>
示例代码:
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
@Api(tags = "C端-分类接口")
public class CategoryController {
@GetMapping("/list")
@ApiOperation("查询分类")
public Result<List<Category>> list(Integer type) {
List<Category> list = categoryService.list(type);
return Result.success(list);
}
}
SpringBoot基本项目结构
以员工表为例,设计employee表(员工总信息:状态,创建时间以及修改人的id......)
具体流程:前端数据 --> Controller --> Server --> Mapper --> SQL
Controller:接口
Server :服务
Mapper :数据库
1.2.1 设计DTO类
根据新增员工接口设计对应的DTO
**注意:**当前端提交的数据和实体类中对应的属性差别比较大时,建议使用DTO来封装数据
由于上述传入参数和实体类有较大差别,所以自定义DTO类。
进入sky-pojo模块,在com.sky.dto包下,已定义EmployeeDTO
package com.sky.dto;
import lombok.Data;
import java.io.Serializable;
@Data
public class EmployeeDTO implements Serializable {
private Long id;
private String username;
private String name;
private String phone;
private String sex;
private String idNumber;
}1.2.2 Controller层
EmployeeController中创建新增员工方法
进入到sky-server模块中,在com.sky.controller.admin包下,在EmployeeController中创建新增员工方法,接收前端提交的参数。
/**
* 新增员工
* @param employeeDTO
* @return
*/
@PostMapping
@ApiOperation("新增员工")
public Result save(@RequestBody EmployeeDTO employeeDTO){
log.info("新增员工:{}",employeeDTO);
employeeService.save(employeeDTO);//该方法后续步骤会定义
return Result.success();
}**注:**Result类定义了后端统一返回结果格式。
1.2.3 Service层接口
在EmployeeService接口中声明新增员工方法
进入到sky-server模块中,com.sky.server.EmployeeService
/**
* 新增员工
* @param employeeDTO
*/
void save(EmployeeDTO employeeDTO);1.2.4 Service层实现类
在EmployeeServiceImpl中实现新增员工方法
com.sky.server.impl.EmployeeServiceImpl中创建方法
/**
* 新增员工
*
* @param employeeDTO
*/
public void save(EmployeeDTO employeeDTO) {
Employee employee = new Employee();
//对象属性拷贝
BeanUtils.copyProperties(employeeDTO, employee);
//设置账号的状态,默认正常状态 1表示正常 0表示锁定
employee.setStatus(StatusConstant.ENABLE);
//设置密码,默认密码123456
employee.setPassword(DigestUtils.md5DigestAsHex(PasswordConstant.DEFAULT_PASSWORD.getBytes()));
//设置当前记录的创建时间和修改时间
employee.setCreateTime(LocalDateTime.now());
employee.setUpdateTime(LocalDateTime.now());
//设置当前记录创建人id和修改人id
employee.setCreateUser(10L);//目前写个假数据,后期修改
employee.setUpdateUser(10L);
employeeMapper.insert(employee);//后续步骤定义
}在sky-common模块com.sky.constants包下已定义StatusConstant.java
package com.sky.constant;
/**
* 状态常量,启用或者禁用
*/
public class StatusConstant {
//启用
public static final Integer ENABLE = 1;
//禁用
public static final Integer DISABLE = 0;
}1.2.5 Mapper层
在EmployeeMapper中声明insert方法
com.sky.EmployeeMapper中添加方法
/**
* 插入员工数据
* @param employee
*/
@Insert("insert into employee (name, username, password, phone, sex, id_number, create_time, update_time, create_user, update_user,status) " +
"values " +
"(#{name},#{username},#{password},#{phone},#{sex},#{idNumber},#{createTime},#{updateTime},#{createUser},#{updateUser},#{status})")
void insert(Employee employee);在application.yml中已开启驼峰命名,故id_number和idNumber可对应。
mybatis:
configuration:
#开启驼峰命名
map-underscore-to-camel-case: true创建人和修改人id
介绍:
ThreadLocal 并不是一个Thread,而是Thread的局部变量。 ThreadLocal 为每个线程提供单独一份存储空间,具有线程隔离的效果,只有在线程内才能获取到对应的值,线程外则不能访问。
常用方法:
- public void set(T value) 设置当前线程的线程局部变量的值
- public T get() 返回当前线程所对应的线程局部变量的值
- public void remove() 移除当前线程的线程局部变量

拦截器
HandlerInterceptor是拦截工具,WebMvcConfigurationSupport是拦截工具的配置文件(制定拦截规则)
WebMvcConfigurationSupport
WebMvcConfigurationSupport 的核心价值是通过重写其内置方法,定制 Spring MVC 的核心规则,最常用的方法集中在拦截器注册、静态资源映射、跨域配置、消息转换器这 4 个场景,下面逐一说明。
1. addInterceptors () — 注册拦截器(使用频率★★★★★)
作用:将自定义的 HandlerInterceptor 注册到 Spring MVC 中,指定拦截 / 放行规则,是拦截器生效的核心方法。
实战示例(登录校验拦截器):
@Override
protected void addInterceptors(InterceptorRegistry registry) {
// 注册登录拦截器
// 拦截的链接都交给HandlerInterceptor处理(new LoginCheckInterceptor())
registry.addInterceptor(new LoginCheckInterceptor())
.addPathPatterns("/**") // 拦截所有请求
.excludePathPatterns( // 放行无需拦截的路径
"/admin/login", // 登录接口
"/user/login",
"/doc.html", // Swagger文档
"/webjars/**", // 静态资源
"/swagger-resources/**"
);
// 可注册多个拦截器(比如再加一个日志拦截器)
registry.addInterceptor(new LogInterceptor()).addPathPatterns("/**");
}return true表示放行,return false表示拦截
2. addResourceHandlers () — 配置静态资源映射(使用频率★★★★☆)
作用:指定静态资源(HTML/CSS/JS/ 图片、Swagger 文档等)的访问路径,解决 “静态资源 404” 问题(比如 Spring Boot 默认只识别 resources/static,自定义路径需手动配置)。
实战示例(放行 Swagger 文档 + 自定义静态资源):
@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
// 1. 放行Swagger文档(前后端分离项目常用)
registry.addResourceHandler("/doc.html")
.addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/");
// 2. 自定义静态资源路径:访问 /images/** 映射到 resources/images/
registry.addResourceHandler("/images/**")
.addResourceLocations("classpath:/images/");
}3. addCorsMappings () — 配置跨域请求(使用频率★★★★☆)
作用:解决前后端分离项目的 “跨域问题”(前端域名≠后端域名时的请求限制),比传统的 CorsFilter 更简洁。
实战示例(允许所有域名跨域):
@Override
protected void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**") // 对所有接口生效
.allowedOriginPatterns("*") // 允许所有域名(生产环境建议指定具体域名)
.allowedMethods("GET", "POST", "PUT", "DELETE") // 允许的请求方法
.allowedHeaders("*") // 允许所有请求头
.allowCredentials(true) // 允许携带Cookie
.maxAge(3600); // 预检请求缓存时间(秒)
}4. configureMessageConverters () — 配置消息转换器(使用频率★★★☆☆)
configureMessageConverters:完全替换 Spring MVC 默认的消息转换器列表(会清空原有转换器);
extendMessageConverters:扩展 / 修改原有转换器列表(保留默认转换器,仅做新增 / 调整);
日常开发中优先用 extendMessageConverters,除非你需要完全自定义所有转换器。
作用:定制 JSON 序列化 / 反序列化规则(比如日期格式、空值处理、枚举转换),替换默认的 Jackson 或集成 FastJson。
实战示例(统一日期格式):
@Override
protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
// 1. 创建Jackson消息转换器
MappingJackson2HttpMessageConverter jacksonConverter = new MappingJackson2HttpMessageConverter();
// 2. 配置序列化规则
ObjectMapper objectMapper = new ObjectMapper();
// 日期格式统一为 yyyy-MM-dd HH:mm:ss
objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
// 忽略空值字段(可选)
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
jacksonConverter.setObjectMapper(objectMapper);
// 3. 添加到转换器列表(优先级高于默认)
converters.add(0, jacksonConverter);
}5. configurePathMatch () — 配置路径匹配规则(使用频率★★☆☆☆)
作用:定制 URL 路径的匹配规则(比如是否忽略大小写、是否忽略后缀)。
实战示例(忽略 URL 后缀,如 /user 和 /user.json 都能访问)
@Override
protected void configurePathMatch(PathMatchConfigurer configurer) {
// 忽略URL的后缀(如 .html/.json)
configurer.setUseSuffixPatternMatch(false);
// 忽略URL大小写(如 /User 和 /user 视为同一个接口)
configurer.setUseCaseSensitiveMatch(false);
}HandlerInterceptor
return true表示放行,return false表示拦截
@Slf4j
// 拦截器
@Component
public class LoginCheckInterceptor implements HandlerInterceptor {
// 请求处理之前执行(核心拦截点),返回true放行,返回false拦截
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
log.info("链接已拦截");
String url = request.getRequestURI().toString();
log.info("请求URL {}", url);
// 2.判断请求的url中是否包含有login,如果包含,则说明是登录操作,则放行
if (url.contains("login")) {
log.info("登录放行操作,放行。。。");
return true;
}
// ......
return true;
}
// 请求处理之后、视图渲染之前执行
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
}
// 请求处理完全结束后执行(最终清理)
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
}
}1. HttpServletRequest request —— HTTP 请求对象
核心含义
代表客户端发送给服务器的所有 HTTP 请求信息,是 Servlet 规范的核心接口,Spring MVC 对其做了封装,包含请求头、请求参数、请求路径、Cookie、Session 等所有请求相关数据。
核心作用
| 常用操作 | 示例代码 | 作用说明 |
|---|---|---|
| 获取请求参数 | request.getParameter("name") | 获取客户端传入的参数(如 URL 参数、表单参数) |
| 获取请求路径 | request.getRequestURI() | 获取请求的路径(如 /admin/report/export) |
| 获取请求头 | request.getHeader("Token") | 获取请求头信息(如 Token、User-Agent) |
| 获取 Cookie | request.getCookies() | 获取客户端携带的 Cookie |
| 获取 Session | request.getSession() | 获取 / 创建用户会话(保存用户状态) |
| 获取请求方式 | request.getMethod() | 判断是 GET/POST/PUT/DELETE 等请求方式 |
通俗类比
相当于快递员(客户端)送到你公司(服务器)的 “快递单 + 包裹”:request 包含了快递单上的收件人、地址、联系方式(请求头 / 参数),以及包裹里的内容(请求体)。
2. HttpServletResponse response —— HTTP 响应对象
核心含义
代表服务器要返回给客户端的所有 HTTP 响应信息,同样是 Servlet 规范的核心接口,用于设置响应状态码、响应头、响应体(如返回 JSON、文件、页面)、Cookie 等。
核心作用
| 常用操作 | 示例代码 | 作用说明 |
|---|---|---|
| 设置响应状态码 | response.setStatus(200) | 设置 200(成功)、404(未找到)、500(服务器错误)等 |
| 设置响应头 | response.setHeader("Content-Type", "application/json") | 指定响应数据类型(如 JSON、Excel) |
| 输出响应体 | response.getWriter().write("success") | 向客户端返回字符串数据 |
| 下载文件 | response.getOutputStream().write(fileBytes) | 向客户端输出二进制文件(如 Excel、图片) |
| 设置 Cookie | response.addCookie(new Cookie("token", "xxx")) | 向客户端写入 Cookie |
| 设置字符编码 | response.setCharacterEncoding("UTF-8") | 避免响应数据乱码 |
通俗类比
相当于你(服务器)要寄回给快递员(客户端)的 “回执单 + 回寄包裹”:response 包含回执单上的签收状态(状态码)、备注(响应头),以及回寄的包裹内容(响应体)。
3. Object handler —— 处理器对象
四、核心总结
| 参数 | 核心定位 | 核心作用 |
|---|---|---|
HttpServletRequest | 请求信息载体 | 读取客户端传入的所有请求数据(参数、头、路径等) |
HttpServletResponse | 响应信息载体 | 向客户端返回数据(JSON、文件、状态码等) |
Object handler | 请求处理器标识 | 定位处理当前请求的控制器方法,获取方法 / 类的元信息 |
统一时间格式
package com.sky.config;
import com.sky.interceptor.JwtTokenAdminInterceptor;
import com.sky.interceptor.JwtTokenUserInterceptor;
import com.sky.json.JacksonObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
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;
import java.util.List;
/**
* 配置类,注册web层相关组件
*/
@Configuration
@Slf4j
public class WebMvcConfiguration extends WebMvcConfigurationSupport {
@Autowired
private JwtTokenAdminInterceptor jwtTokenAdminInterceptor;
@Autowired
private JwtTokenUserInterceptor jwtTokenUserInterceptor;
/**
* 注册自定义拦截器
*
* @param registry
*/
protected void addInterceptors(InterceptorRegistry registry) {
log.info("开始注册自定义拦截器...");
registry.addInterceptor(jwtTokenAdminInterceptor)
.addPathPatterns("/admin/**")
.excludePathPatterns("/admin/employee/login");
registry.addInterceptor(jwtTokenUserInterceptor)
.addPathPatterns("/user/**")
.excludePathPatterns("/user/user/login")
.excludePathPatterns("/user/shop/status");
}
/**
* 统一时间格式
* @param converters
*/
protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
log.info("开始消息转换");
// 创建一个消息转换器对象
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
// 设置对象转换器,可以将java对象转换成json字符串
converter.setObjectMapper(new JacksonObjectMapper());
// 将转换器放入spring MVC中
converters.add(0, converter);
}
}ThreadLocal线程隔离
介绍:
ThreadLocal 并不是一个Thread,而是Thread的局部变量。 ThreadLocal为每个线程提供单独一份存储空间,具有线程隔离的效果,只有在线程内才能获取到对应的值,线程外则不能访问。
常用方法:
- public void set(T value) 设置当前线程的线程局部变量的值
- public T get() 返回当前线程所对应的线程局部变量的值
- public void remove() 移除当前线程的线程局部变量
- nitialValue () 初始化变量

初始工程中已经封装了 ThreadLocal 操作的工具类:
public class BaseContext {
// 创建线程
public static ThreadLocal<Long> threadLocal = new ThreadLocal<>();
// 将用户id存储到线程中
public static void setCurrentId(Long id) {
threadLocal.set(id);
}
// 获取用户id
public static Long getCurrentId() {
return threadLocal.get();
}
// 销毁线程
public static void removeCurrentId() {
threadLocal.remove();
}
// 初始化变量
protected Long initialValue() {
return 0; // 线程第一次 get() 时,若未 set(),则返回 0
}
}在拦截器中解析出当前登录员工id,并放入线程局部变量中:
存放用户id
BaseContext.setCurrentId(10);获取线程局部变量中的值:
//获取当前用户id
Long id = BaseContext.getCurrentId(); // 10异常处理
全局异常处理
// 这段代码是一个 全局异常处理器,用于统一捕获和处理 Spring 应用中抛出的异常
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler // 获取异常
public Result exceptionHandler(BaseException ex){
log.error("异常信息:{}", ex.getMessage());
return Result.error(ex.getMessage());
}
@ExceptionHandler // 获取数据库异常
public Result exceptionHandler(SQLIntegrityConstraintViolationException ex){ }
}自定义业务异常
public class BaseException extends RuntimeException {
public BaseException() { }
public BaseException(String msg) { super(msg); }
}// 自定义的密码错误异常
public class PasswordErrorException extends BaseException {
public PasswordErrorException() { }
public PasswordErrorException(String msg) { super(msg); }
}throw new PasswordErrorException("密码错误异常");MyBatis
模版
<?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.sky.mapper.UserMapper">
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
insert into user (openid, name, phone, sex, id_number, avatar, create_time)
values (#{openid}, #{name}, #{phone}, #{sex}, #{idNumber}, #{avatar}, #{createTime})
</insert>
</mapper>resultType是返回的类型,parameterType是传进来的类型
<select id="list" parameterType="AddressBook" resultType="AddressBook">
......
</select>- useGeneratedKeys="true" 是告诉 MyBatis:“我要获取数据库自动生成的主键”。
- keyProperty="id" 是告诉 MyBatis:“把获取到的主键值,赋值给传入参数(Setmeal 对象)的 id 属性”。
useGeneratedKeys和keyProperty两者必须配合使用,缺一不可
只有开启useGeneratedKeys,MyBatis 才会去拿主键;只有指定keyProperty,才知道把主键值放到哪个属性里。
<insert id="insert" parameterType="Setmeal" useGeneratedKeys="true" keyProperty="id">
......
</insert>String 方法
判断某字段是否存在
String 类的 contains() 方法是用于判断当前字符串是否包含指定的字符序列
String message = ex.getMessage();
if(message.contains("Duplicate entry")){}分页查询
maven 插件
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>${pagehelper}</version>
</dependency>mybatis-pagehelper:简化分页查询语句插件
// 表示第1页,该页中最多显示3条数据
PageHelper.startPage(0, 3);
// 将所有的查询结果返回到Page中
Page<Employee> page = employeeMapper.pageQuery(employeePageQueryDTO);
// 根据PageHelper中设置的页码以及当前页数据量返回对应的值
// 总条数
long total = page.getTotal();
// 当前页数据
List<Employee> records = page.getResult();-- LIMIT 0, 3
-- 限制返回结果的行数,是 MySQL 中用于分页或限制结果集的关键字。
-- 第一个参数 `0`:表示 “起始行索引”(从 0 开始计数,即从第 1 行开始)。
-- 第二个参数 `3`:表示 “返回的最大行数”(最多返回 3 行数据)。
select * from user1 limit 0, 3;/**
* 用户端订单分页查询进阶
*
* @param pageNum
* @param pageSize
* @param status
* @return
*/
public PageResult pageQuery4User(int pageNum, int pageSize, Integer status) {
// 设置分页
PageHelper.startPage(pageNum, pageSize);
OrdersPageQueryDTO ordersPageQueryDTO = new OrdersPageQueryDTO();
ordersPageQueryDTO.setUserId(BaseContext.getCurrentId());
ordersPageQueryDTO.setStatus(status);
// 分页条件查询
Page<Orders> page = orderMapper.pageQuery(ordersPageQueryDTO);
List<OrderVO> list = new ArrayList();
// 查询出订单明细,并封装入OrderVO进行响应
if (page != null && page.getTotal() > 0) {
for (Orders orders : page) {
Long orderId = orders.getId();// 订单id
// 查询订单明细
List<OrderDetail> orderDetails = orderDetails = orderDetailMapper.getByOrderId(orderId);
OrderVO orderVO = new OrderVO();
BeanUtils.copyProperties(orders, orderVO);
orderVO.setOrderDetailList(orderDetails);
list.add(orderVO);
}
}
// OrderVO继承Orders,所以返回 List<OrderVO> list 相当于是返回 OrderVO 加 Orders 的属性
return new PageResult(page.getTotal(), list);
}AOP
joinPoint.getArgs():获取目标方法参数(只读 / 修改参数,不执行方法);
joinPoint.proceed():执行目标方法(触发业务逻辑,返回方法结果)
/**
* 自定义切面,实现公共字段自动填充处理逻辑
*/
@Aspect
@Component
@Slf4j
public class AutoFillAspect {
/**
* 切入点
*/
@Pointcut("execution(* com.sky.mapper.*.*(..)) && @annotation(com.sky.annotation.AutoFill)")
public void autoFillPointCut() {
}
/**
* 前置通知,在通知中进行公共字段的赋值
*/
@Before("autoFillPointCut()")
public void autoFill(JoinPoint joinPoint) {
log.info("开始进行公共字段自动填充...");
//获取到当前被拦截的方法上的数据库操作类型
MethodSignature signature = (MethodSignature) joinPoint.getSignature();//方法签名对象
AutoFill autoFill = signature.getMethod().getAnnotation(AutoFill.class);//获得方法上的注解对象
OperationType operationType = autoFill.value();//获得数据库操作类型
//获取到当前被拦截的方法的参数--实体对象
Object[] args = joinPoint.getArgs();
if (args == null || args.length == 0) {
return;
}
// 获取第一个参数
Object entity = args[0];
//准备赋值的数据
LocalDateTime now = LocalDateTime.now(); // 当前时间
Long currentId = BaseContext.getCurrentId(); // 操作人id
//根据当前不同的操作类型,为对应的属性通过反射来赋值
if (operationType == OperationType.INSERT) {
//为4个公共字段赋值
try {
/**
* entity.getClass() 获取对象,获取entity是那个对象
* getDeclaredMethod(方法名, 参数类型)
*/
// getDeclaredMethod()中则要写对应的方法名以及方法类型
Method setCreateTime = entity.getClass().getDeclaredMethod("setCreateTime", LocalDateTime.class);
Method setCreateUser = entity.getClass().getDeclaredMethod("setCreateUser", Long.class);
Method setUpdateTime = entity.getClass().getDeclaredMethod("setUpdateTime", LocalDateTime.class);
Method setUpdateUser = entity.getClass().getDeclaredMethod("setUpdateUser", Long.class);
/**
* methodName.invoke(对象, 参数)
*/
//通过反射为对象属性赋值
setCreateTime.invoke(entity, now);
setCreateUser.invoke(entity, currentId);
setUpdateTime.invoke(entity, now);
setUpdateUser.invoke(entity, currentId);
} catch (Exception e) {
e.printStackTrace();
}
} else if (operationType == OperationType.UPDATE) {
//为2个公共字段赋值
try {
Method setUpdateTime = entity.getClass().getDeclaredMethod("setUpdateTime", LocalDateTime.class);
Method setUpdateUser = entity.getClass().getDeclaredMethod("setUpdateUser", Long.class);
//通过反射为对象属性赋值
setUpdateTime.invoke(entity, now);
setUpdateUser.invoke(entity, currentId);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}数据库操作类型
public enum OperationType {
/**
* 更新操作
*/
UPDATE,
/**
* 插入操作
*/
INSERT
}自定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoFill {
//数据库操作类型:UPDATE INSERT
OperationType value();
}使用注解,通过AOP切片自动设置时间和用户id
@AutoFill(value = OperationType.INSERT)
void insert(Dish dish);