需求说明
为了保证系统的安全性,需要对所有的 查询列表 接口,添加分页参数,并对分页参数进行校验,
 ,保证参数的合法性。
比如, pageSize(每页显示条数),如果不做校验,一旦传递过来一个很大的数值,比如,十万亿,数据库可能会直接卡住,或者应用服务器的内存可能也被挤爆。
分页参数与校验逻辑
| 分页参数 | 校验逻辑 | 
|---|---|
| currentPage | 当前页,应大于等于1 | 
| pageSize | 每页显示条数,取值范围为[1, 100] | 
currentPage 和 pageSize,都应该是整数,如果传入的是小数、超出范围的数字、或者非数字,也应该直接报错;SpringMVC 已经自动支持这部分校验,不需要我们再去额外处理。
解决方案
使用 AOP(面向切面编程),在所有接口前,检查分页参数;如果不合法,直接返回接口调用失败,并将错误原因返回。
返回接口调用失败,采用的方法是抛出业务异常,然后,由异常统一处理模块,将错误原因封装到返回结果中。
代码
参数校验切面
package com.example.core.advice;
import com.example.core.model.BusinessException;
import com.example.core.model.ErrorEnum;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
/**
 * 分页参数校验
 */
@Aspect
@Order(10)
@Component
public class PageValidator {
    private static final String CURRENT_PAGE = "currentPage";
    private static final String PAGE_SIZE = "pageSize";
    // 拦截 com.example.web 包及其子包下的所有类的@RequestMapping注解修饰的方法
    @Pointcut("execution(* com.example.web..*.*(..)) and @annotation(org.springframework.web.bind.annotation.RequestMapping)")
    private void pointcut() {
    }
    // Before表示 advice() 将在目标方法执行前执行
    @Before("pointcut()")
    public void advice(JoinPoint joinPoint) {
        // 获取请求信息
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (attributes == null) {
            return;
        }
        HttpServletRequest request = attributes.getRequest();
        // 校验: [当前页]
        validateCurrentPage(request);
        // 校验: [每页显示条数]
        validatePageSize(request);
    }
    /**
     * 校验: [当前页]
     */
    private void validateCurrentPage(HttpServletRequest request) {
        String currentPageString = request.getParameter(CURRENT_PAGE);
        if (currentPageString == null) {
            return;
        }
        int currentPage = Integer.parseInt(currentPageString);
        if (currentPage >= 1) {
            return;
        }
        String userMessage = "当前页,应大于等于1";
        String errorMessage = String.format("%s:【分页参数校验异常】:【错误字段:[%s],错误值:[%s],错误信息:[%s]】。",
                ErrorEnum.A0425.getMessage(), CURRENT_PAGE, currentPage, userMessage);
        throw new BusinessException(userMessage, ErrorEnum.A0425.name(), errorMessage);
    }
    /**
     * 校验: [每页显示条数]
     */
    private void validatePageSize(HttpServletRequest request) {
        String pageSizeString = request.getParameter(PAGE_SIZE);
        if (pageSizeString == null) {
            return;
        }
        int pageSize = Integer.parseInt(pageSizeString);
        if (pageSize >= 1 && pageSize <= 100) {
            return;
        }
        String userMessage = "每页显示条数,取值范围为[1, 100]";
        String errorMessage = String.format("%s:【分页参数校验异常】:【错误字段:[%s],错误值:[%s],错误信息:[%s]】。",
                ErrorEnum.A0425.getMessage(), PAGE_SIZE, pageSize, userMessage);
        throw new BusinessException(userMessage, ErrorEnum.A0425.name(), errorMessage);
    }
}
分页参数实体
分页参数,一般使用封装好的 分页参数实体 接收(推荐),但是也可以直接写在接口参数列表中(不推荐)。但是,不论怎样接收,只要是分页参数,就应该被校验。
下面是 分页参数实体 的代码:
package com.example.core.model;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springdoc.api.annotations.ParameterObject;
@Data
@ParameterObject
@Schema(name = "分页参数Query")
public class PageQuery {
    @Schema(description = "当前页", type = "Integer", defaultValue = "1", example = "1", minimum = "1")
    private Integer currentPage = 1;
    @Schema(description = "每页显示条数", type = "Integer", defaultValue = "10", example = "10", minimum = "1", maximum = "100")
    private Integer pageSize = 10;
}
测试接口
package com.example.web.page.controller;
import com.example.core.log.annotation.ApiLog;
import com.example.core.model.PageQuery;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
@Slf4j
@RestController
@RequestMapping("page")
@Tag(name = "分页参数校验")
public class PageController {
    @ApiLog
    @GetMapping(path = "users/PageQuery")
    @Operation(summary = "查询用户列表 PageQuery", description = "分页参数校验,使用PageQuery接收分页参数。")
    public String listUsers(PageQuery pageQuery) {
        return "查询用户列表 PageQuery:成功";
    }
    @GetMapping(path = "users/NoPageQuery")
    @Operation(summary = "查询用户列表 NoPageQuery", description = "分页参数校验,分页参数直接写在了方法的参数列表中,未使用PageQuery接收分页参数。")
    public String listUsersWithoutPageQuery(Integer currentPage, Integer pageSize) {
        return "查询用户列表 NoPageQuery:成功";
    }
    @RequestMapping(path = "users/RequestMapping", method = RequestMethod.GET)
    @Operation(summary = "查询用户列表 RequestMapping", description = "分页参数校验,使用 @RequestMapping 注解。")
    public String listUsersByRequestMapping(PageQuery pageQuery) {
        return "查询用户列表 RequestMapping:成功";
    }
}
测试结果


@Order(10)
对切面进行排序,设定切面的触发顺序。数值越小的,优先级越高。
能够触发的切面可能有好多个,需要排个序,告诉项目先触发哪一个。
如果没有设定触发顺序,会按照切面在项目中的位置顺序来触发。比如下图中,默认情况,就是PageValidator 先触发,ApiLogAspect 后出发。
如果 PageValidator 抛出了异常,后面的 ApiLogAspect 就不会被触发了。














![2023年中国肠胃炎用药行业现状分析:随着老龄化进程明显加速,市场规模同比增长7%[图]](https://img-blog.csdnimg.cn/img_convert/3d983f53934f6399eefdab45a3a52af7.png)





