初始化项目

This commit is contained in:
2026-08-06 15:19:45 +08:00
commit 63c8d8b757
229 changed files with 12653 additions and 0 deletions
@@ -0,0 +1,28 @@
package xtools.boot.idempotent.base;
import org.springframework.context.annotation.Import;
import xtools.boot.core.utils.ModuleLoadUtils;
import xtools.boot.idempotent.base.selector.BootIdempotentBaseImportSelector;
/**
* <p>Title : BootIdempotentBaseConfiguration</p>
* <p>Description : BootIdempotentBaseConfiguration</p>
* <p>DevelopTools : Idea_x64_v2026.1</p>
* <p>DevelopSystem : macOS Sequoia 15.7.5</p>
* <p>Company : org.xujun</p>
*
* @author : XuJun
* @version : 5.0.0
* @date : 2026/07/29 10:00
*/
@Import(BootIdempotentBaseImportSelector.class)
public class BootIdempotentBaseConfiguration {
/**
* 构造方法
*/
public BootIdempotentBaseConfiguration() {
ModuleLoadUtils.loadSuccess(BootIdempotentBaseConfiguration.class);
}
}
@@ -0,0 +1,61 @@
package xtools.boot.idempotent.base.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* <p>Title : Idempotent</p>
* <p>Description : Idempotent</p>
* <p>DevelopTools : Idea_x64_v2026.1</p>
* <p>DevelopSystem : macOS Sequoia 15.7.5</p>
* <p>Company : org.xujun</p>
*
* @author : XuJun
* @version : 5.0.0
* @date : 2026/07/28 09:30
*/
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Idempotent {
/**
* Redis Key 前缀
*/
String prefix() default "idempotent";
/**
* 幂等 Key(SpEL 表达式),为空时使用默认策略: 类名+方法名+参数摘要
* 如: #orderId, #p0, #req.id
*
* @return SpEL 表达式
*/
String key() default "";
/**
* 幂等有效时间(秒)
*
* @return 有效时间
*/
long expire() default 5;
/**
* 重复提交提示信息
*
* @return 提示信息
*/
String message() default "请勿重复提交";
/**
* 是否在方法执行成功后立即删除 Key(放行下一次提交).
* false: Key 保留至 expire 过期,实现时间窗口内的幂等(默认)
* true: 方法成功后立即释放,仅防止并发重复提交
*
* @return 是否立即删除 Key
*/
boolean delKey() default false;
}
@@ -0,0 +1,65 @@
package xtools.boot.idempotent.base.aspect;
import lombok.RequiredArgsConstructor;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
import xtools.boot.api.exection.BizWarning;
import xtools.boot.idempotent.base.annotation.Idempotent;
import xtools.boot.idempotent.base.key.IdempotentKeyResolver;
import xtools.boot.idempotent.base.service.IdempotentService;
/**
* <p>Title : IdempotentAspect</p>
* <p>Description : IdempotentAspect</p>
* <p>DevelopTools : Idea_x64_v2026.1</p>
* <p>DevelopSystem : macOS Sequoia 15.7.5</p>
* <p>Company : org.xujun</p>
*
* @author : XuJun
* @version : 5.0.0
* @date : 2026/07/28 09:30
*/
@Aspect
@Component
@RequiredArgsConstructor
public class IdempotentAspect {
/**
* 幂等服务
*/
private final IdempotentService idempotentService;
/**
* 幂等 Key 解析器
*/
private final IdempotentKeyResolver idempotentKeyResolver;
/**
* 幂等拦截
*
* @param joinPoint 连接点
* @param idempotent 幂等注解
* @return 方法执行结果
* @throws Throwable 方法执行异常
*/
@Around("@annotation(idempotent)")
public Object around(ProceedingJoinPoint joinPoint, Idempotent idempotent) throws Throwable {
// 解析 Key
String key = idempotentKeyResolver.resolve(joinPoint, idempotent);
// 尝试获取幂等锁,失败则视为重复提交
boolean acquired = idempotentService.tryAcquire(key, idempotent.expire());
if (!acquired) {
throw new BizWarning(idempotent.message());
}
// 执行业务
Object result = joinPoint.proceed();
if (idempotent.delKey()) {
// 成功后立即释放
idempotentService.release(key);
}
return result;
}
}
@@ -0,0 +1,115 @@
package xtools.boot.idempotent.base.key;
import com.alibaba.fastjson2.JSON;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import xtools.boot.idempotent.base.annotation.Idempotent;
import xtools.core.StringUtils;
import xtools.core.encrypt.Md5Utils;
import java.lang.reflect.Method;
import java.util.Arrays;
import static xtools.base.config.Constants.CP_COLON;
import static xtools.base.config.Constants.CP_EMPTY;
import static xtools.base.config.Constants.CP_NUM0;
/**
* <p>Title : IdempotentKeyResolver</p>
* <p>Description : IdempotentKeyResolver</p>
* <p>DevelopTools : Idea_x64_v2026.1</p>
* <p>DevelopSystem : macOS Sequoia 15.7.5</p>
* <p>Company : org.xujun</p>
*
* @author : XuJun
* @version : 5.0.0
* @date : 2026/07/28 09:30
*/
@Component
public class IdempotentKeyResolver {
/**
* SpEL 解析器
*/
private static final ExpressionParser PARSER = new SpelExpressionParser();
/**
* 参数名发现器
*/
private static final ParameterNameDiscoverer NAME_DISCOVERER = new DefaultParameterNameDiscoverer();
/**
* 解析幂等 Key
*
* @param joinPoint 连接点
* @param idempotent 幂等注解
* @return Redis Key
*/
public String resolve(JoinPoint joinPoint, Idempotent idempotent) {
// 获取目标方法与参数
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
Object[] args = joinPoint.getArgs();
Object target = joinPoint.getTarget();
// Key 片段:配置了 SpEL 则按 SpEL 求值,否则使用默认策略
String keyPart;
if (StringUtils.isBlank(idempotent.key())) {
keyPart = defaultKey(target.getClass(), method, args);
} else {
keyPart = spelKey(method, args, target, idempotent.key());
}
return idempotent.prefix() + CP_COLON + keyPart;
}
/**
* 默认 Key:类名 + 方法名 + 参数摘要
*
* @param targetClass 目标类
* @param method 方法
* @param args 参数
* @return Key 片段
*/
private String defaultKey(Class<?> targetClass, Method method, Object[] args) {
String argsDigest = Md5Utils.encryptToString(JSON.toJSONString(Arrays.asList(args)));
return targetClass.getSimpleName() + CP_COLON + method.getName() + CP_COLON + argsDigest;
}
/**
* SpEL Key:支持 #参数名,#p0,#a0 及 root 对象属性
*
* @param method 方法
* @param args 参数
* @param target 目标对象
* @param spel SpEL 表达式
* @return Key 片段
*/
private String spelKey(Method method, Object[] args, Object target, String spel) {
// 构建求值上下文,root 对象为目标对象
StandardEvaluationContext context = new StandardEvaluationContext(target);
context.setRootObject(target);
// 绑定参数名与索引别名(#p0,#a0)
String[] paramNames = NAME_DISCOVERER.getParameterNames(method);
if (paramNames != null) {
for (int i = CP_NUM0; i < paramNames.length && i < args.length; i++) {
context.setVariable(paramNames[i], args[i]);
}
}
for (int i = CP_NUM0; i < args.length; i++) {
context.setVariable("p" + i, args[i]);
context.setVariable("a" + i, args[i]);
}
// 解析表达式
Expression expression = PARSER.parseExpression(spel);
Object value = expression.getValue(context);
return value == null ? CP_EMPTY : value.toString();
}
}
@@ -0,0 +1,36 @@
package xtools.boot.idempotent.base.selector;
import org.jspecify.annotations.NonNull;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;
/**
* <p>Title : BootIdempotentBaseImportSelector</p>
* <p>Description : BootIdempotentBaseImportSelector</p>
* <p>DevelopTools : Idea_x64_v2026.1</p>
* <p>DevelopSystem : macOS Sequoia 15.7.5</p>
* <p>Company : org.xujun</p>
*
* @author : XuJun
* @version : 5.0.0
* @date : 2026/07/29 10:00
*/
public class BootIdempotentBaseImportSelector implements ImportBeanDefinitionRegistrar {
/**
* 根据给定的注释元数据,根据需要注册bean
*
* @param importingClassMetadata AnnotationMetadata
* @param registry BeanDefinitionRegistry
*/
@Override
public void registerBeanDefinitions(@NonNull AnnotationMetadata importingClassMetadata, @NonNull BeanDefinitionRegistry registry) {
// 构建扫描对象
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(registry, true);
// 扫描核心层包
scanner.scan("xtools.boot.idempotent.base");
}
}
@@ -0,0 +1,58 @@
package xtools.boot.idempotent.base.service;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import xtools.boot.cache.redis.base.RedisService;
import xtools.core.StringUtils;
import xtools.core.UuidUtils;
import java.util.concurrent.TimeUnit;
/**
* <p>Title : IdempotentService</p>
* <p>Description : IdempotentService</p>
* <p>DevelopTools : Idea_x64_v2026.1</p>
* <p>DevelopSystem : macOS Sequoia 15.7.5</p>
* <p>Company : org.xujun</p>
*
* @author : XuJun
* @version : 5.0.0
* @date : 2026/07/28 09:30
*/
@Component
@RequiredArgsConstructor
public class IdempotentService {
private final RedisService redisService;
/**
* 尝试获取幂等锁
*
* @param key 锁 Key
* @param expire 有效时间(时间窗口)
* @return true or false
*/
public boolean tryAcquire(String key, long expire) {
// 参数校验
if (StringUtils.isBlank(key) || expire <= 0) {
return true;
}
Boolean result = redisService.setNx(key, UuidUtils.get(), expire, TimeUnit.SECONDS);
return Boolean.TRUE.equals(result);
}
/**
* 释放幂等锁
*
* @param key 锁 Key
* @return true or false
*/
public boolean release(String key) {
if (StringUtils.isBlank(key)) {
return false;
}
Boolean result = redisService.del(key);
return Boolean.TRUE.equals(result);
}
}
@@ -0,0 +1 @@
xtools.boot.idempotent.base.BootIdempotentBaseConfiguration