原文链接
前言
经常做表单操作的小伙伴都知道,测试没能测出来然后由于种种原因在线上出问题且大概率不能直接定位到问题的BUG
,其中没有进行字符串的修剪是一个不少人在初期,甚至中后期都有可能不小心犯的错误。因为这种问题的出现的BUG
一般不是本身,而是衍生出来比如字符串不匹配导致的检查错误、认证错误等
前段时间和产品聊天,某产品表示,谁谁谁的技术框架是很烂不能被接受的,问其原因,说是一次线上问题定位很久才定位到:是由于管理员输入用户姓名时,前后端没有进行修剪,导致用户在进行后续操作的时候提示查无此人。但是这种问题,我扪心自问,也很难避免,所以这里我们简化业务trim
的处理,统一使用注解实现
实现
创建注解
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface TrimParam {
}
创建切面
这里有两种方案,如果你只在数据请求的时候进行修剪操作,那么通过继承RequestBodyAdviceAdapter
实现HandlerMethodArgumentResolver
的方式,对于指定注解参数进行处理,这里由于我们需要更加通用的操作,所以使用切面实现
package com.aster.yuno.index.utils;
import com.aster.yuno.index.ann.TrimParam;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
@Aspect
@Component
public class TrimParamStringsAspect {
@Around("execution(* *(.., @com.aster.yuno.index.ann.TrimParam (*), ..))")
public Object trimParamStrings(ProceedingJoinPoint joinPoint) throws Throwable {
Object[] args = joinPoint.getArgs();
Method method = getCurrentMethod(joinPoint);
if (method == null) {
return joinPoint.proceed(args);
}
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
for (int annCount = 0; annCount < parameterAnnotations.length; annCount++) {
for (Annotation annotation : parameterAnnotations[annCount]) {
if (annotation instanceof TrimParam) {
Object needTrimParam = args[annCount];
if (needTrimParam instanceof String thisStr) {
args[annCount] = thisStr.trim();
} else {
trimFields(args[annCount]);
}
}
}
}
return joinPoint.proceed(args);
}
private void trimFields(Object object) {
for (Field field : object.getClass().getDeclaredFields()) {
if (field.getType().equals(String.class)) {
field.setAccessible(true);
try {
String value = (String) field.get(object);
if (value != null) {
field.set(object, value.trim());
}
} catch (Exception ignore) {
}
}
}
}
private Method getCurrentMethod(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
Class<?>[] parameterTypes = ((MethodSignature) joinPoint.getSignature()).getParameterTypes();
try {
return joinPoint.getTarget().getClass().getMethod(methodName, parameterTypes);
} catch (NoSuchMethodException ignore) {
return null;
}
}
}
这里我们注解给到参数,如果是字符串参数,则直接修剪,如果是对象参数,则进入对象,修剪对象中的所有字符串字段
注入
由于我们这里是使用的自建的外部包,方便所有微服务使用,所以需要在注册到当前容器内,如果小伙伴只在当前项目中处理,则不需要进行注入了。注入方式很多,这里使用@ComponentScan
//more
@ComponentScan(basePackages = ["com.aster.yuno.index.utils", "com.aster.yuno.demo"])
class DemoApplication
fun main(args: Array<String>) {
runApplication<DemoApplication>(*args)
}
使用
@RestController
@RequestMapping("/test")
class TestController @Autowired constructor(
private val testService: ITestService,
) {
@PostMapping("/{projectId}/user/insert/auth")
fun new(
@PathVariable("projectId") projectId: String,
@TrimParam @RequestBody param: InsertProjectUserParam,
): ResultObj<Unit> {
testService.new(projectId, adminUserId, param)
return ResultObj.success()
}
}
此时我们传入携带首尾空格的字符串字段在InsertProjectUserParam
中,即可观察到,该字符串被修剪