AOP:分页参数统一校验

需求说明

为了保证系统的安全性,需要对所有的 查询列表 接口,添加分页参数,并对分页参数进行校验,
,保证参数的合法性。

比如, pageSize(每页显示条数),如果不做校验,一旦传递过来一个很大的数值,比如,十万亿,数据库可能会直接卡住,或者应用服务器的内存可能也被挤爆。

分页参数与校验逻辑

分页参数 校验逻辑
currentPage 当前页,应大于等于1
pageSize 每页显示条数,取值范围为[1, 100]

currentPagepageSize,都应该是整数,如果传入的是小数、超出范围的数字、或者非数字,也应该直接报错;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:成功";
    }

}

测试结果

AOP:分页参数统一校验_第1张图片

AOP:分页参数统一校验_第2张图片

@Order(10)

对切面进行排序,设定切面的触发顺序。数值越小的,优先级越高。

能够触发的切面可能有好多个,需要排个序,告诉项目先触发哪一个。

如果没有设定触发顺序,会按照切面在项目中的位置顺序来触发。比如下图中,默认情况,就是PageValidator 先触发,ApiLogAspect 后出发。

如果 PageValidator 抛出了异常,后面的 ApiLogAspect 就不会被触发了。

AOP:分页参数统一校验_第3张图片

你可能感兴趣的:(Spring,Boot,spring,boot)