前言
在实际项目中,相信很多都会用到数字字典之类的配置功能,其作用之一就是配置开关功能。针对开关问题,写了一个简单的通用版本用于抛砖引玉。
一、设计思路
自定义注解+AOP+SPEL解析,使用策略模式进行不同验证需求的验证
二、具体实现
1.自定义注解
自定义注解需要设置的值
values:表示需要进行验证的字段和在数字字典存的值的key,这里是数组,表明可以接收多个字段和key
messages:表示验证出错时,需要返回的报错信息,可以于values长度对应,也可以共用一个。
rules:验证规则,表示验证的规则,可以和前面的字段对应,也可以共用一个
注意:当messages和rules少于values时,取的是最后一个message和rule。
/**
* 通过开关注解
* @author jy
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface UniversalSwitch {
/**
*
* @return
*/
String[] values ();
/**
* 报错信息
* @return
*/
String[] messages ();
/**
* 规则
* @return
*/
SwitchRuleEnum[] rules();
}
2.规则枚举
枚举中code代表的是后期具体规则验证类的beanName,如果有需要扩展规则的在此添加。
/**
* 比较规则
* @author jy
*/
public enum SwitchRuleEnum {
//大于
GREATER("greaterSwitchRule","大于"),
//大于等于
GREATER_OR_EQUAL("greaterOrEqualSwitchRule","大于等于"),
//小于
LESS("lessSwitchRule","小于"),
//小于等于
LESS_OR_EQUAL("lessOrEqualSwitchRule","小于等于"),
//等于
EQUAL("equalSwitchRule","等于"),
//不等于
UNEQUAL("unequalSwitchRule","不等于"),
//不为空
NOT_EMPTY("notEmptySwitchRule","不为空"),
;
private String code;
private String description;
SwitchRuleEnum(String code, String description) {
this.code = code;
this.description = description;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public static SwitchRuleEnum getByType(String code) {
for (SwitchRuleEnum s : values()) {
if (s.getCode().equals(code)) {
return s;
}
}
return null;
}
public static String getName(String code) {
for (SwitchRuleEnum c : SwitchRuleEnum.values()) {
if (c.code.equals(code)) {
return c.name();
}
}
return null;
}
}
3.AOP实现
通过SPEL解析到字段中的具体值,该值就是待验证的值,和数字字段中的进行比较。
利用ApplicationContext获取到具体的验证规则类。
/**
* @author jy
*/
@Aspect
@Component
public class UniversalSwitchAop implements ApplicationContextAware {
private ApplicationContext applicationContext;
/**
* 需要被解析的前后缀
*/
public static final TemplateParserContext TEMPLATE_PARSER_CONTEXT = new TemplateParserContext("{", "}");
/**
* Spell解析器
*/
public static final ExpressionParser EXPRESSION_PARSER = new SpelExpressionParser();
@Pointcut("@annotation(com.dili.ann.UniversalSwitch)")
public void dsPointCut() {
}
@After("dsPointCut()")
public Object around(JoinPoint point) {
MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod();
//获取参数名称
String[] parameterNames = signature.getParameterNames();
//获取参数
Object[] args = point.getArgs();
//把handleMethod的形参都添加到上下文中,并使用参数名作为key
EvaluationContext context = new StandardEvaluationContext();
int length = args.length;
for (int i = 0; i < length; i++) {
context.setVariable(parameterNames[i], args[i]);
}
UniversalSwitch annotation = method.getAnnotation(UniversalSwitch.class);
if (annotation != null) {
String[] values = annotation.values();
String[] messages = annotation.messages();
SwitchRuleEnum[] r = annotation.rules();
int valLength = values.length;
int msgLength = messages.length;
int sLength = r.length;
for (int i = 0; i < valLength; i++) {
String o = EXPRESSION_PARSER.parseExpression(values[i], TEMPLATE_PARSER_CONTEXT).getValue(context, String.class);
//报错信息可以允许公用,但是如果是少于检验字段的话,少的部分使用最后一个报错信息
String msg = i+1 > msgLength ? messages[msgLength - 1] : messages[i];
//检验规则允许公用,同理少于部分使用的是最后一个规则
SwitchRuleEnum switchRuleEnum = i+1 > sLength ? r[sLength - 1] : r[i];
applicationContext.getBean(switchRuleEnum.getCode(), SwitchRule.class).execute(o, msg);
}
}
return point.getTarget();
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
4.验证接口
/**
* 验证
* @author jy
*/
public interface SwitchRule {
/**
* 执行校验
* @param val
* @param msg
*/
void execute(String val, String msg);
}
5.验证实现
这里的实现只是测试代码,具体实现最好从数字字典中获取。
注意:val按=拆分为数组后下标0的值对应的应该是数字字典或redis中的key
/**
* 验证大于
* @author jy
*/
@Service(value = "greaterSwitchRule")
public class SwitchRule4Greater implements SwitchRule{
@Resource
private StringRedisTemplate stringRedisTemplate;
@Override
public void execute(String val, String msg) {
//val中包含了数字字典或者redis中的key和需要被校验的值,按=分隔
String[] split = val.split("=");
String s = stringRedisTemplate.opsForValue().get(split[0]);
//如果没有值,则不需判断
if (StrUtil.isBlank(s)){
return;
}
String str = split.length>1?split[1]:"0";
//下面则是判断这个入参不能大于查回的值 100写死的,实际中应该是从数字字典或redis中获取到的
TUtils.isTure(str.compareTo("100")>0).throwMessage(msg);
}
}
TUtils这个类的具体使用可以参考[工具类-if判断]
https://blog.csdn.net/qq_41792853/article/details/131010598?spm=1001.2014.3001.5502
5.测试验证
/**
* 通用开关
* @author jy
*/
@RestController
@RequestMapping("/switch")
@Slf4j
public class UniversalSwitchController {
@UniversalSwitch(values = {"code={#accountCycleDo.userCode}","balance={#accountCycleDo.balance}"},
messages = {"操作员编号不能为空","盘存金额不能大于100"},rules = {SwitchRuleEnum.NOT_EMPTY,SwitchRuleEnum.GREATER})
@RequestMapping("/test")
public BaseOutput<String> test(@RequestBody AccountCycleDo accountCycleDo){
return BaseOutput.success();
}
}
这样就可以进行值验证了。
总结
写得比较粗略,实现也只写了一个,具体的肯定要都实现。就当一个思路吧,重要的是总结自己实际业务中的需求,积累自己的工具库,不断提升吧,大家加油!!!!!!