[SpringBoot] 多模块统一返回格式带分页信息
创始人
2024-01-17 19:28:38

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

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

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

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

🍸效果 

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

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

项目创建参考SpringBoot专栏里:

父项目scm-root的pom.xml


4.0.0org.springframework.bootspring-boot-starter-parent2.7.4com.momingscm-root14-SNAPSHOTpom1.8scm-authorityscm-appscm-daoscm-dtoscm-entityscm-servicescm-apiscm-coreorg.mybatismybatis3.5.6compilecn.hutoolhutool-all5.8.5

数据库

首先实现简单的查询业务

scm-dto模块

pom.xml依赖

        org.projectlomboklomboktrue

创建UserDto实体类

package com.moming.dto;import lombok.Data;@Data
public class UserDto {private Integer id;private String username;private String password;
}

scm-entity模块

pom.xml依赖

        org.projectlomboklomboktrue

创建UserEntity实体类

package com.moming.entity;import lombok.Data;@Data
public class UserEntity {private Integer id;private String username;private String password;
}

scm-dao模块

pom.xml依赖

        org.mybatismybatiscompilecom.momingscm-entity14-SNAPSHOTcompile

创建UserDao接口

package com.moming.dao;import com.moming.entity.UserEntity;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;import java.util.List;@Mapper
public interface UserDao {@Select("select * from tb_user order by id")List select();}

scm-service模块

pom.xml依赖

        org.springframeworkspring-contextcn.hutoolhutool-allcom.momingscm-dao14-SNAPSHOTcompilecom.momingscm-dto14-SNAPSHOTcompile

创建UserService业务类

package com.moming.service;import cn.hutool.core.bean.BeanUtil;
import com.moming.dao.UserDao;
import com.moming.dto.UserDto;
import com.moming.entity.UserEntity;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;import java.util.List;@Service
public class UserService {@Autowiredprivate UserDao userDao;public List select(){List entityList = userDao.select();List userDtos = BeanUtil.copyToList(entityList, UserDto.class);return userDtos;}
}

scm-app模块

pom.xml依赖

        org.springframework.bootspring-boot-starter-weborg.springframework.bootspring-boot-starter-testtestorg.springframework.dataspring-data-jdbcmysqlmysql-connector-javaruntimecom.alibabadruid-spring-boot-starter1.2.9org.mybatis.spring.bootmybatis-spring-boot-starter2.1.4com.momingscm-service14-SNAPSHOTcompile

创建controller/UserController

package com.moming.controller;import com.moming.dto.UserDto;
import com.moming.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import java.util.List;@RestController
@RequestMapping("/api/user")
public class UserController {@Autowiredprivate UserService userService;@GetMapping("/select")public List select(){return userService.select();}
}

创建resources/application.yml

