1、前言:
项目开发中经常会用到许多分页处理,特别是做管理后台开发,大多数情况下都会去根据请求参数按照指定格式进行分页,返回时,也还需对返回数据进行类型转换,这一系列下来特别麻烦,所以突发奇想,写一个分页工具,可以满足基本的分页需求
2、代码:
所需依赖:
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.2</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.4.M1</version>
</dependency>
这边就直接贴代码了,总共分为三个部分
- 定义分页实体类
- 解析分页实体类
- 响应实体类
2.1、分页实体类
代码:
package com.migu.migumall.common.request.requestpage.v2;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.migu.migumall.utils.SqlHandlerUtils;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import java.util.Map;
import java.util.stream.Collectors;
/**
* 请求分页对象
*
* @param <T> 数据库实体类型
* @param <R> 请求分页对象
* @author kkmigu 664325656@qq.com
*/
@Data
@ApiModel("请求分页实例")
@Slf4j
public class RequestPageInstance<T, R> {
/**
* 当前请求页码
*/
@ApiModelProperty("当前页码")
private Long current;
/**
* 当前请求数量
*/
@ApiModelProperty("请求数据数量")
private Long size;
/**
* 数据库映射的实体类型
*/
@ApiModelProperty("实际分页类型")
private Class<T> destClass;
/**
* 当前请求数据
*/
@ApiModelProperty("请求对象")
private R reqInstance;
public RequestPageInstance() {
this.current = 1L;
this.size = 10L;
}
public RequestPageInstance(Long current) {
this.current = current;
this.size = 10L;
}
public RequestPageInstance(Long current, Long size) {
this.current = current;
this.size = size;
}
public RequestPageInstance(Long current, Long size, R reqInstance) {
this.current = current;
this.size = size;
this.reqInstance = reqInstance;
}
public WrapperConstructor wrapperConstructor() {
return new WrapperConstructor();
}
/**
* 普通分页查询
*
* @param service
* @return
*/
public IPage<T> startPage(IService<T> service) {
return service.page(new Page<>(current, size));
}
/**
* 支持给定返回类型 的分页查询
*
* @param service 分页服务对象
* @param p 响应对象
* @param <P> 返回类型
* @return
* @throws IllegalAccessException
*/
public <P> IPage<P> startPage(IService<T> service, Class<P> p) throws IllegalAccessException {
return copyPageAndRsp(p, startPage(service));
}
/**
* 根据 reqInstance实例属性值 进行分页查询
*
* @param service
* @return
*/
public IPage<T> startPageByParam(IService<T> service) {
return service.page(new Page<>(current, size),wrapperConstructor().defaultWrapper());
}
/**
* 支持指定类型返回进行转换
*
* @param service 分页服务对象
* @param p 响应对象
* @param <P> 返回类型
* @return
* @throws IllegalAccessException
*/
public <P> IPage<P> startPageByParam(IService<T> service, Class<P> p) throws IllegalAccessException {
return copyPageAndRsp(p, startPageByParam(service));
}
/**
* 基于reqInstance实例属性值并可结合可扩展的wrapper构造使用
*
* @param service 分页服务对象
* @return
* @throws IllegalAccessException
*/
private IPage<T> startPageByParam(IService<T> service, LambdaQueryWrapper<T> wrapper) throws IllegalAccessException {
return service.page(new Page<>(current, size),wrapper);
}
/**
* 结合可扩展的wrapper构造使用
*
* @param service 分页服务对象
* @param p 响应对象
* @param <P> 返回类型
* @return
* @throws IllegalAccessException
*/
private <P> IPage<P> startPageByParam(IService<T> service, Class<P> p, LambdaQueryWrapper<T> wrapper) throws IllegalAccessException {
return copyPageAndRsp(p, startPageByParam(service,wrapper));
}
/**
* 返回指定类型的分页处理
*
* @param p 指定类型
* @param page 分页对象
* @return
*/
private <P> Page<P> copyPageAndRsp(Class<P> p, IPage<T> page) {
Page<P> rPage = new Page<>();
BeanUtils.copyProperties(page, rPage);
if (p != null) {
rPage.setRecords(page.getRecords().stream().map(item -> JSONUtil.toBean(JSONUtil.toJsonStr(item), p)).collect(Collectors.toList()));
}
return rPage;
}
/**
* 扩展分页查询逻辑
*/
public class WrapperConstructor {
private LambdaQueryWrapper<T> wrapper = defaultWrapper();
@SneakyThrows
public IPage<T> startPage(IService<T> service) {
return RequestPageInstance.this.startPageByParam(service,wrapper);
}
@SneakyThrows
public <P> IPage<P> startPage(IService<T> service, Class<P> p) {
return RequestPageInstance.this.startPageByParam(service,p,wrapper);
}
/**
* 默认分页参数
*
* @return
*/
@SneakyThrows
private LambdaQueryWrapper<T> defaultWrapper() {
return parseParam().lambda();
}
/**
* 扩展方法 排序
* @param columns
* @return
*/
public WrapperConstructor orderByDesc(SFunction<T, ?> columns) {
this.wrapper = wrapper.orderByDesc(columns);
return this;
}
public WrapperConstructor orderByAsc(SFunction<T, ?> columns) {
this.wrapper = wrapper.orderByAsc(columns);
return this;
}
// 后续可以再次添加其他分页操作,建议不要太复杂,否则推荐使用原生分页sql
/**
* 根据实例 解析 wrapper参数
*
* @return
* @throws IllegalAccessException
*/
private QueryWrapper<T> parseParam() throws IllegalAccessException {
QueryWrapper<T> wrapper = new QueryWrapper<>();
Map<String, Object> map = SqlHandlerUtils.getInstanceKeyValue(RequestPageInstance.this.reqInstance);
for (Map.Entry<String, Object> entry : map.entrySet()) {
String key = entry.getKey();
Object value = entry.getValue();
if (value instanceof CharSequence) {
wrapper = NumberUtil.isNumber((CharSequence) value) ? wrapper.eq(key, value) : wrapper.like(key, value);
} else if (value instanceof Number) {
wrapper = wrapper.eq(key, value);
} else {
wrapper = wrapper.like(key, value);
}
}
return wrapper;
}
}
public void setCurrent(Long current) {
if (current == null || current == 0) {
this.current = 1L;
return;
}
this.current = current;
}
public void setSize(Long size) {
if (size == null || size == 0) {
this.size = 10L;
return;
}
this.size = size;
}
}
2.2、解析分页实体工具类
代码:
package com.migu.migumall.utils;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.annotation.TableField;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.HashMap;
/**
* 数据库处理相关工具类
*
* @author kkmigu 664325656@qq.com
*/
public class SqlHandlerUtils {
/**
* 获取所有key:
* 配置所有key: k: 默认由 实例的字段 转为驼峰命名方式 / @TableField 指定 --->过滤 @TableField(exist = false) | v is null
* @param instance
* @return
* @throws IllegalAccessException
*/
public static <T> HashMap<String, Object> getInstanceKeyValue(T instance) throws IllegalAccessException {
// 存储实例中进行校验的 k v
HashMap<String, Object> map = new HashMap<>();
if (instance != null) {
Field[] instanceFields = instance.getClass().getDeclaredFields();
for (Field field : instanceFields) {
// static | final 不操作
if (Modifier.isFinal(field.getModifiers()) || Modifier.isStatic(field.getModifiers())) {
continue;
}
field.setAccessible(true);
Object o = field.get(instance);
if (o == null) {
continue;
}
boolean b = field.isAnnotationPresent(TableField.class);
// 如果存在该注解
if (b) {
TableField annotationInstance = field.getAnnotation(TableField.class);
boolean isExist = annotationInstance.exist();
if (!isExist) {
continue;
}
String value = annotationInstance.value();
if (StrUtil.isNotBlank(value)) {
map.put(value, o);
continue;
}
}
// 执行驼峰转下划线
map.put(StrUtil.toUnderlineCase(field.getName()), o);
}
}
return map;
}
}
2.3、返回实体类
返回实体接口代码:
package com.migu.migumall.common.result;
public interface Result<T> {
}
基本分页实现类:
package com.migu.migumall.common.result;
import com.baomidou.mybatisplus.core.metadata.IPage;
import lombok.Data;
import java.util.List;
@Data
public class CommonPageResult<T> implements Result<T>{
private long code = 200;
private String message = "success";
/**
* 总量
*/
private long total;
/**
* 当前所在页码
*/
private long current;
/**
* 页码数量
*/
private long pageSize;
/**
* 数据集合
*/
protected List<T> data;
public CommonPageResult (IPage<T> page) {
this.total = page.getTotal();
this.data = page.getRecords();
this.current = page.getCurrent();
this.pageSize = page.getPages();
}
public static <T> CommonPageResult<T> success(IPage<T> page){
return new CommonPageResult<T>(page);
}
}
3、使用
第一个例子:根据请求参数进行分页
@ApiOperation("获取所有商品列表")
@PostMapping("list")
@PreAuthorize("hasAuthority('pms:product:read')")
public Result<PmsProduct> getList(@RequestBody RequestPageInstance<PmsProduct,PmsProductReq> pageInstance) throws IllegalAccessException {
return CommonPageResult.success(pageInstance.startPageByParam(pmsProductService));
}
第二个例子:对于用户数据,密码等隐私信息我们不能返回,那么可指定返回类型进行分页
@ApiOperation("获取所有用户")
@PostMapping("list")
@PreAuthorize("hasAnyAuthority('ums:user:read')")
public Result<AdminParamRsp> getList(@RequestBody RequestPageInstance<UmsAdmin, AdminQueryParam> pageInstance) throws IllegalAccessException {
return CommonPageResult.success(pageInstance.startPageByParam(umsAdminService, AdminParamRsp.class));
}
第三个例子:有时,我们需要查找最新订单,那么就可额外根据wrapper进行分页
@ApiOperation("获取所有订单")
@PostMapping("list")
@PreAuthorize("hasAuthority('oms:order:read')")
public Result<OmsOrder> getList(@RequestBody RequestPageInstance<OmsOrder,OmsOrderReq> pageInstance) throws IllegalAccessException {
return CommonPageResult.success(
pageInstance.wrapperConstructor().orderByDesc(OmsOrder::getCreateTime).startPage(omsOrderService)
);
}
4、总结
该方案对于个人而言使用起来非常方便,最近一直在写CRUD,受不了了,就写了这一套工具出来
同时该实现也结合到作者本人正在学习的一个项目中,地址:migu-mall: 依据 https://github.com/macrozheng/mall-swarm 教程 学习搭建 (gitee.com)
在这之中也封装了一些基本的CRUD实现