背景
笔者最近接到一个需求,需要将系统中所有的列表(分页接口)增加一个排序的功能,系统是基于MybatisPlus实现的,分页方面并没有太多问题,但是对于排序的字段会有明显的几个问题如下:
- 列表中部分字段是字典转换的,与原值字段不一致
比如状态StatusDesc
为中文禁用
,真实排序字段为Status
- 多表关联查询,必须指定字段的表来源,否则查询会报错
select t1.*
from student t1
join class t2 on t1.class_id=t2.id
order name
name
字段必须指定是学生表还是班级表的,不然无法进行查询
温馨提示:代码中大量使用了Hutool
工具类,请替换为自己项目中的工具类,抱歉:)
解法
笔者的解法有几个前提,先放出来,免得大家直接复制代码发现无法使用,但是整体的思路是一致的,就算情况不一致,也可以借鉴修改,实现相关功能
前提
- 列表分页都是
GET
请求,参数由URLParams
传递 - 生成
MybatisPlus
分页信息Page
有统一入口,方便管理
思路
按照以上前提,很简单就有一个思路产生,直接修改URLParams
不就可以了吗?是的,但是SpringMVC框架的原生的Request
对象,有一个特点,只能被读一次,且无法修改。
这个问题引发了进一步的思考,有没有方式解决这个问题呢,看一些文章,很自然的发现相关类HttpServletRequestWrapper
,继承该类可以通过组合
的方式,对Request
进行增强
其实这个增强很简单,即在调用getParameter(String name)
之前,先调用一个前置的Map
中的值,如果Map
为空,或者取值为空,就继续调用getParameter(String name)
完成原有逻辑,将核心代码给出如下:
public class SortRewriteBodyWrapper extends HttpServletRequestWrapper {
private Map params;
public SortRewriteBodyWrapper(HttpServletRequest request) {
super(request);
}
@Override
public String getParameter(String name) {
String result;
if (CollUtil.isEmpty(params)) {
return super.getParameter(name);
}
Object v = params.get(name);
if (v != null) {
String[] strArr = (String[]) v;
if (strArr.length > 0) {
result = strArr[0];
} else {
result = null;
}
return result;
}
return super.getParameter(name);
}
public void addParam(String key, String value) {
if (CollUtil.isEmpty(params)) {
params = new HashMap();
}
params.put(key, new String[]{value});
}
}
集成
有了核心实现以后,就是要将该Wrapper
集成进入系统之中,笔者是通过自定义Filter
的方式集成的,主要是因为:
- 集成简单,方便
- 可以通过
Filter Flag
控制影响范围 - 可以通过设置
Filter
的顺序不影响其他功能
Filter部分
public class SortRewriteParamFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
filterChain.doFilter(new SortRewriteBodyWrapper(request), response);
}
@Override
protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
String pageNum = ServletUtils.getParameter("pageNum");
return StrUtil.isEmpty(pageNum);
}
}
至于Filter集成到MvcConfig
的部分这里笔者就略过了,不同版本的SpringBoot
的配置不尽相同,大家去搜索一下,就能找到
自定义注解
这里解释一下,为什么会有自定义注解,因为每一个列表的字段都不相同,同时可能在返回给前端的过程中,还进行了向Vo
或者Domain
的转换,需要通过注解进行灵活动态的控制,方便配置排序的别名
SortRewrite
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SortRewrite {
Class rewriter();
String defaultSort() default "createTime";
}
用途: Controller,标识列表中的排序值的转换列表
属性说明:
- rewriter() Vo的类
- defaultSort() 默认排序的字段
示例:
@GetMapping("/list")
@SortRewrite(rewriter = Student.class)
public Page list() {
return null;
}
SortAlias
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SortAlias {
String value();
}
用途: Vo字段注解,用于标识排序的别名
属性说明:
- value() 别名的名称
- defaultSort() 默认排序的字段
示例:
public class Student {
@SortAlias("t1.name")
private String name;
}
切面
@Order(0)
@Aspect
@Component
@Slf4j
public class SortRewriteAspect {
@Pointcut("@annotation(SortRewrite)")
public void pointCut() {
}
@Before("pointCut()")
public void beforePage(JoinPoint joinPoint) {
HttpServletRequest request = ((ServletRequestAttributes)
Objects.requireNonNull(RequestContextHolder.getRequestAttributes()))
.getRequest();
String orderCol = request.getParameter("orderByColumn");
boolean isWrapper = request instanceof HttpServletRequestWrapper;
//循环获取最原始的request,直到找到SortRewriteBodyWrapper
while (isWrapper) {
if (request instanceof SortRewriteBodyWrapper) {
break;
}
request = (HttpServletRequest) ((HttpServletRequestWrapper) request).getRequest();
isWrapper = request instanceof HttpServletRequestWrapper;
}
if (request instanceof SortRewriteBodyWrapper) {
SortRewriteBodyWrapper reqWrap = (SortRewriteBodyWrapper) request;
// 如果有排序字段,则进行重写
// 获取重写类
SortRewrite sortWrite = getSortWrite(joinPoint);
if (StrUtil.isNotEmpty(orderCol)) {
Class rewriter = sortWrite.rewriter();
// 重写排序字段
Field orderField = ReflectUtil.getField(rewriter, orderCol);
if (orderField != null) {
SortAlias sortAlias = orderField.getAnnotation(SortAlias.class);
// 如果有别名,则进行重写
if (sortAlias != null) {
String alias = sortAlias.value();
if (StrUtil.isNotEmpty(alias)) {
// 重写排序字段
reqWrap.addParam("orderByColumn", alias);
}
}
}
} else {
String sort = sortWrite.defaultSort();
reqWrap.addParam("orderByColumn", sort);
reqWrap.addParam("isAsc", "desc");
}
}
}
private static SortRewrite getSortWrite(JoinPoint joinPoint) {
try {
Class targetCls = joinPoint.getTarget().getClass();
MethodSignature ms = (MethodSignature) joinPoint.getSignature();
Method targetMethod = targetCls.getDeclaredMethod(ms.getName(), ms.getParameterTypes());
SortRewrite sortRewrite = targetMethod.getAnnotation(SortRewrite.class);
return sortRewrite;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
切面逻辑概括:
- 循环直到获取到
SortRewriteBodyWrapper
类型的Request
- 反射获取
SortRewrite
注解中的类 - 反射获取
SortAlias
中的别名 - 将真实的排序字段加到前置的
Map
中 - 如果不存在排序字段,设置默认字段
缺陷
这个版本的代码是笔者初步的一个设计,肯定会有一些疏漏和欠考虑的地方,比如如果Vo
被复用了且两个接口的排序字段不同怎么处理等等,如果大家关于代码和缺陷有问题和想法,欢迎在评论区留言,最后,感谢浏览,感恩