refactor(idempotent): 重构幂等模块并支持 Redisson 缓存

This commit is contained in:
2025-03-17 21:25:21 +08:00
parent b199b651ec
commit 27a71cf076
16 changed files with 308 additions and 498 deletions

View File

@@ -13,45 +13,15 @@
<description>ContiNew Starter 幂等模块</description>
<dependencies>
<!-- Spring Boot Starter自动配置相关依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- 缓存模块 - Redisson -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<groupId>top.continew</groupId>
<artifactId>continew-starter-cache-redisson</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

View File

@@ -20,27 +20,39 @@ import java.lang.annotation.*;
import java.util.concurrent.TimeUnit;
/**
* 定义幂等注解
* 幂等注解
*
* @version 1.0
* @Author loach
* @Date 2025-03-07 19:26
* @Package top.continew.starter.idempotent.annotation.Idempotent
* @author loach
* @author Charles7c
* @since 2.10.0
*/
@Documented
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Idempotent {
// 幂等key前缀
String prefix() default "idempotent:";
/**
* 名称
*/
String name() default "";
// key的过期时间
long timeout() default 0;
/**
* 键(支持 Spring EL 表达式)
*/
String key() default "";
// 时间单位
TimeUnit timeUnit() default TimeUnit.SECONDS;
/**
* 超时时间
*/
int timeout() default 1;
// 失败时的提示信息
/**
* 时间单位(默认:毫秒)
*/
TimeUnit unit() default TimeUnit.MILLISECONDS;
/**
* 提示信息
*/
String message() default "请勿重复操作";
}

View File

@@ -0,0 +1,107 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.idempotent.aop;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.ObjectUtil;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import top.continew.starter.cache.redisson.util.RedisUtils;
import top.continew.starter.core.util.expression.ExpressionUtils;
import top.continew.starter.idempotent.annotation.Idempotent;
import top.continew.starter.idempotent.autoconfigure.IdempotentProperties;
import top.continew.starter.idempotent.exception.IdempotentException;
import top.continew.starter.idempotent.generator.IdempotentNameGenerator;
import java.lang.reflect.Method;
import java.time.Duration;
/**
* 幂等切面
*
* @author loach
* @author Charles7c
* @since 2.10.0
*/
@Aspect
public class IdempotentAspect {
private final IdempotentProperties properties;
private final IdempotentNameGenerator nameGenerator;
public IdempotentAspect(IdempotentProperties properties, IdempotentNameGenerator nameGenerator) {
this.properties = properties;
this.nameGenerator = nameGenerator;
}
/**
* 幂等处理
*
* @param joinPoint 切点
* @param idempotent 幂等注解
* @return 目标方法的执行结果
* @throws Throwable /
*/
@Around("@annotation(idempotent)")
public Object around(ProceedingJoinPoint joinPoint, Idempotent idempotent) throws Throwable {
String cacheKey = this.getCacheKey(joinPoint, idempotent);
// 如果键已存在,则抛出异常
if (!RedisUtils.setIfAbsent(cacheKey, Duration.ofMillis(idempotent.unit().toMillis(idempotent.timeout())))) {
throw new IdempotentException(idempotent.message());
}
// 执行目标方法
try {
return joinPoint.proceed();
} catch (Throwable e) {
// 删除键
RedisUtils.delete(cacheKey);
throw e;
}
}
/**
* 获取缓存 Key
*
* @param joinPoint 切点
* @param idempotent 幂等注解
* @return 缓存 Key
*/
private String getCacheKey(ProceedingJoinPoint joinPoint, Idempotent idempotent) {
Object target = joinPoint.getTarget();
MethodSignature methodSignature = (MethodSignature)joinPoint.getSignature();
Method method = methodSignature.getMethod();
Object[] args = joinPoint.getArgs();
// 获取名称
String name = idempotent.name();
if (CharSequenceUtil.isBlank(name)) {
name = nameGenerator.generate(target, method, args);
}
// 解析 Key
String key = idempotent.key();
if (CharSequenceUtil.isNotBlank(key)) {
Object eval = ExpressionUtils.eval(key, target, method, args);
if (ObjectUtil.isNull(eval)) {
throw new IdempotentException("幂等 Key 解析错误");
}
key = Convert.toStr(eval);
}
return RedisUtils.formatKey(properties.getKeyPrefix(), name, key);
}
}

