责任链简介:为请求创建了一个接收者对象的链。
作用:
避免请求发送者与接收者耦合在一起,让多个对象都有可能接收请求,将这些对象连接成一条链,并且沿着这条链传递请求,直到有对象处理它为止。
这里主要说下自己项目当中使用到责任链设计模式的使用场景,针对客户端向后台发起的请求,每个接口都需要做一些校验、比如
- 是否登录
- 是否完成实名
- 是否绑卡(易达金或信用卡任意一张)
- 是否绑定绑定本行卡校验
- 是否绑定他行卡校验
并且上述步骤是有先后顺序的,按照12345下来的,每个步骤可以选择校验或不校验,同时,只要有一个校验不满足就返回客户端提示信息。
实现方式:
首先定义一个接口 (对每个步骤约定一个行为规范)
/**
* Created with IntelliJ IDEA.
*
* @Author: zhaoxn
* @Date: 2022/08/29/21:33
* @Description:公共校验接口
*/
public interface CommonCheck {
/**
* @Date: 2022/11/7 7:28
* @Description:校验逻辑
* @Author: zhaoxn
* @param ckPo: 实体
* @return boolean
**/
boolean verify(CheckPO ckPo);
/**
* @Date: 2022/11/7 7:30
* @Description:是否需要校验
* @Author: zhaoxn
* @param level:认证级别
* @param appCommon:公共字段
* @return java.lang.String
**/
String doCheck(String level, AppCommon appCommon) throws Exception;
}
用户认证级别枚举类
public enum AuthLevelEnum {
UNBIND_CARD("00","未绑卡"),
BIND_CARD_ONE("01","绑卡(信用卡与易达金任意一个)"),
CREDIT_CARD("02","本行信用卡"),
OTHER_BANK("03","他行卡");
private String code;
private String msg;
AuthLevelEnum(String code, String msg) {
this.code = code;
this.msg = msg;
}
}
1. 登录校验实现类
/**
* Created with IntelliJ IDEA.
* @Author: zhaoxn
* @Date: 2022/08/29/21:39
* @Description:登录校验
*/
@Order(1)
@Component
public class LoginCheck implements CommonCheck{
private final static Logger log = LoggerFactory.getLogger(LoginCheck.class);
@Autowired
private RedisTemplate<String,Object> redis;
@Override
public boolean verify(CheckPO ckPo) {
//当是否校验标识为Y时进行校验
return "Y".equals(ckPo);
}
@Override
public String doCheck(String level, AppCommon appCommon) throws Exception {
log.debug("开始校验登录");
UserSession userSession = new UserSession(AuthLevelEnum.CREDIT_CARD.getCode());
//读取登录客户的session
String jSessionId = "w34sdf9sad8912iheishdfhasdhfsaasdfsfs";
//判断此人是否命中反洗钱
String res = (String) redis.opsForValue().get("ANTIMONEY:"+jSessionId);
if("ANTIMONEY_FLAG".equals(res)){
throw new RuntimeException("抛出命中反洗钱提示");
}
return userSession.getCtfctnLvl();
}
}
2. 实名校验实现类
/**
* Created with IntelliJ IDEA.
* @Author: zhaoxn
* @Date: 2022/08/29/21:54
* @Description:是否实名校验
*/
@Order(2)
@Component
public class IsRealNmCheck implements CommonCheck{
private final static Logger log = LoggerFactory.getLogger(IsRealNmCheck.class);
@Override
public boolean verify(CheckPO ckPo) {
//当是否校验标识为Y时进行校验
return "Y".equals(ckPo.getIfRealNmUsr());
}
@Override
public String doCheck(String level, AppCommon appCommon) throws Exception {
log.debug("校验是否实名");
UserSession userSession = new UserSession(Boolean.TRUE);
if(!userSession.isIfRealNmUsr()){
throw new RuntimeException("提示您未实名认证");
}
return level;
}
}
3. 绑卡校验
/**
* Created with IntelliJ IDEA.
* @Author: zhaoxn
* @Date: 2022/08/29/22:04
* @Description:绑卡校验 (易达金或信用卡任意一张)
*/
@Order(3)
@Component
public class BindCardCheck implements CommonCheck{
private final static Logger log = LoggerFactory.getLogger(IfRealNmCheck.class);
@Override
public boolean verify(CheckPO ckPo) {
//当是否校验标识为Y时进行校验
return "Y".equals(ckPo.getIsbdCard());
}
@Override
public String doCheck(String level, AppCommon appCommon) throws Exception {
log.debug("开始校验绑卡");
if(AuthLevelEnum.BIND_CARD_ONE.getCode().equals(level)){
throw new RuntimeException("提示未绑卡");
}
return level;
}
}
4. 绑定本行卡校验
/**
* Created with IntelliJ IDEA.
* @Author: zhaoxn
* @Date: 2022/08/29/22:10
* @Description:绑定本行卡校验
*/
@Order(4)
@Slf4j
@Component
public class BindCreditCheck implements CommonCheck{
@Override
public boolean verify(CheckPO ckPo) {
//当是否校验标识为Y时进行校验
return "Y".equals(ckPo.getIsbdXyk());
}
@Override
public String doCheck(String level, AppCommon appCommon) throws Exception {
log.debug("开始校验绑信用卡");
if(!(AuthLevelEnum.CREDIT_CARD.getCode().equals(level)) || AuthLevelEnum.CREDIT_CARD.getCode().equals(level)){
throw new RuntimeException("提示未绑信用卡");
}
return level;
}
}
5. 绑定他行卡校验
/**
* Created with IntelliJ IDEA.
* @Author: zhaoxn
* @Date: 2022/08/29/22:17
* @Description:绑定他行卡校验
*/
@Order(4)
@Slf4j
@Component
public class BindOtherBankCheck implements CommonCheck{
private final static Logger log = LoggerFactory.getLogger(IfRealNmCheck.class);
@Override
public boolean verify(CheckPO ckPo) {
//当是否校验标识为Y时进行校验
return "Y".equals(ckPo.getIsbdYdj());
}
@Override
public String doCheck(String level, AppCommon appCommon) throws Exception {
log.debug("开始校验绑他行卡");
if(!(AuthLevelEnum.OTHER_BANK.getCode().equals(level)) || AuthLevelEnum.OTHER_BANK.getCode().equals(level)){
throw new RuntimeException("提示未绑他行卡校验");
}
return level;
}
}
实现类的执行顺序这里是通过@Order(1)注解来控制的 注解里的值越小优先级越高
切面类定义如下:
/**
* Created with IntelliJ IDEA.
*
* @Author: zhaoxn
* @Date: 2022/11/08/7:48
* @Description:权限校验切面
*/
@Aspect
@Component
@Order(-999)
public class PowerCheckAspect {
@Autowired
private RedisTemplate redisTemplate;
private static final Logger logger = LoggerFactory.getLogger(PowerCheckAspect.class);
@Autowired
private List<CommonCheck> commonChecks;
@Autowired
private DbHxInterfaceMapper dbHxInterfaceMapper;
@Pointcut("@annotation(org.springframework.web.bind.annotation.RestController)")
public void dataSubmit() {}
/**
* 伪代码表示
* @param point
* @throws Throwable
* @return
*/
@Around("dataSubmit()")
public Object doAround(ProceedingJoinPoint point) throws Throwable {
//使用NotPowerCheck注解的功能直接不进行校验
String method = "";//假设得到方法名
if(null!=method.getAnnotation(NotPowerCheck.class)){
return point.proceed();
}
//接口权限表结构 是否绑定信用卡--是否绑定易达金--是否绑卡--是否绑定附属卡--是否绑定借记卡--是否绑定其他行卡--客户认证级别--是否登录--是否实名用户
//这里考虑某些接口可能没有在接口权限表里预先配置,则放行,通过redis一下,下面有放redis逻辑
String ifCheck = (String) redisTemplate.opsForValue().get("check:interface:notExists:"+method); //下面往缓存放N值
//值为N代表不需要权限校验,原因是权限校验表没有该接口的配置信息
if(ifCheck.equals("N")){
return point.proceed();
}
//取缓存中的接口权限信息,没有就去查db,再放进缓存有效期为次日零点
String key = "common:interface:exists:"+method;
Object dbEntity = redisTemplate.opsForValue().get(key);//对象O就是权限校验实体
if(dbEntity==null){
//根据接口名查询接口权限
dbEntity = dbHxInterfaceMapper.selectById(method);
if(dbEntity==null){ //表中没有配置
redisTemplate.opsForValue().set("check:interface:notExists:"+method,"N","次日零点", TimeUnit.SECONDS);
return point.proceed();//表里没配置放行
}
//不为空则将接口配置放redis,有效期至第二天0点
redisTemplate.opsForValue().set(key,dbEntity,"次日零点", TimeUnit.SECONDS);
}
logger.debug("-------------------开始校验-------------------");
AppCommon appCommon = null;
//此处省略获取每个方法中传过来的appCommon参数 。。。。。。。
// appCommon参数处理
String level = null;
for (CommonCheck commonCheck: commonChecks){
if(commonCheck.verify((CheckPO)dbEntity)){ //不同实现类定义了是否走具体校验逻辑标识
level = commonCheck.doCheck(level,appCommon);
}
}
return point.proceed();
}
}