#数据库连接信息配置
spring:datasource:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/db5?useSSL=false&useServerPrepStmts=trueusername: rootpassword: 123456druid:initial-size: 10 # 初始化时建立物理连接的个数min-idle: 10 # 最小连接池数量maxActive: 200 # 最大连接池数量maxWait: 60000 # 获取连接时最大等待时间,单位毫秒
#映射文件所在位置
mybatis:mapper-locations: classpath:mapper/*Mapper.xml#别名type-aliases-package: com.moming.entity#配置日志级别
logging:level:com.moming: debug

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

🥂统一返回格式

scm-dto模块

创建ResponseDto实体类

package com.moming.dto;import lombok.Data;@Data
public class ResponseDto {private int code;private String message;private Object data;
}

scm-app模块

创建advice/MyResponseAdvice

package com.moming.advice;import cn.hutool.core.lang.Dict;
import cn.hutool.json.JSONUtil;
import com.moming.dto.PageInfo;
import com.moming.dto.ResponseDto;
import com.moming.local.PageInfoLocal;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;@RestControllerAdvice(basePackages = MyResponseAdvice.BASEPACKAGES)
public class MyResponseAdvice implements ResponseBodyAdvice {public static final String BASEPACKAGES="com.moming.controller";@Overridepublic boolean supports(MethodParameter returnType, Class> converterType) {return true;}@ExceptionHandlerpublic  Object processException(Exception ex){ResponseDto responseDto = new ResponseDto();responseDto.setCode(1);responseDto.setMessage(ex.getMessage());return responseDto;}@Overridepublic Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {ResponseDto responseDto = new ResponseDto();responseDto.setCode(0);responseDto.setMessage("");responseDto.setData(body);//处理返回类型为字符串if (selectedConverterType == StringHttpMessageConverter.class) {//hutool JSONUtil.toJsonStr字符串转换为json数据return JSONUtil.toJsonStr(responseDto);} else {return responseDto;}}
}
 

接口测试查看效果

🍻返回带分页信息格式 

业务需求:

不改变原有的代码,在返回的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坐标依赖

        org.springframework.bootspring-boot-starter-aop

第三方pagehelper分页坐标依赖

        com.github.pagehelperpagehelper-spring-boot-starter1.4.5

相关模块导入依赖

        com.momingscm-core14-SNAPSHOTcompile

创建local/PageInfoLocal

package com.moming.local;import com.moming.dto.PageInfo;public class PageInfoLocal {private static ThreadLocal threadLocal = new InheritableThreadLocal<>();//给当前线程头上贴一个变量 pageinfopublic static void set(PageInfo pageInfo){threadLocal.set(pageInfo);}//获取当前线程头上的变量 pageinfopublic static PageInfo get(){return threadLocal.get();}//移除当前线程头上的变量 pageinfopublic static void remove(){threadLocal.remove();}
}

创建aop/DaoAop

package com.moming.aop;import cn.hutool.core.util.ObjectUtil;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.moming.dto.PageInfo;
import com.moming.local.PageInfoLocal;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;import javax.servlet.http.HttpServletRequest;@Component
@Aspect
public class DaoAop {//通过注解方式定义切入点@Pointcut("@annotation(com.moming.annotation.PageCut)")public void point(){}//环绕通知@Around("point()")public Object around(ProceedingJoinPoint pjp) throws Throwable {//获取url传递的参数ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();HttpServletRequest request = attributes.getRequest();String pageNum = request.getParameter("pageNum");String pageSize = request.getParameter("pageSize");//如果只传递pageSize,则默认pageNum为1if( ObjectUtil.isNotEmpty(pageSize)){int page_num =1;if(ObjectUtil.isNotEmpty(pageNum)){page_num = Integer.valueOf(pageNum);}int page_size = Integer.valueOf(pageSize);PageHelper.startPage(page_num,page_size);}//目标方法List select()---Object result = pjp.proceed();//如果目标方法的返回值类型为Page,则将pageInfo对象贴在当前线程脑门上if(result instanceof Page){Page page = (Page) result;//使用自定义的PageInfo对象获取需要的分页信息PageInfo pageInfo = new PageInfo();pageInfo.setPages(page.getPages());pageInfo.setTotal(page.getTotal());PageInfoLocal.set(pageInfo);}return result;}
}

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

        //分页组件扩展开始PageInfo pageInfo = PageInfoLocal.get();try{if(pageInfo!=null){Dict dict = Dict.create().set("pages",pageInfo.getPages()).set("total", pageInfo.getTotal()).set("items",body);responseDto.setData(dict);}else {responseDto.setData(body);}//分页组件扩展结束}finally {//移除当前线程头上的变量标签if(pageInfo!=null){PageInfoLocal.remove();}}

advice/MyResponseAdvice

package com.moming.advice;import cn.hutool.core.lang.Dict;
import cn.hutool.json.JSONUtil;
import com.moming.dto.PageInfo;
import com.moming.dto.ResponseDto;
import com.moming.local.PageInfoLocal;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;@RestControllerAdvice(basePackages = MyResponseAdvice.BASEPACKAGES)
public class MyResponseAdvice implements ResponseBodyAdvice {public static final String BASEPACKAGES="com.moming.controller";@Overridepublic boolean supports(MethodParameter returnType, Class> converterType) {return true;}@ExceptionHandlerpublic  Object processException(Exception ex){ResponseDto responseDto = new ResponseDto();responseDto.setCode(1);responseDto.setMessage(ex.getMessage());return responseDto;}@Overridepublic Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {ResponseDto responseDto = new ResponseDto();responseDto.setCode(0);responseDto.setMessage("");//分页组件扩展开始PageInfo pageInfo = PageInfoLocal.get();try{if(pageInfo!=null){Dict dict = Dict.create().set("pages",pageInfo.getPages()).set("total", pageInfo.getTotal()).set("items",body);responseDto.setData(dict);}else {responseDto.setData(body);}//分页组件扩展结束}finally {//移除当前线程头上的变量标签if(pageInfo!=null){PageInfoLocal.remove();}}//responseDto.setData(body);//处理返回类型为字符串if (selectedConverterType == StringHttpMessageConverter.class) {//hutool JSONUtil.toJsonStr字符串转换为json数据return JSONUtil.toJsonStr(responseDto);} else {return responseDto;}}
}
 

scm-core模块

pom.xml依赖

        org.projectlomboklomboktrue

创建annotation/PageCut注解

package com.moming.annotation;import java.lang.annotation.*;@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface PageCut {
}

创建dto/PageInfo实体类

package com.moming.dto;import lombok.Data;@Data
public class PageInfo {private int pages;private long total;
}

scm-dao模块

目标方法添加切入点注解

package com.moming.dao;import com.moming.annotation.PageCut;
import com.moming.entity.UserEntity;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;import java.util.List;@Mapper
public interface UserDao {@PageCut@Select("select * from tb_user order by id")List select();}

测试

相关内容

热门资讯

埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
怎么样消灭臭虫 臭虫用什么药消... 奔奔虫控|被吸血臭虫骚扰的滋味,你体会过吗?相信被臭虫骚扰过的人都不想再体验一次了吧?那么臭虫应该怎...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
苁蓉的食用方法 鲜苁蓉的吃法有... 肉苁蓉号称“温而不热,补而不峻,暖而不燥,滑而不泄,故有从容之名。”历来也是传统药食同源的滋补佳品。...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...