View File

@@ -1,71 +0,0 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.idempotent.aspect;
import jakarta.servlet.http.HttpServletRequest;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import top.continew.starter.idempotent.annotation.Idempotent;
import top.continew.starter.idempotent.service.IdempotentService;
import top.continew.starter.idempotent.util.IdempotentKeyGenerator;
/**
* 注解切面
*
* @version 1.0
* @Author loach
* @Date 2025-03-07 19:27
* @Package top.continew.starter.idempotent.aspect.IdempotentAspect
*/
@Aspect
public class IdempotentAspect {
private final IdempotentService idempotentService;
public IdempotentAspect(IdempotentService idempotentService) {
this.idempotentService = idempotentService;
}
/**
* AspectJ 环绕通知
*
* @param joinPoint 切点
* @param idempotent 注解
* @return
* @throws Throwable
*/
@Around("@annotation(idempotent)")
public Object around(ProceedingJoinPoint joinPoint, Idempotent idempotent) throws Throwable {
HttpServletRequest request = ((ServletRequestAttributes)RequestContextHolder.getRequestAttributes())
.getRequest();
String key = IdempotentKeyGenerator.generateKey(idempotent.prefix(), joinPoint, request);
try {
if (!idempotentService.checkAndLock(key, idempotent.timeout())) {
throw new RuntimeException(idempotent.message());
}
return joinPoint.proceed();
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
idempotentService.unlock(key);
}
}
}

View File

@@ -0,0 +1,73 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.idempotent.autoconfigure;
import jakarta.annotation.PostConstruct;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.core.ResolvableType;
import top.continew.starter.core.constant.PropertiesConstants;
import top.continew.starter.idempotent.aop.IdempotentAspect;
import top.continew.starter.idempotent.generator.IdempotentNameGenerator;
/**
* 幂等自动配置
*
* @author loach
* @author Charles7c
* @since 2.10.0
*/
@AutoConfiguration
@EnableConfigurationProperties(IdempotentProperties.class)
@ConditionalOnProperty(prefix = PropertiesConstants.IDEMPOTENT, name = PropertiesConstants.ENABLED, havingValue = "true", matchIfMissing = true)
public class IdempotentAutoConfiguration {
private static final Logger log = LoggerFactory.getLogger(IdempotentAutoConfiguration.class);
/**
* 幂等切面
*/
@Bean
public IdempotentAspect idempotentAspect(IdempotentProperties properties,
IdempotentNameGenerator idempotentNameGenerator) {
return new IdempotentAspect(properties, idempotentNameGenerator);
}
/**
* 幂等名称生成器
*/
@Bean
@ConditionalOnMissingBean
public IdempotentNameGenerator idempotentNameGenerator() {
if (log.isErrorEnabled()) {
log.error("Consider defining a bean of type '{}' in your configuration.", ResolvableType
.forClass(IdempotentNameGenerator.class));
}
throw new NoSuchBeanDefinitionException(IdempotentNameGenerator.class);
}
@PostConstruct
public void postConstruct() {
log.debug("[ContiNew Starter] - Auto Configuration 'Idempotent' completed initialization.");
}
}

View File

@@ -0,0 +1,44 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.idempotent.autoconfigure;
import org.springframework.boot.context.properties.ConfigurationProperties;
import top.continew.starter.core.constant.PropertiesConstants;
/**
* 幂等配置属性
*
* @author loach
* @author Charles7c
* @since 2.10.0
*/
@ConfigurationProperties(PropertiesConstants.IDEMPOTENT)
public class IdempotentProperties {
/**
* Key 前缀
*/
private String keyPrefix = "Idempotent";
public String getKeyPrefix() {
return keyPrefix;
}
public void setKeyPrefix(String keyPrefix) {
this.keyPrefix = keyPrefix;
}
}

View File

