使用反射实现@RequestBody的参数校验功能

简介: springboot中对实体类参数中属性进行校验一般都是使用javax.validation中提供的注解

springboot中对实体类参数中属性进行校验一般都是使用javax.validation中提供的注解


我这次这个项目需要所有接口参数加密,我这里参数解密是使用自定义参数解析器实现HandlerMethodArgumentResolver接口来实现的,通过获取请求体中的加密字符串然后解密后封装到接口参数中。所以就不用@RequestBody注解了,并且那些参数校验的属性也不会起作用。


如果要是在接口里面写if校验就有点。。不优雅,然后就想到在参数解析的时候自己根据这些注解进行校验

package com.gt.gxjhpt.configuration;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.ReUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.symmetric.AES;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.gt.gxjhpt.annotation.ParamsAES;
import com.gt.gxjhpt.entity.dto.BaseReq;
import com.gt.gxjhpt.utils.AESUtil;
import lombok.extern.log4j.Log4j2;
import org.jetbrains.annotations.Nullable;
import org.springframework.core.MethodParameter;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolationException;
import javax.validation.constraints.*;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.stream.Collectors;
/**
 * 解析加密注解
 *
 * @author vhukze
 * @date 2021/9/8 11:14
 */
@Log4j2
public class AESDecodeResolver implements HandlerMethodArgumentResolver {
    /*
        json参数的key
     */
    private static final String NAME = "str";
    /**
     * 如果接口或者接口参数有解密注解,就解析
     */
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return parameter.hasMethodAnnotation(ParamsAES.class) || parameter.hasParameterAnnotation(ParamsAES.class);
    }
    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer modelAndViewContainer,
                                  NativeWebRequest webRequest, WebDataBinderFactory webDataBinderFactory) throws IOException, InstantiationException, IllegalAccessException {
        AES aes = AESUtil.aes;
        // 获取post请求的json字符串
        String postStr = getPostStr(webRequest);
        // 接口参数的字节码对象
        Class<?> parameterType = parameter.getParameterType();
        //如果是实体类参数,把请求参数封装
        if (BaseReq.class.isAssignableFrom(parameterType)) {
            //获取加密的请求数据并解密
//            String beforeParam = webRequest.getParameter(NAME);
            String afterParam = aes.decryptStr(JSONUtil.parseObj(postStr).get(NAME).toString(),
                    CharsetUtil.CHARSET_UTF_8);
            // 校验参数
            if (parameter.hasParameterAnnotation(Validated.class)) {
                Validated validated = parameter.getParameterAnnotation(Validated.class);
                this.verifyObjField(afterParam, parameterType, validated.value());
            }
            //json转对象  // 这里的return就会把转化过的参数赋给控制器的方法参数
            return JSONUtil.toBean(afterParam, parameterType);
            // 如果是非集合类,就直接解码返回
        } else if (!Iterable.class.isAssignableFrom(parameterType)) {
//            String decryptStr = aes.decryptStr(webRequest.getParameter(parameter.getParameterName()), CharsetUtil.CHARSET_UTF_8);
//            return Integer.class.isAssignableFrom(parameter.getParameterType()) ? Integer.parseInt(decryptStr) : decryptStr;
            Object value = JSONUtil.parseObj(aes.decryptStr(JSONUtil.parseObj(postStr).get(NAME).toString(),
                    CharsetUtil.CHARSET_UTF_8)).get(parameter.getParameterName());
            this.verifyOneField(parameter, value);
            return value;
            //如果是集合类
        } else if (Iterable.class.isAssignableFrom(parameterType)) {
            //获取加密的请求数据并解密
//            String beforeParam = webRequest.getParameter(NAME);
            String afterParam = aes.decryptStr(JSONUtil.parseObj(postStr).get(NAME).toString(),
                    CharsetUtil.CHARSET_UTF_8);
            //转成对象数组
            JSONArray jsonArray = JSONUtil.parseArray(afterParam);
            this.verifyCollField(parameter, jsonArray);
            return jsonArray.toList(Object.class);
        }
        return null;
    }
    /**
     * 校验单个参数
     */
    private void verifyOneField(MethodParameter parameter, Object value) {
        for (Annotation annotation : parameter.getParameterAnnotations()) {
            if (annotation instanceof NotBlank) {
                if (value == null || StrUtil.isBlank(value.toString())) {
                    log.info("参数为空");
                    throw new ConstraintViolationException(null);
                }
            }
            if (annotation instanceof NotNull) {
                if (value == null) {
                    log.info("参数为空");
                    throw new ConstraintViolationException(null);
                }
            }
            // 只能是字符串类型
            if (annotation instanceof Size) {
                Size size = (Size) annotation;
                if (value != null && (value.toString().length() < size.min() || value.toString().length() > size.max())) {
                    log.info("参数长度不对");
                    throw new ConstraintViolationException(null);
                }
            }
        }
    }
    /**
     * 校验集合类型
     */
    private void verifyCollField(MethodParameter parameter, JSONArray jsonArray) {
        for (Annotation annotation : parameter.getParameterAnnotations()) {
            if (annotation instanceof NotEmpty) {
                if (jsonArray == null || jsonArray.size() == 0) {
                    log.info("集合参数值为空");
                    throw new ConstraintViolationException(null);
                }
            }
            if (annotation instanceof Size) {
                Size size = (Size) annotation;
                if (jsonArray.size() < size.min() || jsonArray.size() > size.max()) {
                    log.info("集合参数值大小不对");
                    throw new ConstraintViolationException(null);
                }
            }
        }
    }
    /**
     * 校验实体类参数
     *
     * @param param  前端传的参数(json字符串)
     * @param clazz  接口实体类参数的字节码对象
     * @param groups 校验那些组
     */
    private void verifyObjField(String param, Class<?> clazz, Class<?>[] groups) {
        // 前端传的参数
        JSONObject jsonObj = JSONUtil.parseObj(param);
        // 实体类所有字段
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            // 字段如果不可访问,设置可访问
            if (!field.isAccessible()) {
                field.setAccessible(true);
            }
            Annotation[] annotations = field.getDeclaredAnnotations();
            for (Annotation annotation : annotations) {
                if (annotation instanceof NotNull) {
                    NotNull notNull = (NotNull) annotation;
                    if ((ArrayUtil.isEmpty(groups) && ArrayUtil.isEmpty(notNull.groups()))
                            || ArrayUtil.containsAny(groups, notNull.groups())) {
                        if (jsonObj.get(field.getName()) == null) {
                            log.info("字段>>>>>>" + field.getName() + "值有问题");
                            throw new ConstraintViolationException(null);
                        }
                    }
                }
                if (annotation instanceof NotBlank) {
                    NotBlank notBlank = (NotBlank) annotation;
                    if ((ArrayUtil.isEmpty(groups) && ArrayUtil.isEmpty(notBlank.groups()))
                            || ArrayUtil.containsAny(groups, notBlank.groups())) {
                        Object val = jsonObj.get(field.getName());
                        if (val == null || StrUtil.isBlank(val.toString())) {
                            log.info("字段>>>>>>" + field.getName() + "值有问题");
                            throw new ConstraintViolationException(null);
                        }
                    }
                }
                if (annotation instanceof Size) {
                    Size size = (Size) annotation;
                    if ((ArrayUtil.isEmpty(groups) && ArrayUtil.isEmpty(size.groups()))
                            || ArrayUtil.containsAny(groups, size.groups())) {
                        Object val = jsonObj.get(field.getName());
                        if (val instanceof String) {
                            if (val.toString().length() < size.min() || val.toString().length() > size.max()) {
                                log.info("字段>>>>>>" + field.getName() + "值有问题");
                                throw new ConstraintViolationException(null);
                            }
                        }
                        if (val instanceof Collection && Convert.toList(val).size() == 0) {
                            log.info("字段>>>>>>" + field.getName() + "值有问题");
                            throw new ConstraintViolationException(null);
                        }
                    }
                }
                if (annotation instanceof Pattern) {
                    Pattern pattern = (Pattern) annotation;
                    if ((ArrayUtil.isEmpty(groups) && ArrayUtil.isEmpty(pattern.groups()))
                            || ArrayUtil.containsAny(groups, pattern.groups())) {
                        Object val = jsonObj.get(field.getName());
                        if (val != null && !ReUtil.isMatch(pattern.regexp(), val.toString())) {
                            log.info("字段>>>>>>" + field.getName() + "值正则校验失败");
                            throw new ConstraintViolationException(null);
                        }
                    }
                }
                if (annotation instanceof Max) {
                    Max max = (Max) annotation;
                    if ((ArrayUtil.isEmpty(groups) && ArrayUtil.isEmpty(max.groups()))
                            || ArrayUtil.containsAny(groups, max.groups())) {
                        Object val = jsonObj.get(field.getName());
                        if (val != null && Convert.toInt(val) > max.value()) {
                            log.info("字段>>>>>>" + field.getName() + "值太大");
                            throw new ConstraintViolationException(null);
                        }
                    }
                }
            }
        }
    }
    @Nullable
    private String getPostStr(NativeWebRequest webRequest) throws IOException {
        //获取post请求的json数据
        HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest();
        int contentLength = request.getContentLength();
        if (contentLength < 0) {
            return null;
        }
        byte[] buffer = new byte[contentLength];
        for (int i = 0; i < contentLength; ) {
            int readlen = request.getInputStream().read(buffer, i,
                    contentLength - i);
            if (readlen == -1) {
                break;
            }
            i += readlen;
        }
        String str = new String(buffer, CharsetUtil.CHARSET_UTF_8);
        StringBuilder sb = new StringBuilder();
        for (char c : str.toCharArray()) {
            //去掉json中的空格 换行符 制表符
            if (c != 32 && c != 13 && c != 10) {
                sb.append(c);
            }
        }
        return sb.toString();
    }
}
相关文章
|
消息中间件 运维 负载均衡
【Kafka】Kafka 实现负载均衡与故障转移
【4月更文挑战第5天】【Kafka】Kafka 实现负载均衡与故障转移
|
SQL Java 关系型数据库
JDBC Connection详解:连接到数据库的关键
在Java中,要与数据库进行交互,需要使用Java数据库连接(JDBC)。JDBC允许您连接到不同类型的数据库,并执行SQL查询、插入、更新和删除操作。在JDBC中,连接数据库是一个重要的步骤,而Connection对象是实现这一目标的关键。本篇博客将详细解释Connection对象的作用、创建和使用方法,以及与数据库连接相关的注意事项。
745 0
|
12月前
|
决策智能 数据库 开发者
使用Qwen2.5+SpringBoot+SpringAI+SpringWebFlux的基于意图识别的多智能体架构方案
本项目旨在解决智能体的“超级入口”问题,通过开发基于意图识别的多智能体框架,实现用户通过单一交互入口使用所有智能体。项目依托阿里开源的Qwen2.5大模型,利用其强大的FunctionCall能力,精准识别用户意图并调用相应智能体。 核心功能包括: - 意图识别:基于Qwen2.5的大模型方法调用能力,准确识别用户意图。 - 业务调用中心:解耦框架与业务逻辑,集中处理业务方法调用,提升系统灵活性。 - 会话管理:支持连续对话,保存用户会话历史,确保上下文连贯性。 - 流式返回:支持打字机效果的流式返回,增强用户体验。 感谢Qwen2.5系列大模型的支持,使项目得以顺利实施。
3507 8
使用Qwen2.5+SpringBoot+SpringAI+SpringWebFlux的基于意图识别的多智能体架构方案
|
9月前
|
弹性计算 Ubuntu 网络安全
ECS磁盘使用率异常升高,BPS,IOPS飙升
我刚开了一个2C4G的ECS,运行Ubuntu 20.04,常出现无响应、SSH断开等问题。原因是未配置swap,导致内存过高时磁盘写入频繁。解决办法在文章里。
672 72
|
前端开发 jenkins 测试技术
自动化测试介绍,为何 Apifox 是进行自动化测试的最佳工具
自动化测试利用专用软件执行测试用例,比手动测试更高效准确。Apifox是一款集API文档、调试与自动化测试于一体的工具,提供一体化解决方案,简化API变更管理。其强大的测试功能支持丰富的断言及测试场景组合,便于模拟真实业务流程。Apifox还提供详尽的测试报告与分析功能,有助于快速定位问题。此外,它能轻松集成到CI/CD流程中,并支持定时任务及多分支管理,极大提升了测试效率和团队协作。相较于其他工具,Apifox以其全面的功能和友好的界面脱颖而出。
|
12月前
|
存储 前端开发 UED
React 标签页组件 Tab
标签页(Tab)组件是现代Web应用中常见的UI元素,用于在有限空间内展示多个内容面板。本文介绍如何在React中实现功能完善的标签页组件,涵盖基本概念、状态管理、常见问题及解决方案。通过`useState`管理选中标签,使用CSS确保布局一致性和过渡效果,并解决性能和逻辑错误。高级功能如懒加载和持久化选择状态进一步提升用户体验。
327 16
|
SQL JavaScript 前端开发
Hive学习-lateral view 、explode、reflect和窗口函数
Hive学习-lateral view 、explode、reflect和窗口函数
645 4
|
SQL Oracle 关系型数据库
使用Oracle IMP导入数据
使用Oracle IMP导入数据
|
前端开发 Java 关系型数据库
通过HTML网页对mysql数据库进行增删改查(CRUD实例)
通过HTML网页对mysql数据库进行增删改查(CRUD实例)
820 0