业务场景
为保证客户数据的准确性、安全和严谨性,现需要对请求参数包含电话号码的接口进行优化。如果请求中包含号码,必须验证该号码是否在号码屏蔽池,且号码在号码屏蔽池的接口不向前台响应任何数据。
业务分析
由于涉及号码查询的接口有很多,如果一个个进行优化则改动较大。结合实际情况我们采用Spring Aop切面技术进行处理,大大降低代码耦合度与研发周期,也能够提高代码可复用性和可维护性。
技术积累
AOP是Spring 提供的切面处理业务的一种方式,可以在代码运行中插入一段业务代码进行执行。其代表的方法有:
1、前通知:方法执行之前,method:增强执行的方法
2、后通知:方法执行之后,又称最终通知,无论如何都执行
3、返回后通知:成功返回后,有异常时不执行
4、异常通知:发生异常后,只有异常抛出时才执行,不能try…catch异常
5、环绕通知:在方法的执行前后进行一些增强,在方法的执行前后进行一些增强 =前通知+返回后通知
技术方案
1、首先我们需要在方法执行前进行号码验证业务处理并修改请求参数,所以我们选择环绕通知进行处理
2、由于我们需要对传入参数进行动态验证是否在屏蔽池,故我们需要增加动态获获取方法参数,这里就用到了Spring表达式语言Spel来处理自定义注解中的参数表达式。(Spring 表达式语言 Spring Expression Language是一个支持运行时查询和操作对象图的表达式语言。 语法类似于 EL 表达式,但提供了显式方法调用和基本字符串模板函数等额外特性 )
实战演练
1、首先创建一个验证号码的注解TelCheck,使其作用于方法并在运行时提供服务
@Documented
@Inherited
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TelCheck {
/**
* 号码字段名
* @return
*/
String telPhoneField() default "";
/**
* 返回索引
* @return
*/
int resultIndex() default 1;
}
2、创建一个切面TelActionAop,并提供环绕增强
为保证演示效果,这里仅对号码不为空进行数据回写。
这里两个重点:
一个是以验证号码的注解作为切点
@Pointcut(value = "@annotation(com.ysjr.base.domain.annotation.TelCheck)")
public void annotationPointCut(){}
另一个是用spel表达式进行动态参数获取
field = generateKey(joinPoint, field);
具体切面类为:
/**
* 号码处理切面
* @author senfel
* @version 1.0
* @date 2023/2/7 9:30
*/
@Aspect
@Component
@Slf4j
public class TelActionAop {
@Pointcut(value = "@annotation(com.ysjr.base.domain.annotation.TelCheck)")
public void annotationPointCut(){}
/**
* 环绕增强
* @param joinPoint
* @author senfel
* @date 2023/2/7 14:43
* @return java.lang.Object
*/
@Around("annotationPointCut()")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
//获取注解
TelCheck annotation = signature.getMethod().getAnnotation(TelCheck.class);
//获取注解方法参数
Object[] args = joinPoint.getArgs();
//获取注解中设置的数据
String field = annotation.telPhoneField();
int resultIndex = annotation.resultIndex();
//解析动态参数
field = generateKey(joinPoint, field);
args[resultIndex] = true;
if (StringUtils.isNotBlank(field)) {
//具体业务逻辑处理
//这里仅判断如果传入参数值不为空则返回异常标识
args[resultIndex] = false;
}
return joinPoint.proceed(args);
}
/**
* 解析动态参数
* @param joinPoint
* @param spELString
* @author senfel
* @date 2023/2/7 10:40
* @return java.lang.String
*/
private String generateKey(JoinPoint joinPoint,String spELString) {
if(StringUtils.isBlank(spELString)){
return null;
}
// 通过joinPoint获取被注解方法
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Method method = methodSignature.getMethod();
//创建解析器
SpelExpressionParser parser = new SpelExpressionParser();
//获取表达式
Expression expression = parser.parseExpression(spELString);
//设置解析上下文(有哪些占位符,以及每种占位符的值)
EvaluationContext context = new StandardEvaluationContext();
//获取参数值
Object[] args = joinPoint.getArgs();
//获取运行时参数的名称
DefaultParameterNameDiscoverer discoverer = new DefaultParameterNameDiscoverer();
String[] parameterNames = discoverer.getParameterNames(method);
for (int i = 0; i < parameterNames.length; i++) {
context.setVariable(parameterNames[i],args[i]);
}
//解析,获取替换后的结果
String result = Objects.isNull(expression.getValue(context)) ? "" : expression.getValue(context).toString();
System.out.println(result);
return result;
}
}
3、测试请求接口
3.1 后端接口增加验证号码注解
@TelCheck(telPhoneField = "#query.nameOrTel",resultIndex = 3)
3.2 方法内部增加回写验证标识参数 Boolean telCheckFlag
详细接口:
/**
* 根据条件查询商机列表(分页)
*
* @param query 查询条件
* @return ResultMsg
* @ignore
*/
@TelCheck(telPhoneField = "#query.nameOrTel",resultIndex = 3)
@PostMapping("queryBusinessListPage")
public ResultMsg queryBusinessListPage(@Valid @RequestBody BusinessClueListQueryVo query,
BindingResult bindingResult,
HttpServletRequest request,Boolean telCheckFlag) {
try {
if(!telCheckFlag){
//验证失败直接响应空的数据,不再进行后续业务逻辑
ResultMsg resultMsg = ResultMsg.ok();
resultMsg.put("total", 0);
return resultMsg;
}
if (bindingResult.hasErrors()) {
return ValidUtil.validError(bindingResult);
}
ResultMsg resultMsg = this.searchService.searchBusiness(query, false, false);
return resultMsg;
}catch (CustomTelException te){
ResultMsg resultMsg = ResultMsg.ok();
resultMsg.put("total", 0);
return resultMsg;
}catch (CustomException e) {
return ResultMsg.systemError(e.getMessage());
} catch (Exception e) {
log.error("商机列表查询异常:", e);
return ResultMsg.systemError("商机列表查询异常!");
}
}
4.2 后端切面环绕增强在方法执行之前就获取到参数传入号码,这里为了测试仅验证不为空则响应验证失败,并回写到号码验证标识参数中。
4.3 线程处理增强后进入接口方法,获取到验证失败的标识,直接响应前端不再执行后续逻辑。
5、总结与重点
本案例直接运用AOP切面动态获取方法参数并回写标识,其中重点是AOP切面环绕增强逻辑处理与请求参数回写,以及内部通过Spring 表达式语言来动态解析注解方法参数,大大提升代码可读性与可维护性。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/154656.html