一 背景
最近告警群里经常报一些转换异常的错误。比如:String强转为Long报错,经过排查原因是测试团队在做接口扫描,定期的接口测试,但是参数会传一些特殊字符@!#
等等,因此在服务端进行String转换为Long时就会报错,从而触发告警群的通知。
二 解决方案
- 入参String改为Long类型
- String转Long做校验
- 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;
}