还是基于《消失的字段》和《消失的消息》两篇文章中提到的供应链金融项目,项目业务涉及向银行申请授信、发起融资等,开发过程中需要跟银行进行接口对接联调。与银行对接过的朋友应该都知道,跟银行进行接口对接联调是一项艰巨而缓慢的工作,而且配合度无法把控,项目进度无法得到保证。而我们这个项目不止对接一家银行,需要对接多家银行,客户可以根据自己的资产向平台对接的多家银行发起融资申请。
基于上述情况,为了保证项目内部开发可控,需要针对银行对接服务实现一套挡板机制,只需要根据银行接口响应数据结构,实现内部开发流程闭环。该挡板机制由如下要求:
(1)可以灵活对指定银行实施挡板机制,比如:只对华夏银行实施挡板机制;
(2)可以灵活对指定接口方法实施挡板机制,比如:只对融资申请方法实施挡板机制;
(3)可以不停服变更并生效挡板机制,比如:撤销对华夏银行的挡板,增加对工商银行的挡板;
目前项目已存在银行接口调用服务接口FinancingService及其实现类FinancingServiceImpl,为了保证开闭的设计原则,实现方案采用自定义注解和AspectJ技术,通过Nacos配置实现挡板机制的灵活切换。
首先定义一个银行服务接口的挡板实现类,实现挡板逻辑;然后定义一个挡板注解BankBaffle,应用于需要进行挡板的方法上面;最后编写注解拦截器,根据注解配置信息,进行相应的挡板服务调用。
项目已有银行服务对接接口FinancingService及其实现类FinancingServiceImpl。
@Service
public interface FinancingService {
Resp apply(ApplyReq applyReq); // 融资申请
Resp applyResult(ApplyResultQueryReq applyResultQueryReq); // 融资申请结果查询
}
@Service
public class FinancingServiceImpl implements FinancingService {
@Override
public Resp apply(ApplyReq applyReq) {
...
}
@Override
public Resp applyResult(ApplyResultQueryReq applyResultQueryReq) {
...
}
}
@Service
public class BaffleFinancingServiceImpl implements FinancingService {
@Override
public Resp apply(ApplyReq applyReq) {
// 对应方法的挡板逻辑
...
}
@Override
public Resp applyResult(ApplyResultQueryReq applyResultQueryReq) {
// 对应方法的挡板逻辑
...
}
}
package com.xxx.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface BankBaffle {
// 默认启动挡板逻辑
String enable() default "true";
// 挡板实现类,必须与业务类实现相同的接口
String className();
// 拦截指定的银行请求
String bankIds();
}
@Aspect
@Component
public class BankBaffleAspect implements EnvironmentAware {
private static final Logger log = LoggerFactory.getLogger(BankBaffleAspect.class);
// 挡板默认开启
private static final String BAFFLE_ENABLE_DEFAULT = "true";
private static final String BAFFLE_BANKID_ALL = "ALL";
private Environment environment;
// 挡板对象Map,起缓存作用
@Resource
private Map map;
@Override
public void setEnvironment(Environment environment) {
this.environment = environment;
}
/**
* 定义切入点
*/
@Pointcut("@annotation(com.xxx.annotation.BankBaffle) ")
public void entryPoint() {
// 无需内容
}
@Around("entryPoint()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
Signature sig = joinPoint.getSignature();
if (!(sig instanceof MethodSignature)) {
throw new IllegalArgumentException("该注解只能用于方法");
}
MethodSignature msig = (MethodSignature) sig;
Object target = joinPoint.getTarget();
Method currentMethod = target.getClass().getMethod(msig.getName(), msig.getParameterTypes());
// 获取注解
BankBaffle bankBaffle = currentMethod.getAnnotation(BankBaffle.class);
// 解析注解参数
String enable = this.environment.resolvePlaceholders(bankBaffle.enable());
String classname = this.environment.resolvePlaceholders(bankBaffle.className());
String bankIds = this.environment.resolvePlaceholders(bankBaffle.bankIds());
if(BAFFLE_ENABLE_DEFAULT.equalsIgnoreCase(enable)) {
// 容器中是否加载注入指定的挡板实例
FinancingService baffleObj = map.get(classname);
if(null == baffleObj) {
return joinPoint.proceed();
}
// 是否配置指定执行挡板程序的银行
String[] bankIdArray = StringUtils.split(bankIds, ",");
if(null == bankIdArray || bankIdArray.length == 0) {
return joinPoint.proceed();
}
List bankIdList = Arrays.asList(bankIdArray);
// 如果bankid值配置了‘ALL’,则所有银行请求全部进入挡板; 对指定的银行请求执行挡板程序,否则执行原程序;
JSONObject methodParamValue = (JSONObject) JSON.toJSON(joinPoint.getArgs()[0]);
String bankId = methodParamValue.getJSONObject("head").getString("bankid");
if(bankIdList.contains(BAFFLE_BANKID_ALL) || bankIdList.contains(bankId)) {
String methodName = msig.getMethod().getName();
log.info("执行挡板程序:{}.{}", classname, msig.getMethod().getName());
Method m = baffleObj.getClass().getMethod(methodName, msig.getParameterTypes());
return m.invoke(baffleObj, joinPoint.getArgs());
} else {
return joinPoint.proceed();
}
} else {
return joinPoint.proceed();
}
}
}
(1)在原银行对接接口实现类上增加注解@Primary,由于增加了BaffleFinancingServiceImpl,FinancingService接口存在两个实现类,自动注入将出现问题。加上@Primary注解表示优先注入FinancingServiceImpl。
@Primary
@Service
public class FinancingServiceImpl implements FinancingService {
@Override
public Resp apply(ApplyReq applyReq) {
...
}
@Override
public Resp applyResult(ApplyResultQueryReq applyResultQueryReq) {
...
}
}
(2)对需要进行挡板的方法上增加@BankBaffle注解,具体如下:
@Primary
@Service
public class FinancingServiceImpl implements FinancingService {
@Override
@BankBaffle(enable = "${baffle.bank.enable}",
className = "${baffle.bank.classname}",
bankIds = "${baffle.bank.method.apply-ids}"
)
public Resp apply(ApplyReq applyReq) {
...
}
@Override
@BankBaffle(enable = "${baffle.bank.enable}",
className = "${baffle.bank.classname}",
bankIds = "${baffle.bank.method.apply-ids}"
)
public Resp applyResult(ApplyResultQueryReq applyResultQueryReq) {
...
}
}
(3)在Nacos增加如下配置:
baffle:
bank:
enable: true
classname: com.xxx.service.impl.BaffleFinancingServiceImpl
apply-ids: ALL //ALL表示所有银行都拦截,也可以指定bankId,与请求头匹配一致则进入挡板程序
针对银行服务接口编写了对应的挡板服务实现类,通过自定义注解BankBaffle和注解拦截器BankBaffleAspect实现挡板服务与正常服务的切换,切换判断需要根据在Nacos中配置的挡板参数。
银行服务接口中哪个方法需要增加挡板机制,只需加上注解BankBaffle即可;需要拦截哪些银行,只需要在Nacos中配置对应的银行id即可;关闭和开启挡板服务只需要在Nacos中修改enable参数即可。
最后,上述方案还有比较大的优化空间,感兴趣的同学可以自由发挥。火花因碰撞而闪现!