@@ -1,73 +0,0 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.idempotent.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.StringRedisTemplate;
import top.continew.starter.idempotent.aspect.IdempotentAspect;
import top.continew.starter.idempotent.service.IdempotentService;
import top.continew.starter.idempotent.service.impl.MemoryIdempotentServiceImpl;
import top.continew.starter.idempotent.service.impl.RedisIdempotentServiceImpl;
/**
* 引用配置暂定默认内存实现扫描到启用redis 使用redis实现
*
* @version 1.0
* @Author loach
* @Date 2025-03-07 20:03
* @Package top.continew.starter.idempotent.config.IdempotentAutoConfiguration
*/
@Configuration
@EnableConfigurationProperties(IdempotentProperties.class)
public class IdempotentAutoConfiguration {
@Autowired
private IdempotentService idempotentService;
private final IdempotentProperties properties;
public IdempotentAutoConfiguration(IdempotentProperties properties) {
this.properties = properties;
}
@Bean
public IdempotentAspect idempotentAspect(IdempotentService idempotentService) {
return new IdempotentAspect(idempotentService);
}
@Bean(name = "redisIdempotentService")
@ConditionalOnBean(StringRedisTemplate.class)
public IdempotentService redisIdempotentService(StringRedisTemplate redisTemplate) {
return new RedisIdempotentServiceImpl(redisTemplate, properties.getRedisTimeout());
}
@Bean
@ConditionalOnMissingBean(IdempotentService.class)
public IdempotentService memoryIdempotentService() {
return new MemoryIdempotentServiceImpl(properties.getCleanInterval());
}
@Bean
public IdempotentProperties idempotentProperties() {
return new IdempotentProperties();
}
}

View File

@@ -1,53 +0,0 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.idempotent.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* 属性配置类
*
* @version 1.0
* @Author loach
* @Date 2025-03-07 19:27
* @Package top.continew.starter.idempotent.config.IdempotentProperties
*/
@ConfigurationProperties(prefix = "idempotent")
public class IdempotentProperties {
// 内存实现清理过期 key 的间隔毫秒默认5分钟
private long cleanInterval = 300000;
// redis实现过期 key 的间隔毫秒默认5分钟
private long redisTimeout = 300000;
public long getCleanInterval() {
return cleanInterval;
}
public void setCleanInterval(long cleanInterval) {
this.cleanInterval = cleanInterval;
}
public long getRedisTimeout() {
return redisTimeout;
}
public void setRedisTimeout(long redisTimeout) {
this.redisTimeout = redisTimeout;
}
}

View File

@@ -1,49 +0,0 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.idempotent.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import top.continew.starter.idempotent.service.IdempotentService;
import top.continew.starter.idempotent.service.impl.MemoryIdempotentServiceImpl;
/**
* 内存定时任务配置类
*
* @version 1.0
* @Author loach
* @Date 2025-03-07 22:26
* @Package top.continew.starter.idempotent.config.MemoryIdempotentSchedulingConfig
*/
@Configuration
@ConditionalOnMissingBean(name = "redisIdempotentService") // 当 Redis Bean 不存在时启用
@EnableScheduling
public class MemoryIdempotentSchedulingConfig {
@Autowired
private IdempotentService idempotentService;
@Scheduled(fixedRateString = "${idempotent.clean-interval:60000}")
public void cleanExpiredKeys() {
if (idempotentService instanceof MemoryIdempotentServiceImpl) {
((MemoryIdempotentServiceImpl)idempotentService).cleanExpiredKeys();
}
}
}

View File

