一个来自生产的频繁报错的解决方案之字符串转换数值型校验切面

一 背景

最近告警群里经常报一些转换异常的错误。比如:String强转为Long报错,经过排查原因是测试团队在做接口扫描,定期的接口测试,但是参数会传一些特殊字符@!#等等,因此在服务端进行String转换为Long时就会报错,从而触发告警群的通知。

二 解决方案

  1. 入参String改为Long类型
  2. String转Long做校验
  3. AQP切面校验参数,参数异常直接返回

三 实现

最终经过评估,采用了方案3,AOP切面校验参数。实现如下:

3.1 声明一个注解ValidateParam

/**
 * @author 男人要霸气
 * @Classname ValidateParam
 * @date 2024/5/6 09:36
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ValidateParam {
    String param() default "";
}

3.2 AOP实现

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.Enumeration;
import java.util.Objects;
import java.util.regex.Pattern;

/**
 * 字符串转换数值型校验切面
 *
 * @author 男人要霸气
 * @Classname ValidateParam
 * @date 2024/5/6 09:36
 */
@Aspect
@Slf4j
@Component
public class ValidateParamAop {

    public static final String METHOD_GET = "GET";
    public static final String METHOD_POST = "POST";

    @Pointcut(value = "@annotation(com.annotation.ValidateParam)")
    public void validateParamPointcut() {
    }

    @Around(value = "validateParamPointcut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        Class<?> aClass = joinPoint.getTarget().getClass();
        String methodName = joinPoint.getSignature().getName();
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = aClass.getMethod(methodName, methodSignature.getParameterTypes());

        ValidateParam validateParam = method.getAnnotation(ValidateParam.class);
        if (Objects.isNull(validateParam) || StringUtils.isBlank(validateParam.param())) {
            return joinPoint.proceed();
        }

        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();

        if (METHOD_GET.equalsIgnoreCase(request.getMethod())) {
            if (methodGetCheckNumber(validateParam, request)) {
                return joinPoint.proceed();
            }
        }

        if (METHOD_POST.equalsIgnoreCase(request.getMethod())) {
            Parameter[] parameters = methodSignature.getMethod().getParameters();
            if (Objects.isNull(parameters)) {
                return joinPoint.proceed();
            }
            if (parameters[0].isAnnotationPresent(RequestParam.class)) {
                if (methodGetCheckNumber(validateParam, request)) {
                    return joinPoint.proceed();
                }
            }else if (parameters[0].isAnnotationPresent(RequestBody.class)) {
                if (methodPostCheckNumber(validateParam, request)) {
                    return joinPoint.proceed();
                }
            }else {
                return joinPoint.proceed();
            }
        }


        return "非法字符";
    }

    private boolean methodPostCheckNumber(ValidateParam validateParam, HttpServletRequest request) throws Throwable {
        BufferedReader reader = request.getReader();
        StringBuilder sb = new StringBuilder();
        String line;
        while ((line = reader.readLine()) != null) {
            sb.append(line);
        }
        String requestBody = sb.toString();
        JSONObject jsonObject = JSON.parseObject(requestBody);
        for (String name : jsonObject.keySet()) {
            if (Objects.equals(name, validateParam.param())) {
                String arg = (String) jsonObject.get(name);
                if (Boolean.TRUE.equals(checkNumber(arg))) {
                    return true;
                }
            }
        }
        return false;
    }

    private boolean methodGetCheckNumber(ValidateParam validateParam, HttpServletRequest request) throws Throwable {
        Enumeration<String> parameterNames = request.getParameterNames();
        while (parameterNames.hasMoreElements()) {
            String name = parameterNames.nextElement();
            if (Objects.equals(name, validateParam.param())) {
                String arg = request.getParameter(name);
                if (Boolean.TRUE.equals(checkNumber(arg))) {
                    return true;
                }
            }
        }
        return false;
    }


    /**
     * 校验是否纯数字型字符串
     *
     * @param str
     * @return
     */
    public static boolean checkNumber(String str) {
        if (StringUtils.isBlank(str)) {
            return false;
        }
        return Pattern.matches("^\\d+$", str);
    }

}

3.3 在方法上加上注解

@GetMapping("/test")
    @ValidateParam(param = "id")
    public Boolean test(@RequestParam("id") @Validated String id) {
        Long id = Long.value(id);
        return true;
    }
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值