aviator 应用(1)
1.项目结构:
└─src
└─main
└─java
└─com
└─yan
├─abstractinterface
│ └─bean
├─utils
│ └─object
└─aop
├─aspect
└─aviator
2.pom
<properties>
<java.version>8</java.version>
<project-module.version>0.0.1-SNAPSHOT</project-module.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>${java.version}</maven.compiler.source>
<maven.compiler.target>${java.version}</maven.compiler.target>
<hutool.version>5.8.18</hutool.version>
<aop.version>1.9.19</aop.version>
<lombok.version>1.18.20</lombok.version>
<aviator.version>5.2.0</aviator.version>
<commons-beanutils.version>1.9.4</commons-beanutils.version>
</properties>
<dependencies>
<!--aviator-->
<dependency>
<groupId>com.googlecode.aviator</groupId>
<artifactId>aviator</artifactId>
<version>${aviator.version}</version>
</dependency>
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>${commons-beanutils.version}</version> <!-- 请使用最新稳定版本 -->
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>${hutool.version}</version>
</dependency>
<!--aop-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>${aop.version}</version>
</dependency>
</dependencies>
2.1 com.yan.abstractinterface.bean
2.1.1 interface AbstractBean
package com.yan.abstractinterface.bean;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
/**
* @Author yan
* @Date 2024/9/22 上午10:46:15
* @Description
*/
public interface AbstractBean {
@Data @Accessors(chain = true)
@NoArgsConstructor
@AllArgsConstructor
class LogBean{
private org.slf4j.Logger logger;
private Class<?> aClass;
}
@JsonIgnore
default LogBean getLogBean(){
LogBean logBean = new LogBean()
.setAClass(this.getClass())
.setLogger(LoggerFactory.getLogger(this.getClass()));
return logBean;
}
@JsonIgnore
default Logger getLogger(){
return getLogBean().getLogger();
}
@JsonIgnore
default Class<?> getAClass(){
return getLogBean().getAClass();
}
/**
* 初始化
*/
@PostConstruct
default void init(){
LogBean logBean = getLogBean();
logBean.getLogger().info("init {} ...",logBean.getAClass().getSimpleName());
}
/**
* 销毁
*/
@PreDestroy
default void destroy() {
LogBean logBean = getLogBean();
logBean.getLogger().info("destroy {} ...",logBean.getAClass().getSimpleName());
}
}
3.com.yan.aop.aviator
3.1 @interface Aviator
package com.yan.aop.aviator;
import java.lang.annotation.*;
/**
* @Author yan
* @Date 2024/11/16 下午7:43:04
* @Description
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Aviator {
/**
* 解析异常是否抛出异常
* @return
*/
boolean throwException() default true;
String defaultErrorMessage() default "参数不符合要求"; // 错误信息
boolean isStr() default false;
String[] keys() default {};
String[] expressions() default {}; // Aviator表达式
String[] errorMessages() default {"参数不符合要求"}; // 错误信息
}
3.2 @interface AviatorValid
package com.yan.aop.aviator;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @Author yan
* @Date 2024/11/12 上午1:30:05
* @Description
*/
@Aviator
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AviatorValid {
/**
* 解析异常是否抛出异常
* @return
*/
boolean throwException() default true;
String expression(); // Aviator表达式
String errorMessage() default "参数不符合要求"; // 错误信息
}
3.3 @interface AviatorNotBlank
package com.yan.aop.aviator;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @Author yan
* @Date 2024/11/12 下午2:53:34
* @Description
*/
@Aviator
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AviatorNotBlank {
/**
* 解析异常是否抛出异常
* @return
*/
boolean throwException() default true;
String key();
String errorMessage() default "参数不符合要求"; // 错误信息
}
3.4 @interface AviatorValids
package com.yan.aop.aviator;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @Author yan
* @Date 2024/11/12 上午1:30:05
* @Description
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AviatorValids {
Aviator[] aviator() default {};
AviatorValid[] values() default {};
AviatorNotBlank[] notBlanks() default {};
}
3.5 class AviatorValidInfo
package com.yan.aop.aviator;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
/**
* @Author yan
* @Date 2024/11/12 下午2:59:52
* @Description
*/
@Data @Accessors(chain = true)
@NoArgsConstructor
@AllArgsConstructor
public class AviatorValidInfo {
//抛出异常
private boolean throwException = true;
//expression
private String expression;
//异常信息
private String errorMessage;
}
4.com.yan.utils
4.1 class ObjectUtils
package com.yan.utils.object;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONConfig;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.google.common.collect.Maps;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.TreeMap;
/**
* @Author yan
* @Date 2024/11/2 下午9:20:38
* @Description
*/
public class ObjectUtils extends ObjectUtil {
public static <T> T defaultIfEmpty(T object, T defaultValue) {
return ObjectUtil.isEmpty(object) ? defaultValue : object;
}
/**
* @param compareTo
* @return
*/
public static Map<String, Object> mapKeyLengthReversed(Map<String, Object> compareTo) {
TreeMap<String, Object> treeMap = new TreeMap<>(Comparator.comparingInt(String::length).reversed().thenComparing(String::compareTo));
treeMap.putAll(compareTo);
return treeMap;
}
/**
* @param compareTo
* @return
*/
public static Map<String, Object> mapKeyLengthSort(Map<String, Object> compareTo) {
TreeMap<String, Object> treeMap = new TreeMap(Comparator.comparingInt(String::length).thenComparing(String::compareTo));
treeMap.putAll(compareTo);
return treeMap;
}
public static Map<String, Object> toMap(Object obj) {
return toMap(null, obj, null, null);
}
/**
* obj 转 map
* obj:{"a":"a","b":"{\"a\":\"a\",\"b\":\"a\"}","c":"[{\"a\":\"a\",\"b\":\"a\"}]"}
* ==>
* map:{"a":"a","b.a":"a","b.b":"a","c[0].a":"a","c[0].b":"a"}
*
* @param map
* @param obj
* @param prx
* @param level
* @return
*/
public static Map<String, Object> toMap(Map<String, Object> map, Object obj, String prx, Integer level) {
JSONConfig jsonConfig = JSONConfig.create().setIgnoreNullValue(false);
prx = StrUtil.isBlank(prx) ? StrUtil.EMPTY : prx;
map = ObjectUtils.defaultIfNull(map, new LinkedHashMap<>());
level = ObjectUtils.defaultIfNull(level, 0);
String jsonObjectJsonStr = JSONUtil.toJsonStr(obj, jsonConfig);
if (JSONUtil.isTypeJSON(jsonObjectJsonStr)) {
JSONObject jsonObject = new JSONObject(jsonObjectJsonStr);
for (Map.Entry<String, Object> entry : jsonObject.entrySet()) {
String key = entry.getKey();
Object value = entry.getValue();
String fullKey = StrUtil.isBlank(prx) ? key : prx + "." + key;
String toJsonStr = JSONUtil.toJsonStr(value, jsonConfig);
if (JSONUtil.isTypeJSONArray(toJsonStr)) {
// Handle JSONArray type
JSONArray array = new JSONArray(toJsonStr);
for (int i = 0; i < array.size(); i++) {
Object arrayValue = array.get(i);
level++;
toMap(map, arrayValue, fullKey + "[" + i + "]", level);
level--;
}
} else if (JSONUtil.isTypeJSON(toJsonStr)) {
// Handle nested JSON object
level++;
toMap(map, value, fullKey, level);
level--;
} else {
// Handle simple value
map.put(fullKey, value);
prx = removeLastSegment(prx);
}
}
} else {
// Handle non-JSON value
map.put(prx, obj);
prx = StrUtil.EMPTY;
}
return map;
}
public static String removeLastSegment(String prx) {
String separator = ".";
if (prx.contains(separator)) {
int lastIndex = prx.lastIndexOf(separator);
return prx.substring(0, lastIndex);
}
return prx;
}
public static Map<String, Object> blankReplace(Map<String, Object> objMap) {
objMap = ObjectUtils.defaultIfEmpty(objMap, Maps.newLinkedHashMap());
for (Map.Entry<String, Object> entry : objMap.entrySet()) {
String key = entry.getKey();
Object value = entry.getValue();
if (ObjectUtils.equals(StrUtil.EMPTY, value)) {
objMap.put(key, "''");
}
}
return objMap;
}
public static void main(String[] args) {
Map<String, Object> hashMap = Maps.newLinkedHashMap();
hashMap.put("as", "");
System.out.println(hashMap);
hashMap = blankReplace(hashMap);
System.out.println(hashMap);
}
}
5.com.yan.aop.aspect
5.1 interface AbstractAviatorValidAspect
package com.yan.aop.aspect;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.json.JSONUtil;
import com.googlecode.aviator.AviatorEvaluator;
import com.yan.aop.aviator.*;
import com.yan.utils.object.ObjectUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import com.yan.abstractinterface.bean.AbstractBean;
import java.lang.reflect.Parameter;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* @Author yan
* @Date 2024/11/12 下午3:09:40
* @Description
*/
public interface AbstractAviatorValidAspect extends AbstractBean {
JSONConfig jsonConfig = JSONConfig.create().setIgnoreNullValue(false);
@SneakyThrows
default <T extends Annotation> T getAnnotation(JoinPoint joinPoint, Class<T> annotationClass) {
Signature signature = joinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
Method method = methodSignature.getMethod();
T annotation = null;
if (ObjectUtils.isNotEmpty(method)) {
annotation = method.getAnnotation(annotationClass);
}
return annotation;
}
/**
*
*/
@Pointcut(value = "@annotation(com.yan.aop.aviator.AviatorValids)"
+ " || @annotation(com.yan.aop.aviator.AviatorValid) "
+ " || @annotation(com.yan.aop.aviator.AviatorNotBlank) "
+ " || @annotation(com.yan.aop.aviator.Aviator) "
)
default void SysLog() {
}
default void check(ProceedingJoinPoint joinPoint) throws Exception {
Logger log = getLogger();
Aviator aviator = getAnnotation(joinPoint, Aviator.class);
AviatorValids aviatorValids = getAnnotation(joinPoint, AviatorValids.class);
AviatorValid aviatorValid = getAnnotation(joinPoint, AviatorValid.class);
AviatorNotBlank aviatorNotBlank = getAnnotation(joinPoint, AviatorNotBlank.class);
Aviator[] aviators = null;
AviatorValid[] valids = null;
AviatorNotBlank[] notBlanks = null;
List<AviatorValidInfo> validInfos = CollUtil.newArrayList();
if (ObjectUtils.isNotEmpty(aviator)) {
aviators = new Aviator[]{aviator};
}else if (ObjectUtils.isNotEmpty(aviatorValids)) {
aviators = aviatorValids.aviator();
valids = aviatorValids.values();
notBlanks = aviatorValids.notBlanks();
} else if (ObjectUtils.isNotEmpty(aviatorValid)) {
valids = new AviatorValid[]{aviatorValid};
} else if (ObjectUtils.isNotEmpty(aviatorNotBlank)) {
notBlanks = new AviatorNotBlank[]{aviatorNotBlank};
}
if (ObjectUtils.isNotEmpty(aviators)) {
Arrays.stream(aviators)
.map(this::aviatorToValidInfos)
.forEach(validInfos::addAll);
//List<AviatorValidInfo> infoList = aviatorToValidInfos(aviator);
//validInfos.addAll(infoList);
}
if (ObjectUtils.isNotEmpty(notBlanks)) {
List<AviatorValidInfo> infos = Arrays.stream(notBlanks)
.map(this::notBlankToValidInfo)
.collect(Collectors.toList());
validInfos.addAll(infos);
}
if (ObjectUtils.isNotEmpty(valids)) {
List<AviatorValidInfo> infos = Arrays.stream(valids).map(this::validToValidInfo).collect(Collectors.toList());
validInfos.addAll(infos);
}
if (CollUtil.isNotEmpty(validInfos)) {
Map<String, Object> variables = new LinkedHashMap<>();
//Map<String, Class> classMaps = new LinkedHashMap<>();
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Parameter[] parameters = methodSignature.getMethod().getParameters();
Object[] args = joinPoint.getArgs();
for (int i = 0; i < parameters.length; i++) {
Object argValue = args[i];
String name = parameters[i].getName();
String argsName = String.format("args[%s]", i);
argValue = JSONUtil.toJsonStr(argValue, jsonConfig);
variables.put(argsName, argValue);
variables.put(name, argValue);
//classMaps.put(argsName, (Class) parameters[i].getParameterizedType());
}
Map<String, Object> variablesMap = ObjectUtils.mapKeyLengthReversed(ObjectUtils.toMap(variables));
variablesMap = ObjectUtils.blankReplace(variablesMap);
log.info("parameters参数:{}", JSONUtil.toJsonStr(parameters, jsonConfig));
log.info("variables参数:{}", variables);
log.info("variablesMap参数:{}", variablesMap);
//log.info("classMaps参数:{}", classMaps);
for (AviatorValidInfo validInfo : validInfos) {
execute(variablesMap, validInfo);
}
}
}
default List<AviatorValidInfo> aviatorToValidInfos(Aviator aviator) {
boolean throwException = aviator.throwException();
String defaultErrorMessage = aviator.defaultErrorMessage();
boolean isStr = aviator.isStr();
List<String> errorMessages = Arrays.stream(aviator.errorMessages()).collect(Collectors.toList());
List<String> expressions = Arrays.stream(aviator.expressions()).collect(Collectors.toList());
int errorSize = errorMessages.size();
int okSize;
if (isStr) {
List<String> keys = Arrays.stream(aviator.keys()).collect(Collectors.toList());
okSize = keys.size();
expressions = keys.stream().map(key -> String.format("%s!=nil&&%s!=''", key, key)).collect(Collectors.toList());
} else {
okSize = expressions.size();
}
if (okSize > errorSize) {
for (int i = 0; i < (okSize - errorSize); i++) {
errorMessages.add(defaultErrorMessage);
}
}
List<AviatorValidInfo> infoList = CollUtil.newArrayList();
for (int i = 0; i < okSize; i++) {
String expression = replaceNullToNil(expressions.get(i));
String errorMessage = errorMessages.get(i);
infoList.add(toAviatorValidInfo(throwException, expression, errorMessage));
}
return infoList;
}
/**
* @param notBlank
* @return
*/
default AviatorValidInfo notBlankToValidInfo(AviatorNotBlank notBlank) {
String errorMessage = notBlank.errorMessage();
String key = notBlank.key();
boolean throwException = notBlank.throwException();
String expression = String.format("%s!=null&&%s!=''", key, key);
expression = replaceNullToNil(expression);
return toAviatorValidInfo(throwException, expression, errorMessage);
}
/**
* @param valid
* @return
*/
default AviatorValidInfo validToValidInfo(AviatorValid valid) {
boolean throwException = valid.throwException();
String expression = valid.expression();
String errorMessage = valid.errorMessage();
// null AviatorEvaluator 无法识别 null==> nil
expression = replaceNullToNil(expression);
return toAviatorValidInfo(throwException, expression, errorMessage);
}
default AviatorValidInfo toAviatorValidInfo(boolean throwException, String expression, String errorMessage) {
return new AviatorValidInfo()
.setThrowException(throwException)
.setExpression(expression)
.setErrorMessage(errorMessage);
}
/**
* @param variablesMap
* @param validInfo
* @throws Exception
*/
default void execute(Map<String, Object> variablesMap, AviatorValidInfo validInfo) throws Exception {
Logger log = getLogger();
Boolean validFail = null;
boolean expressionThrowException = validInfo.isThrowException();
String expression = validInfo.getExpression();
String errorMessage = validInfo.getErrorMessage();
//expression = expressionReplace(expression, variablesMap);
// 使用 Aviator 进行表达式校验
try {
log.info("aviator表达式:{}", expression);
Boolean bool = !(Boolean) AviatorEvaluator.execute(expression, variablesMap);
validFail = ObjectUtils.defaultIfEmpty(bool, true);
} catch (Exception e) {
log.error("aviator表达式解析异常:{}", e.getMessage());
if (expressionThrowException) {
throw new Exception("aviator表达式解析异常:" + e.getMessage());
}
}
validFail = ObjectUtils.defaultIfEmpty(validFail, true);
if (validFail) {
log.error("aviator表达式校验失败:{}", errorMessage);
throw new Exception(errorMessage);
}
}
default String expressionReplace(String expression, Map<String, Object> variablesMap) {
for (Map.Entry<String, Object> entry : variablesMap.entrySet()) {
String key = entry.getKey();
Object value = entry.getValue();
if (ObjectUtils.isNotEmpty(value)) {
expression = expression.replace(key, JSONUtil.toJsonStr(value, jsonConfig));
}
}
return expression;
}
@NotNull
default String replaceNullToNil(String expression) {
return expression.replace("null", "nil");
}
}
5.2 class AviatorValidAspect
package com.yan.aop.aspect;
import cn.hutool.json.JSONConfig;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.googlecode.aviator.AviatorEvaluator;
import com.yan.utils.object.ObjectUtils;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* @Author yan
* @Date 2024/11/12 上午1:32:53
* @Description
*/
@Aspect
@Slf4j
@Component
public class AviatorValidAspect implements AbstractAviatorValidAspect {
/**
* @param joinPoint
* @return
* @throws Throwable
*/
@Override
@Around(value = "SysLog()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
check(joinPoint);
return joinPoint.proceed();
}
}
6.使用
package com.yan.controller;
import cn.hutool.extra.spring.SpringUtil;
import com.yan.abstractinterface.service.AbstractLoginService;
import com.yan.aop.aviator.Aviator;
import com.yan.aop.aviator.AviatorNotBlank;
import com.yan.aop.aviator.AviatorValid;
import com.yan.aop.aviator.AviatorValids;
import com.yan.aop.log.SysLog;
import com.yan.dto.LoginDto;
import com.yan.dto.RegisterDto;
import com.yan.pojo.TokenInfo;
import com.yan.pojo.User;
import com.yan.pojo.UserInfo;
import com.yan.result.Result;
import com.yan.service.ValidateCodeService;
import com.yan.utils.shiro.SecurityContextUtil;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletResponse;
import javax.validation.constraints.NotNull;
/**
* @Author yan
* @Date 2024/11/5 下午11:04:45
* @Description
*/
@RestController
@Tag(name = "登陆注册模块")
@RequestMapping(value = {"/api/auth", "/jwt/auth"})
public class AuthController implements AbstractBaseController {
/**
* 查询用户信息列表
*/
@SysLog(title = "登陆")
@Operation(summary = "登陆")
@PostMapping("/login")
@AviatorValids(
notBlanks = {
@AviatorNotBlank(key = "dto.username", errorMessage = "用户名不能为空"),
@AviatorNotBlank(key = "dto.password", errorMessage = "密码不能为空"),
@AviatorNotBlank(key = "dto.code", errorMessage = "验证码不能为空"),
@AviatorNotBlank(key = "dto.uuid", errorMessage = "验证码唯一值不能为空"),
},
values = {
@AviatorValid(expression = "dto.captchaEnabled==true && dto.code!=null && dto.code!=''", errorMessage = "验证码不能为空"),
}
)
public Result<TokenInfo> login(@RequestBody LoginDto dto, HttpServletResponse response) {
SpringUtil.getBean(ValidateCodeService.class)
.checkCaptcha(dto.getCode(), dto.getUuid());
UserInfo userInfo = new UserInfo().setPassword(dto.getPassword()).setUsername(dto.getUsername());
TokenInfo tokenInfo = SpringUtil.getBean(AbstractLoginService.class).login(userInfo);
response.setHeader(tokenInfo.getTokenName(), tokenInfo.getToken());
if (tokenInfo.getEnableTwoToken()) {
response.setHeader(tokenInfo.getRefreshTokenName(), tokenInfo.getRefreshToken());
}
return ok(tokenInfo);
}
@SysLog(title = "注册")
@Operation(summary = "注册")
@PostMapping("/register")
@AviatorValids(values = {
@AviatorValid(expression = "dto.password != dto.password2", errorMessage = "密码不一致")
})
public Result<User> register(@Validated @NotNull @RequestBody RegisterDto dto) {
User register = SpringUtil.getBean(AbstractLoginService.class)
.register(dto.getNickname(), dto.getPassword(), dto.getPassword2());
return ok(register);
}
@SysLog(title = "登出")
@Operation(summary = "登出")
@PostMapping("/logout")
public Result logout() {
SecurityContextUtil.logout();
return ok();
}
}