• [SpringBoot] 多模块统一返回格式带分页信息


    ✨✨个人主页:沫洺的主页

    📚📚系列专栏: 📖 JavaWeb专栏📖 JavaSE专栏 📖 Java基础专栏📖vue3专栏 

                               📖MyBatis专栏📖Spring专栏📖SpringMVC专栏📖SpringBoot专栏

                               📖Docker专栏📖Reids专栏📖MQ专栏📖SpringCloud专栏     

    💖💖如果文章对你有所帮助请留下三连✨✨

    🍸效果 

    在后端向前端返回统一格式data里一般按照需求是要包含分页信息的,比如总条数和按照每页的条数去返回对应的总页数等

    🍹搭建多模块环境(父子项目) 

    项目创建参考SpringBoot专栏里:

    父项目scm-root的pom.xml

    1. <?xml version="1.0" encoding="UTF-8"?>
    2. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    3. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    4. <modelVersion>4.0.0</modelVersion>
    5. <parent>
    6. <groupId>org.springframework.boot</groupId>
    7. <artifactId>spring-boot-starter-parent</artifactId>
    8. <version>2.7.4</version>
    9. <relativePath/>
    10. </parent>
    11. <groupId>com.moming</groupId>
    12. <artifactId>scm-root</artifactId>
    13. <version>14-SNAPSHOT</version>
    14. <packaging>pom</packaging>
    15. <properties>
    16. <java.version>1.8</java.version>
    17. </properties>
    18. <modules>
    19. <module>scm-authority</module>
    20. <module>scm-app</module>
    21. <module>scm-dao</module>
    22. <module>scm-dto</module>
    23. <module>scm-entity</module>
    24. <module>scm-service</module>
    25. <module>scm-api</module>
    26. <module>scm-core</module>
    27. </modules>
    28. <dependencyManagement>
    29. <dependencies>
    30. <dependency>
    31. <groupId>org.mybatis</groupId>
    32. <artifactId>mybatis</artifactId>
    33. <version>3.5.6</version>
    34. <scope>compile</scope>
    35. </dependency>
    36. <dependency>
    37. <groupId>cn.hutool</groupId>
    38. <artifactId>hutool-all</artifactId>
    39. <version>5.8.5</version>
    40. </dependency>
    41. </dependencies>
    42. </dependencyManagement>
    43. <build>
    44. </build>
    45. </project>

    数据库

    首先实现简单的查询业务

    scm-dto模块

    pom.xml依赖

    1. <dependency>
    2. <groupId>org.projectlombok</groupId>
    3. <artifactId>lombok</artifactId>
    4. <optional>true</optional>
    5. </dependency>

    创建UserDto实体类

    1. package com.moming.dto;
    2. import lombok.Data;
    3. @Data
    4. public class UserDto {
    5. private Integer id;
    6. private String username;
    7. private String password;
    8. }

    scm-entity模块

    pom.xml依赖

    1. <dependency>
    2. <groupId>org.projectlombok</groupId>
    3. <artifactId>lombok</artifactId>
    4. <optional>true</optional>
    5. </dependency>

    创建UserEntity实体类

    1. package com.moming.entity;
    2. import lombok.Data;
    3. @Data
    4. public class UserEntity {
    5. private Integer id;
    6. private String username;
    7. private String password;
    8. }

    scm-dao模块

    pom.xml依赖

    1. <dependency>
    2. <groupId>org.mybatis</groupId>
    3. <artifactId>mybatis</artifactId>
    4. <scope>compile</scope>
    5. </dependency>
    6. <dependency>
    7. <groupId>com.moming</groupId>
    8. <artifactId>scm-entity</artifactId>
    9. <version>14-SNAPSHOT</version>
    10. <scope>compile</scope>
    11. </dependency>

    创建UserDao接口

    1. package com.moming.dao;
    2. import com.moming.entity.UserEntity;
    3. import org.apache.ibatis.annotations.Mapper;
    4. import org.apache.ibatis.annotations.Select;
    5. import java.util.List;
    6. @Mapper
    7. public interface UserDao {
    8. @Select("select * from tb_user order by id")
    9. List select();
    10. }

    scm-service模块

    pom.xml依赖

    1. <dependency>
    2. <groupId>org.springframework</groupId>
    3. <artifactId>spring-context</artifactId>
    4. </dependency>
    5. <dependency>
    6. <groupId>cn.hutool</groupId>
    7. <artifactId>hutool-all</artifactId>
    8. </dependency>
    9. <dependency>
    10. <groupId>com.moming</groupId>
    11. <artifactId>scm-dao</artifactId>
    12. <version>14-SNAPSHOT</version>
    13. <scope>compile</scope>
    14. </dependency>
    15. <dependency>
    16. <groupId>com.moming</groupId>
    17. <artifactId>scm-dto</artifactId>
    18. <version>14-SNAPSHOT</version>
    19. <scope>compile</scope>
    20. </dependency>

    创建UserService业务类

    1. package com.moming.service;
    2. import cn.hutool.core.bean.BeanUtil;
    3. import com.moming.dao.UserDao;
    4. import com.moming.dto.UserDto;
    5. import com.moming.entity.UserEntity;
    6. import org.springframework.beans.factory.annotation.Autowired;
    7. import org.springframework.stereotype.Service;
    8. import java.util.List;
    9. @Service
    10. public class UserService {
    11. @Autowired
    12. private UserDao userDao;
    13. public List select(){
    14. List entityList = userDao.select();
    15. List userDtos = BeanUtil.copyToList(entityList, UserDto.class);
    16. return userDtos;
    17. }
    18. }

    scm-app模块

    pom.xml依赖

    1. <dependency>
    2. <groupId>org.springframework.boot</groupId>
    3. <artifactId>spring-boot-starter-web</artifactId>
    4. </dependency>
    5. <dependency>
    6. <groupId>org.springframework.boot</groupId>
    7. <artifactId>spring-boot-starter-test</artifactId>
    8. <scope>test</scope>
    9. </dependency>
    10. <dependency>
    11. <groupId>org.springframework.data</groupId>
    12. <artifactId>spring-data-jdbc</artifactId>
    13. </dependency>
    14. <dependency>
    15. <groupId>mysql</groupId>
    16. <artifactId>mysql-connector-java</artifactId>
    17. <scope>runtime</scope>
    18. </dependency>
    19. <dependency>
    20. <groupId>com.alibaba</groupId>
    21. <artifactId>druid-spring-boot-starter</artifactId>
    22. <version>1.2.9</version>
    23. </dependency>
    24. <dependency>
    25. <groupId>org.mybatis.spring.boot</groupId>
    26. <artifactId>mybatis-spring-boot-starter</artifactId>
    27. <version>2.1.4</version>
    28. </dependency>
    29. <dependency>
    30. <groupId>com.moming</groupId>
    31. <artifactId>scm-service</artifactId>
    32. <version>14-SNAPSHOT</version>
    33. <scope>compile</scope>
    34. </dependency>

    创建controller/UserController

    1. package com.moming.controller;
    2. import com.moming.dto.UserDto;
    3. import com.moming.service.UserService;
    4. import org.springframework.beans.factory.annotation.Autowired;
    5. import org.springframework.web.bind.annotation.GetMapping;
    6. import org.springframework.web.bind.annotation.RequestMapping;
    7. import org.springframework.web.bind.annotation.RestController;
    8. import java.util.List;
    9. @RestController
    10. @RequestMapping("/api/user")
    11. public class UserController {
    12. @Autowired
    13. private UserService userService;
    14. @GetMapping("/select")
    15. public List select(){
    16. return userService.select();
    17. }
    18. }

    创建resources/application.yml

    1. #数据库连接信息配置
    2. spring:
    3. datasource:
    4. driver-class-name: com.mysql.cj.jdbc.Driver
    5. url: jdbc:mysql://localhost:3306/db5?useSSL=false&useServerPrepStmts=true
    6. username: root
    7. password: 123456
    8. druid:
    9. initial-size: 10 # 初始化时建立物理连接的个数
    10. min-idle: 10 # 最小连接池数量
    11. maxActive: 200 # 最大连接池数量
    12. maxWait: 60000 # 获取连接时最大等待时间,单位毫秒
    13. #映射文件所在位置
    14. mybatis:
    15. mapper-locations: classpath:mapper/*Mapper.xml
    16. #别名
    17. type-aliases-package: com.moming.entity
    18. #配置日志级别
    19. logging:
    20. level:
    21. com.moming: debug

    启动类App运行后调用接口测试

    🥂统一返回格式

    scm-dto模块

    创建ResponseDto实体类

    1. package com.moming.dto;
    2. import lombok.Data;
    3. @Data
    4. public class ResponseDto {
    5. private int code;
    6. private String message;
    7. private Object data;
    8. }

    scm-app模块

    创建advice/MyResponseAdvice

    1. package com.moming.advice;
    2. import cn.hutool.core.lang.Dict;
    3. import cn.hutool.json.JSONUtil;
    4. import com.moming.dto.PageInfo;
    5. import com.moming.dto.ResponseDto;
    6. import com.moming.local.PageInfoLocal;
    7. import org.springframework.core.MethodParameter;
    8. import org.springframework.http.MediaType;
    9. import org.springframework.http.converter.HttpMessageConverter;
    10. import org.springframework.http.converter.StringHttpMessageConverter;
    11. import org.springframework.http.server.ServerHttpRequest;
    12. import org.springframework.http.server.ServerHttpResponse;
    13. import org.springframework.web.bind.annotation.ExceptionHandler;
    14. import org.springframework.web.bind.annotation.RestControllerAdvice;
    15. import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
    16. @RestControllerAdvice(basePackages = MyResponseAdvice.BASEPACKAGES)
    17. public class MyResponseAdvice implements ResponseBodyAdvice<Object> {
    18. public static final String BASEPACKAGES="com.moming.controller";
    19. @Override
    20. public boolean supports(MethodParameter returnType, Classextends HttpMessageConverter> converterType) {
    21. return true;
    22. }
    23. @ExceptionHandler
    24. public Object processException(Exception ex){
    25. ResponseDto responseDto = new ResponseDto();
    26. responseDto.setCode(1);
    27. responseDto.setMessage(ex.getMessage());
    28. return responseDto;
    29. }
    30. @Override
    31. public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Classextends HttpMessageConverter> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
    32. ResponseDto responseDto = new ResponseDto();
    33. responseDto.setCode(0);
    34. responseDto.setMessage("");
    35. responseDto.setData(body);
    36. //处理返回类型为字符串
    37. if (selectedConverterType == StringHttpMessageConverter.class) {
    38. //hutool JSONUtil.toJsonStr字符串转换为json数据
    39. return JSONUtil.toJsonStr(responseDto);
    40. } else {
    41. return responseDto;
    42. }
    43. }
    44. }

    接口测试查看效果

    🍻返回带分页信息格式 

    业务需求:

    不改变原有的代码,在返回的data中呈现总页数(pages)和(total)

    分析:

    首先完整的查询业务在上面已经写好了,根据业务需求要在不改变原有的代码的基础之上进行业务增强,就要使用AOP技术来实现,另外要获取总页数,那么在url接口上就需要传递每页多少条的参数(pageSize),既然实现了每页条数,那么同样的就可以传递第几页(pageNum)去动态查询某页的数据,接下来就是怎么去获取url传递的参数,可以通过AOP中的环绕通知,在环绕前使用RequestContextHolder对象去获取ServletRequestAttributes对象,然后通过它去获取HttpServletRequest对象,最后调用getParameter获取参数,获取参数之后使用PageHelper调用startPage方法会将目标方法返回值的类型改为Page类型(底层实现了ArrayList),而Page底层提供了total,pages等属性的get方法,因为属性有很多所以这里需要处理以下,通过我们自定义的PageInfo的set方法获取total,pages的值,然后使用ThreadLocal(本地线程)的机制去实现线程安全问题,ThreadLocal中填充的的是当前线程的变量,该变量对其他线程而言是封闭且隔离的(也就是线程安全的),ThreadLocal为变量在每个线程中创建了一个副本,这样每个线程就可以访问自己内部的副本变量,通过这种机制我们给当前线程脑门上贴一个变量(pageInfo),然后再获取当前线程脑门上的变量(pageInfo),最后移除当前线程脑门上的变量(pageInfo)

    scm-app模块

    pom.xml添加依赖

    AOP坐标依赖

    1. <dependency>
    2. <groupId>org.springframework.boot</groupId>
    3. <artifactId>spring-boot-starter-aop</artifactId>
    4. </dependency>

    第三方pagehelper分页坐标依赖

    1. <dependency>
    2. <groupId>com.github.pagehelper</groupId>
    3. <artifactId>pagehelper-spring-boot-starter</artifactId>
    4. <version>1.4.5</version>
    5. </dependency>

    相关模块导入依赖

    1. <dependency>
    2. <groupId>com.moming</groupId>
    3. <artifactId>scm-core</artifactId>
    4. <version>14-SNAPSHOT</version>
    5. <scope>compile</scope>
    6. </dependency>

    创建local/PageInfoLocal

    1. package com.moming.local;
    2. import com.moming.dto.PageInfo;
    3. public class PageInfoLocal {
    4. private static ThreadLocal threadLocal = new InheritableThreadLocal<>();
    5. //给当前线程头上贴一个变量 pageinfo
    6. public static void set(PageInfo pageInfo){
    7. threadLocal.set(pageInfo);
    8. }
    9. //获取当前线程头上的变量 pageinfo
    10. public static PageInfo get(){
    11. return threadLocal.get();
    12. }
    13. //移除当前线程头上的变量 pageinfo
    14. public static void remove(){
    15. threadLocal.remove();
    16. }
    17. }

    创建aop/DaoAop

    1. package com.moming.aop;
    2. import cn.hutool.core.util.ObjectUtil;
    3. import com.github.pagehelper.Page;
    4. import com.github.pagehelper.PageHelper;
    5. import com.moming.dto.PageInfo;
    6. import com.moming.local.PageInfoLocal;
    7. import org.aspectj.lang.ProceedingJoinPoint;
    8. import org.aspectj.lang.annotation.Around;
    9. import org.aspectj.lang.annotation.Aspect;
    10. import org.aspectj.lang.annotation.Pointcut;
    11. import org.springframework.stereotype.Component;
    12. import org.springframework.web.context.request.RequestContextHolder;
    13. import org.springframework.web.context.request.ServletRequestAttributes;
    14. import javax.servlet.http.HttpServletRequest;
    15. @Component
    16. @Aspect
    17. public class DaoAop {
    18. //通过注解方式定义切入点
    19. @Pointcut("@annotation(com.moming.annotation.PageCut)")
    20. public void point(){}
    21. //环绕通知
    22. @Around("point()")
    23. public Object around(ProceedingJoinPoint pjp) throws Throwable {
    24. //获取url传递的参数
    25. ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
    26. HttpServletRequest request = attributes.getRequest();
    27. String pageNum = request.getParameter("pageNum");
    28. String pageSize = request.getParameter("pageSize");
    29. //如果只传递pageSize,则默认pageNum为1
    30. if( ObjectUtil.isNotEmpty(pageSize)){
    31. int page_num =1;
    32. if(ObjectUtil.isNotEmpty(pageNum)){
    33. page_num = Integer.valueOf(pageNum);
    34. }
    35. int page_size = Integer.valueOf(pageSize);
    36. PageHelper.startPage(page_num,page_size);
    37. }
    38. //目标方法List<UserEntity> select()---
    39. Object result = pjp.proceed();
    40. //如果目标方法的返回值类型为Page,则将pageInfo对象贴在当前线程脑门上
    41. if(result instanceof Page){
    42. Page page = (Page) result;
    43. //使用自定义的PageInfo对象获取需要的分页信息
    44. PageInfo pageInfo = new PageInfo();
    45. pageInfo.setPages(page.getPages());
    46. pageInfo.setTotal(page.getTotal());
    47. PageInfoLocal.set(pageInfo);
    48. }
    49. return result;
    50. }
    51. }

    在通知类里对分页信息进行封装

    1. //分页组件扩展开始
    2. PageInfo pageInfo = PageInfoLocal.get();
    3. try{
    4. if(pageInfo!=null){
    5. Dict dict = Dict.create()
    6. .set("pages",pageInfo.getPages())
    7. .set("total", pageInfo.getTotal())
    8. .set("items",body);
    9. responseDto.setData(dict);
    10. }
    11. else {
    12. responseDto.setData(body);
    13. }
    14. //分页组件扩展结束
    15. }finally {
    16. //移除当前线程头上的变量标签
    17. if(pageInfo!=null){
    18. PageInfoLocal.remove();
    19. }
    20. }

    advice/MyResponseAdvice

    1. package com.moming.advice;
    2. import cn.hutool.core.lang.Dict;
    3. import cn.hutool.json.JSONUtil;
    4. import com.moming.dto.PageInfo;
    5. import com.moming.dto.ResponseDto;
    6. import com.moming.local.PageInfoLocal;
    7. import org.springframework.core.MethodParameter;
    8. import org.springframework.http.MediaType;
    9. import org.springframework.http.converter.HttpMessageConverter;
    10. import org.springframework.http.converter.StringHttpMessageConverter;
    11. import org.springframework.http.server.ServerHttpRequest;
    12. import org.springframework.http.server.ServerHttpResponse;
    13. import org.springframework.web.bind.annotation.ExceptionHandler;
    14. import org.springframework.web.bind.annotation.RestControllerAdvice;
    15. import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
    16. @RestControllerAdvice(basePackages = MyResponseAdvice.BASEPACKAGES)
    17. public class MyResponseAdvice implements ResponseBodyAdvice {
    18. public static final String BASEPACKAGES="com.moming.controller";
    19. @Override
    20. public boolean supports(MethodParameter returnType, Class> converterType) {
    21. return true;
    22. }
    23. @ExceptionHandler
    24. public Object processException(Exception ex){
    25. ResponseDto responseDto = new ResponseDto();
    26. responseDto.setCode(1);
    27. responseDto.setMessage(ex.getMessage());
    28. return responseDto;
    29. }
    30. @Override
    31. public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
    32. ResponseDto responseDto = new ResponseDto();
    33. responseDto.setCode(0);
    34. responseDto.setMessage("");
    35. //分页组件扩展开始
    36. PageInfo pageInfo = PageInfoLocal.get();
    37. try{
    38. if(pageInfo!=null){
    39. Dict dict = Dict.create()
    40. .set("pages",pageInfo.getPages())
    41. .set("total", pageInfo.getTotal())
    42. .set("items",body);
    43. responseDto.setData(dict);
    44. }
    45. else {
    46. responseDto.setData(body);
    47. }
    48. //分页组件扩展结束
    49. }finally {
    50. //移除当前线程头上的变量标签
    51. if(pageInfo!=null){
    52. PageInfoLocal.remove();
    53. }
    54. }
    55. //responseDto.setData(body);
    56. //处理返回类型为字符串
    57. if (selectedConverterType == StringHttpMessageConverter.class) {
    58. //hutool JSONUtil.toJsonStr字符串转换为json数据
    59. return JSONUtil.toJsonStr(responseDto);
    60. } else {
    61. return responseDto;
    62. }
    63. }
    64. }
    65. scm-core模块

      pom.xml依赖

      1. <dependency>
      2. <groupId>org.projectlombok</groupId>
      3. <artifactId>lombok</artifactId>
      4. <optional>true</optional>
      5. </dependency>

      创建annotation/PageCut注解

      1. package com.moming.annotation;
      2. import java.lang.annotation.*;
      3. @Documented
      4. @Retention(RetentionPolicy.RUNTIME)
      5. @Target(ElementType.METHOD)
      6. public @interface PageCut {
      7. }

      创建dto/PageInfo实体类

      1. package com.moming.dto;
      2. import lombok.Data;
      3. @Data
      4. public class PageInfo {
      5. private int pages;
      6. private long total;
      7. }

      scm-dao模块

      目标方法添加切入点注解

      1. package com.moming.dao;
      2. import com.moming.annotation.PageCut;
      3. import com.moming.entity.UserEntity;
      4. import org.apache.ibatis.annotations.Mapper;
      5. import org.apache.ibatis.annotations.Select;
      6. import java.util.List;
      7. @Mapper
      8. public interface UserDao {
      9. @PageCut
      10. @Select("select * from tb_user order by id")
      11. List select();
      12. }

      测试

    66. 相关阅读:
      某OA系统审计小记
      PostgreSQL 免费的对象-关系数据库
      华为OD机试 - 多段线数据压缩(Java 2024 D卷 100分)
      PG::Ha-natraj
      JavaWeb之组件Servlet详解
      2022年,都在说软件测试饱和了,大环境不好?为何每年还会增加40万测试员?
      【算法训练-搜索算法 一】【DFS网格搜索框架】岛屿数量、岛屿的最大面积、岛屿的周长
      【ML】基于机器学习的糖尿病预测(回归问题)
      [NOI2020统一省选 A] 组合数问题 (推式子)
      程序员都看不懂的代码
    67. 原文地址:https://blog.csdn.net/HeyVIrBbox/article/details/127568799