分页参数,统一校验(AOP)

需求说明

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

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

分页参数与校验逻辑

分页参数参数含义校验逻辑
pageNumber当前页码应大于等于1
pageSize每 1 页的数据量取值范围为[1, 100]

pageNumberpageSize,都应该是整数,如果传入的是小数、超出范围的数字、或者非数字,也应该直接报错;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 PAGE_NUMBER = "pageNumber";
    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();


        // 校验: [当前页码]
        validatePageNumber(request);

        // 校验: [每 1 页的数据量]
        validatePageSize(request);
    }


    /**
     * 校验: [当前页码]
     */
    private void validatePageNumber(HttpServletRequest request) {
        String pageNumberString = request.getParameter(PAGE_NUMBER);
        if (pageNumberString == null) {
            return;
        }

        int pageNumber = Integer.parseInt(pageNumberString);
        if (pageNumber >= 1) {
            return;
        }

        String userMessage = "当前页码,应大于等于1";
        String errorMessage = String.format("%s:【分页参数校验异常】:【错误字段:[%s],错误值:[%s],错误信息:[%s]】。",
                ErrorEnum.A0425.getMessage(), PAGE_NUMBER, pageNumber, userMessage);

        throw new BusinessException(userMessage, ErrorEnum.A0425.name(), errorMessage);
    }


    /**
     * 校验: [每 1 页的数据量]
     */
    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 页的数据量,取值范围为[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 pageNumber = 1;

    @Schema(description = "每 1 页的数据量", 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 pageNumber, Integer pageSize) {
        return "查询用户列表 NoPageQuery:成功";
    }


    @RequestMapping(path = "users/RequestMapping", method = RequestMethod.GET)
    @Operation(summary = "查询用户列表 RequestMapping", description = "分页参数校验,使用 @RequestMapping 注解。")
    public String listUsersByRequestMapping(PageQuery pageQuery) {
        return "查询用户列表 RequestMapping:成功";
    }

}


测试结果

能够正确校验出分页参数的错误。

当前页码

在这里插入图片描述

每 1 页的数据量

在这里插入图片描述

切面调用排序:@Order(10)

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

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

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

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

在这里插入图片描述

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

宋冠巡

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值