@@ -0,0 +1,36 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.idempotent.exception;
import top.continew.starter.core.exception.BaseException;
/**
* 幂等异常
*
* @author Charles7c
* @since 2.10.0
*/
public class IdempotentException extends BaseException {
public IdempotentException(String message) {
super(message);
}
public IdempotentException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@@ -14,31 +14,26 @@
* limitations under the License.
*/
package top.continew.starter.idempotent.service;
package top.continew.starter.idempotent.generator;
import java.lang.reflect.Method;
/**
* 服务接口
* 幂等名称生成器
*
* @version 1.0
* @Author loach
* @Date 2025-03-07 19:48
* @Package top.continew.starter.idempotent.service.IdempotentService
* @author loach
* @author Charles7c
* @since 2.10.0
*/
public interface IdempotentService {
public interface IdempotentNameGenerator {
/**
* 检验是否存在
* 生成幂等名称
*
* @param key 幂等key
* @param timeout 超时时间
* @return
* @param target 目标实例
* @param method 目标方法
* @param args 方法参数
* @return 幂等名称
*/
boolean checkAndLock(String key, long timeout);
/**
* 释放对应key
*
* @param key 幂等key
*/
void unlock(String key);
String generate(Object target, Method method, Object... args);
}

View File

@@ -1,74 +0,0 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.idempotent.service.impl;
import top.continew.starter.idempotent.service.IdempotentService;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
/**
* 内存实现幂等
*
* @version 1.0
* @Author loach
* @Date 2025-03-07 19:49
* @Package top.continew.starter.idempotent.service.impl.MemoryIdempotentServiceImpl
*/
public class MemoryIdempotentServiceImpl implements IdempotentService {
private final ConcurrentHashMap<String, Long> lockMap = new ConcurrentHashMap<>();
private final long cleanInterval;
public MemoryIdempotentServiceImpl(long cleanInterval) {
this.cleanInterval = cleanInterval;
}
@Override
public boolean checkAndLock(String key, long timeout) {
if (key == null || key.isEmpty()) {
throw new IllegalArgumentException("Key cannot be null or empty");
}
long currentTime = System.currentTimeMillis();
Long existingExpireTime = lockMap.get(key);
// 如果 key 已存在且未过期,返回 false
if (existingExpireTime != null && currentTime < existingExpireTime) {
return false;
}
// 设置新的过期时间并放入 map
long effectiveTimeout = timeout > 0 ? timeout : cleanInterval;
long newExpireTime = currentTime + TimeUnit.SECONDS.toMillis(effectiveTimeout);
lockMap.put(key, newExpireTime);
return true;
}
@Override
public void unlock(String key) {
if (key != null && !key.isEmpty()) {
lockMap.remove(key);
}
}
// 清理过期 key 的方法
public void cleanExpiredKeys() {
long currentTime = System.currentTimeMillis();
lockMap.entrySet().removeIf(entry -> currentTime >= entry.getValue());
}
}

View File

@@ -1,54 +0,0 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.idempotent.service.impl;
import org.springframework.data.redis.core.StringRedisTemplate;
import top.continew.starter.idempotent.service.IdempotentService;
import java.util.concurrent.TimeUnit;
/**
* redis实现幂等
*
* @version 1.0
* @Author loach
* @Date 2025-03-07 19:49
* @Package top.continew.starter.idempotent.service.impl.RedisIdempotentServiceImpl
*/
public class RedisIdempotentServiceImpl implements IdempotentService {
private final StringRedisTemplate redisTemplate;
private final long redisTimeout;
public RedisIdempotentServiceImpl(StringRedisTemplate redisTemplate, long redisTimeout) {
this.redisTemplate = redisTemplate;
this.redisTimeout = redisTimeout;
}
@Override
public boolean checkAndLock(String key, long timeout) {
long effectiveTimeout = timeout > 0 ? timeout : redisTimeout;
Boolean success = redisTemplate.opsForValue().setIfAbsent(key, "1", effectiveTimeout, TimeUnit.SECONDS);
return success != null && success;
}
@Override
public void unlock(String key) {
redisTemplate.delete(key);
}
}

View File

@@ -1,57 +0,0 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.idempotent.util;
import jakarta.servlet.http.HttpServletRequest;
import org.aspectj.lang.JoinPoint;
import org.springframework.util.DigestUtils;
import java.util.Arrays;
/**
* 定义幂等key工具类
*
* @version 1.0
* @Author loach
* @Date 2025-03-07 20:13
* @Package top.continew.starter.idempotent.util.IdempotentKeyGenerator
*/
public class IdempotentKeyGenerator {
/**
* 创建key
*
* @param prefix 幂等key前缀
* @param joinPoint 切面参数
* @param request 请求头
* @return
*/
public static String generateKey(String prefix, JoinPoint joinPoint, HttpServletRequest request) {
// 可选使用header中的token
String token = request.getHeader("Authorization");
if (token == null || token.isEmpty()) {
token = request.getParameter("Authorization");
}
String methodSignature = joinPoint.getSignature().toString();
String argsStr = Arrays.toString(joinPoint.getArgs());
// 如果没有token只使用方法签名和参数生成key
String rawKey = prefix + methodSignature + argsStr + (token != null ? token : "");
return DigestUtils.md5DigestAsHex(rawKey.getBytes());
}
}

View File

@@ -1,2 +1 @@
top.continew.starter.idempotent.config.IdempotentAutoConfiguration
top.continew.starter.idempotent.config.MemoryIdempotentSchedulingConfig
top.continew.starter.idempotent.autoconfigure.IdempotentAutoConfiguration