Merge branch 'feat/idempotent' into 0.3.0
This commit is contained in:
66
ballcat-common/ballcat-common-idempotent/pom.xml
Normal file
66
ballcat-common/ballcat-common-idempotent/pom.xml
Normal file
@@ -0,0 +1,66 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>ballcat-common</artifactId>
|
||||
<groupId>com.hccake</groupId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>ballcat-common-idempotent</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.hccake</groupId>
|
||||
<artifactId>ballcat-common-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.hccake</groupId>
|
||||
<artifactId>ballcat-common-util</artifactId>
|
||||
</dependency>
|
||||
<!-- slf4j日志 -->
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-aop</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-context</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.aspectj</groupId>
|
||||
<artifactId>aspectjweaver</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.servlet</groupId>
|
||||
<artifactId>javax.servlet-api</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-redis</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<!--hutool-->
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-cache</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-webmvc</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,93 @@
|
||||
package com.hccake.ballcat.common.idempotent;
|
||||
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import com.hccake.ballcat.common.idempotent.annotation.Idempotent;
|
||||
import com.hccake.ballcat.common.idempotent.exception.IdempotentException;
|
||||
import com.hccake.ballcat.common.idempotent.key.IdempotentKeyStore;
|
||||
import com.hccake.ballcat.common.model.result.BaseResultCode;
|
||||
import com.hccake.ballcat.common.util.SpelUtils;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.aspectj.lang.ProceedingJoinPoint;
|
||||
import org.aspectj.lang.annotation.Around;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.reflect.MethodSignature;
|
||||
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* @author hccake
|
||||
*/
|
||||
@Slf4j
|
||||
@Aspect
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class IdempotentAspect {
|
||||
|
||||
private final IdempotentKeyStore idempotentKeyStore;
|
||||
|
||||
@Around("@annotation(idempotentAnnotation)")
|
||||
public Object around(ProceedingJoinPoint joinPoint, Idempotent idempotentAnnotation) throws Throwable {
|
||||
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
|
||||
Method method = signature.getMethod();
|
||||
Object[] args = joinPoint.getArgs();
|
||||
|
||||
// 获取幂等标识
|
||||
String idempotentKey = buildIdempotentKey(joinPoint, idempotentAnnotation, method, args);
|
||||
|
||||
// 校验当前请求是否重复请求
|
||||
Assert.isTrue(idempotentKeyStore.saveIfAbsent(idempotentKey, idempotentAnnotation.duration()), () -> {
|
||||
String errorMessage = String.format("拒绝重复执行方法[%s], 幂等key:[%s]", method.getName(), idempotentKey);
|
||||
throw new IdempotentException(BaseResultCode.REPEATED_EXECUTE.getCode(), errorMessage);
|
||||
});
|
||||
|
||||
try {
|
||||
Object result = joinPoint.proceed();
|
||||
if (idempotentAnnotation.removeKeyWhenFinished()) {
|
||||
idempotentKeyStore.remove(idempotentKey);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
catch (Throwable e) {
|
||||
// 异常时必须删除,方便重试处理
|
||||
idempotentKeyStore.remove(idempotentKey);
|
||||
throw e;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建幂等标识 key
|
||||
* @param joinPoint 切点
|
||||
* @param idempotentAnnotation 幂等注解
|
||||
* @param method 当前方法
|
||||
* @param args 方法参数
|
||||
* @return String 幂等标识
|
||||
*/
|
||||
private String buildIdempotentKey(ProceedingJoinPoint joinPoint, Idempotent idempotentAnnotation, Method method,
|
||||
Object[] args) {
|
||||
String uniqueExpression = idempotentAnnotation.uniqueExpression();
|
||||
// 如果没有填写表达式,直接返回 prefix
|
||||
if ("".equals(uniqueExpression)) {
|
||||
return idempotentAnnotation.prefix();
|
||||
}
|
||||
|
||||
// 根据当前切点,获取到 spEL 上下文
|
||||
StandardEvaluationContext spelContext = SpelUtils.getSpelContext(joinPoint.getTarget(), method, args);
|
||||
// 如果在 sevlet 环境下,则将 request 信息放入上下文,便于获取请求参数
|
||||
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder
|
||||
.getRequestAttributes();
|
||||
if (requestAttributes != null) {
|
||||
spelContext.setVariable("request", requestAttributes.getRequest());
|
||||
}
|
||||
// 解析出唯一标识
|
||||
String uniqueStr = SpelUtils.parseValueToString(spelContext, uniqueExpression);
|
||||
// 和 prefix 拼接获得完整的 key
|
||||
return idempotentAnnotation.prefix() + ":" + uniqueStr;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package com.hccake.ballcat.common.idempotent.annotation;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 幂等控制注解
|
||||
* @author hccake
|
||||
*/
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface Idempotent {
|
||||
|
||||
String KEY_PREFIX = "idem";
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 幂等标识的前缀,可用于区分服务和业务,防止 key 冲突
|
||||
* </p>
|
||||
* ps: 完整的幂等标识 = {prefix}:{uniqueExpression.value}
|
||||
* @return 业务标识
|
||||
*/
|
||||
String prefix() default KEY_PREFIX;
|
||||
|
||||
/**
|
||||
* 值为 SpEL 表达式,从上下文中提取幂等的唯一性标识。
|
||||
* @return Spring-EL expression
|
||||
*/
|
||||
String uniqueExpression() default "";
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 幂等的控制时长(秒),必须大于业务的处理耗时
|
||||
* </p>
|
||||
* 其值为幂等 key 的标记时长,超过标记时间,则幂等 key 可再次使用。
|
||||
* @return 标记时长(秒),默认 10 min
|
||||
*/
|
||||
long duration() default 10 * 60;
|
||||
|
||||
/**
|
||||
* 否在业务完成后立刻清除,幂等 key
|
||||
* @return boolean true: 立刻清除 false: 不处理
|
||||
*/
|
||||
boolean removeKeyWhenFinished() default false;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.hccake.ballcat.common.idempotent.exception;
|
||||
|
||||
import com.hccake.ballcat.common.core.exception.BusinessException;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* @author hccake
|
||||
*/
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class IdempotentException extends BusinessException {
|
||||
|
||||
public IdempotentException(int code, String message) {
|
||||
super(code, message);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.hccake.ballcat.common.idempotent.key;
|
||||
|
||||
/**
|
||||
* @author hccake
|
||||
*/
|
||||
public interface IdempotentKeyStore {
|
||||
|
||||
/**
|
||||
* 当不存在有效 key 时将其存储下来
|
||||
* @param key idempotentKey
|
||||
* @param duration key的有效时长
|
||||
* @return boolean true: 存储成功 false: 存储失败
|
||||
*/
|
||||
boolean saveIfAbsent(String key, long duration);
|
||||
|
||||
/**
|
||||
* 删除 key
|
||||
* @param key idempotentKey
|
||||
*/
|
||||
void remove(String key);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.hccake.ballcat.common.idempotent.key;
|
||||
|
||||
import cn.hutool.cache.CacheUtil;
|
||||
import cn.hutool.cache.impl.TimedCache;
|
||||
|
||||
/**
|
||||
* @author hccake
|
||||
*/
|
||||
public class InMemoryIdempotentKeyStore implements IdempotentKeyStore {
|
||||
|
||||
private final TimedCache<String, Long> cache;
|
||||
|
||||
public InMemoryIdempotentKeyStore() {
|
||||
this.cache = CacheUtil.newTimedCache(Integer.MAX_VALUE);
|
||||
cache.schedulePrune(1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized boolean saveIfAbsent(String key, long duration) {
|
||||
Long value = cache.get(key, false);
|
||||
if (value == null) {
|
||||
cache.put(key, System.currentTimeMillis(), duration * 1000);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(String key) {
|
||||
cache.remove(key);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.hccake.ballcat.common.idempotent.key;
|
||||
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.data.redis.core.ValueOperations;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* @author hccake
|
||||
*/
|
||||
public class RedisIdempotentKeyStore implements IdempotentKeyStore {
|
||||
|
||||
StringRedisTemplate stringRedisTemplate;
|
||||
|
||||
@Override
|
||||
public boolean saveIfAbsent(String key, long duration) {
|
||||
ValueOperations<String, String> opsForValue = stringRedisTemplate.opsForValue();
|
||||
Boolean saveSuccess = opsForValue.setIfAbsent(key, String.valueOf(System.currentTimeMillis()), duration,
|
||||
TimeUnit.SECONDS);
|
||||
return saveSuccess != null && saveSuccess;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(String key) {
|
||||
stringRedisTemplate.delete(key);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.hccake.ballcat.common.idempotent.test;
|
||||
|
||||
import com.hccake.ballcat.common.idempotent.annotation.Idempotent;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* @author hccake
|
||||
*/
|
||||
@RestController
|
||||
public class IdempotentController {
|
||||
|
||||
@RequestMapping("/")
|
||||
@Idempotent(uniqueExpression = "#request.getHeader('formId')", duration = 1)
|
||||
public String greeting() {
|
||||
return "hello word";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.hccake.ballcat.common.idempotent.test;
|
||||
|
||||
import com.hccake.ballcat.common.idempotent.annotation.Idempotent;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* @author hccake
|
||||
*/
|
||||
@Slf4j
|
||||
public class IdempotentMethods {
|
||||
|
||||
@Idempotent(uniqueExpression = "#key", duration = 1)
|
||||
public String method1(String key) {
|
||||
log.info("===执行方法1成功===");
|
||||
return key;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.hccake.ballcat.common.idempotent.test;
|
||||
|
||||
import com.hccake.ballcat.common.idempotent.IdempotentAspect;
|
||||
import com.hccake.ballcat.common.idempotent.key.IdempotentKeyStore;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.EnableAspectJAutoProxy;
|
||||
|
||||
/**
|
||||
* @author hccake
|
||||
*/
|
||||
@Configuration
|
||||
@EnableAspectJAutoProxy(proxyTargetClass = true)
|
||||
@ComponentScan
|
||||
public class IdempotentTestConfiguration {
|
||||
|
||||
@Bean
|
||||
public IdempotentAspect idempotentAspect(IdempotentKeyStore idempotentKeyStore) {
|
||||
return new IdempotentAspect(idempotentKeyStore);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public IdempotentMethods idempotentMethods() {
|
||||
return new IdempotentMethods();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.hccake.ballcat.common.idempotent.test;
|
||||
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import com.hccake.ballcat.common.idempotent.exception.IdempotentException;
|
||||
import com.hccake.ballcat.common.idempotent.key.InMemoryIdempotentKeyStore;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
|
||||
/**
|
||||
* @author hccake
|
||||
*/
|
||||
@Slf4j
|
||||
@SpringJUnitConfig({ IdempotentTestConfiguration.class, InMemoryIdempotentKeyStore.class })
|
||||
class InMemoryIdempotentTest {
|
||||
|
||||
@Autowired
|
||||
private IdempotentMethods idempotentMethods;
|
||||
|
||||
@Test
|
||||
void testIdempotent() throws InterruptedException {
|
||||
tryExecute("aaa");
|
||||
Assert.isTrue(tryExecute("bbb"));
|
||||
Assert.isFalse(tryExecute("bbb"));
|
||||
Thread.sleep(1000);
|
||||
Assert.isTrue(tryExecute("bbb"));
|
||||
}
|
||||
|
||||
private boolean tryExecute(String key) {
|
||||
try {
|
||||
idempotentMethods.method1(key);
|
||||
}
|
||||
catch (IdempotentException e) {
|
||||
System.out.println(e.getMessage());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package com.hccake.ballcat.common.idempotent.test;
|
||||
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import com.hccake.ballcat.common.idempotent.exception.IdempotentException;
|
||||
import com.hccake.ballcat.common.idempotent.key.InMemoryIdempotentKeyStore;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
/**
|
||||
* @author hccake
|
||||
*/
|
||||
@Slf4j
|
||||
@WebMvcTest(IdempotentController.class)
|
||||
@AutoConfigureMockMvc
|
||||
@SpringJUnitConfig({ IdempotentTestConfiguration.class, InMemoryIdempotentKeyStore.class })
|
||||
class WebIdempotentTest {
|
||||
|
||||
@Autowired
|
||||
private IdempotentMethods idempotentMethods;
|
||||
|
||||
@Autowired
|
||||
private MockMvc mockMvc;
|
||||
|
||||
@Test
|
||||
void shouldReturnDefaultMessage() throws Exception {
|
||||
this.mockMvc.perform(get("/").header("formId", "formId1")).andDo(print()).andExpect(status().isOk())
|
||||
.andExpect(content().string(containsString("hello word")));
|
||||
|
||||
this.mockMvc.perform(get("/").header("formId", "formId1")).andDo(print()).andExpect(status().isOk())
|
||||
.andExpect(content().string(containsString("hello word")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testIdempotent() throws InterruptedException {
|
||||
tryExecute("aaa");
|
||||
Assert.isTrue(tryExecute("bbb"));
|
||||
Assert.isFalse(tryExecute("bbb"));
|
||||
Thread.sleep(1000);
|
||||
Assert.isTrue(tryExecute("bbb"));
|
||||
}
|
||||
|
||||
private boolean tryExecute(String key) {
|
||||
try {
|
||||
idempotentMethods.method1(key);
|
||||
}
|
||||
catch (IdempotentException e) {
|
||||
System.out.println(e.getMessage());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -32,6 +32,11 @@ public enum BaseResultCode implements ResultCode {
|
||||
*/
|
||||
FILE_UPLOAD_ERROR(90006, "File Upload Error"),
|
||||
|
||||
/**
|
||||
* 重复执行
|
||||
*/
|
||||
REPEATED_EXECUTE(90007, "Repeated execute"),
|
||||
|
||||
/**
|
||||
* 未知异常
|
||||
*/
|
||||
|
||||
@@ -60,5 +60,11 @@
|
||||
<artifactId>jsoup</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-context</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.hccake.ballcat.common.redis.core;
|
||||
package com.hccake.ballcat.common.util;
|
||||
|
||||
import org.springframework.context.expression.MethodBasedEvaluationContext;
|
||||
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
|
||||
@@ -14,7 +14,11 @@ import java.lang.reflect.Method;
|
||||
* @version 1.0
|
||||
* @date 2019/9/3 10:29
|
||||
*/
|
||||
public class SpELUtil {
|
||||
@SuppressWarnings("SpellCheckingInspection")
|
||||
public final class SpelUtils {
|
||||
|
||||
private SpelUtils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* SpEL 解析器
|
||||
@@ -22,48 +26,53 @@ public class SpELUtil {
|
||||
public static final ExpressionParser PARSER = new SpelExpressionParser();
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* 方法参数获取
|
||||
*/
|
||||
public static final ParameterNameDiscoverer PARAMETER_NAME_DISCOVERER = new LocalVariableTableParameterNameDiscoverer();
|
||||
|
||||
/**
|
||||
* 支持 #p0 参数索引的表达式解析
|
||||
* @param rootObject 根对象,method 所在的对象
|
||||
* @param spEL 表达式
|
||||
* @param rootObject 根对象, method 所在类的对象实例
|
||||
* @param spelExpression spel 表达式
|
||||
* @param method 目标方法
|
||||
* @param args 方法入参
|
||||
* @return 解析后的字符串
|
||||
*/
|
||||
public static String parseValueToString(Object rootObject, Method method, Object[] args, String spEL) {
|
||||
|
||||
StandardEvaluationContext context = getSpElContext(rootObject, method, args);
|
||||
return parseValueToString(context, spEL);
|
||||
public static String parseValueToString(Object rootObject, Method method, Object[] args, String spelExpression) {
|
||||
StandardEvaluationContext context = getSpelContext(rootObject, method, args);
|
||||
return parseValueToString(context, spelExpression);
|
||||
}
|
||||
|
||||
/**
|
||||
* 支持 #p0 参数索引的表达式解析
|
||||
* @param rootObject 根对象,method 所在的对象
|
||||
* @param method ,目标方法
|
||||
* @param args 方法入参
|
||||
* @return 解析后的字符串
|
||||
* @param rootObject 根对象, method 所在的对象
|
||||
* @param method 目标方法
|
||||
* @param args 方法实际入参
|
||||
* @return StandardEvaluationContext spel 上下文
|
||||
*/
|
||||
public static StandardEvaluationContext getSpElContext(Object rootObject, Method method, Object[] args) {
|
||||
|
||||
String[] paraNameArr = PARAMETER_NAME_DISCOVERER.getParameterNames(method);
|
||||
// SPEL 上下文
|
||||
public static StandardEvaluationContext getSpelContext(Object rootObject, Method method, Object[] args) {
|
||||
// spel 上下文
|
||||
StandardEvaluationContext context = new MethodBasedEvaluationContext(rootObject, method, args,
|
||||
PARAMETER_NAME_DISCOVERER);
|
||||
// 把方法参数放入 SPEL 上下文中
|
||||
for (int i = 0; i < paraNameArr.length; i++) {
|
||||
context.setVariable(paraNameArr[i], args[i]);
|
||||
// 方法参数名数组
|
||||
String[] parameterNames = PARAMETER_NAME_DISCOVERER.getParameterNames(method);
|
||||
// 把方法参数放入 spel 上下文中
|
||||
if (parameterNames != null && parameterNames.length > 0) {
|
||||
for (int i = 0; i < parameterNames.length; i++) {
|
||||
context.setVariable(parameterNames[i], args[i]);
|
||||
}
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
public static String parseValueToString(StandardEvaluationContext context, String spEL) {
|
||||
return PARSER.parseExpression(spEL).getValue(context, String.class);
|
||||
/**
|
||||
* 解析 spel 表达式
|
||||
* @param context spel 上下文
|
||||
* @param spelExpression spel 表达式
|
||||
* @return String 解析后的字符串
|
||||
*/
|
||||
public static String parseValueToString(StandardEvaluationContext context, String spelExpression) {
|
||||
return PARSER.parseExpression(spelExpression).getValue(context, String.class);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -20,6 +20,7 @@
|
||||
<module>ballcat-common-model</module>
|
||||
<module>ballcat-common-websocket</module>
|
||||
<module>ballcat-common-security</module>
|
||||
<module>ballcat-common-idempotent</module>
|
||||
</modules>
|
||||
|
||||
|
||||
|
||||
@@ -129,6 +129,11 @@
|
||||
<artifactId>ballcat-common-desensitize</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.hccake</groupId>
|
||||
<artifactId>ballcat-common-idempotent</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.hccake</groupId>
|
||||
<artifactId>ballcat-common-model</artifactId>
|
||||
@@ -461,6 +466,11 @@
|
||||
<artifactId>hutool-extra</artifactId>
|
||||
<version>${hutool.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-cache</artifactId>
|
||||
<version>${hutool.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-json</artifactId>
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
package com.hccake.ballcat.common.redis.core;
|
||||
|
||||
import com.hccake.ballcat.common.redis.config.CachePropertiesHolder;
|
||||
import com.hccake.ballcat.common.util.SpelUtils;
|
||||
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -21,89 +20,50 @@ public class KeyGenerator {
|
||||
/**
|
||||
* SpEL 上下文
|
||||
*/
|
||||
StandardEvaluationContext spElContext;
|
||||
StandardEvaluationContext spelContext;
|
||||
|
||||
public KeyGenerator(Object target, Method method, Object[] arguments) {
|
||||
this.spElContext = SpELUtil.getSpElContext(target, method, arguments);
|
||||
this.spelContext = SpelUtils.getSpelContext(target, method, arguments);
|
||||
}
|
||||
|
||||
public String getKey(String key, String spELExpressions) {
|
||||
// 根据keyJoint 判断是否需要拼接
|
||||
if (spELExpressions == null || spELExpressions.length() == 0) {
|
||||
return key;
|
||||
/**
|
||||
* 根据 keyPrefix 和 keyJoint 获取完整的 key 信息
|
||||
* @param keyPrefix key 前缀
|
||||
* @param keyJoint key 拼接元素,值为 spel 表达式,可为空
|
||||
* @return 拼接完成的 key
|
||||
*/
|
||||
public String getKey(String keyPrefix, String keyJoint) {
|
||||
// 根据 keyJoint 判断是否需要拼接
|
||||
if (keyJoint == null || keyJoint.length() == 0) {
|
||||
return keyPrefix;
|
||||
}
|
||||
|
||||
// 获取所有需要拼接的元素, 组装进集合中
|
||||
String joint = SpELUtil.parseValueToString(spElContext, spELExpressions);
|
||||
String joint = SpelUtils.parseValueToString(spelContext, keyJoint);
|
||||
Assert.notNull(joint, "Key joint cannot be null!");
|
||||
|
||||
if (!StringUtils.hasText(key)) {
|
||||
if (!StringUtils.hasText(keyPrefix)) {
|
||||
return joint;
|
||||
}
|
||||
// 拼接后返回
|
||||
return jointKey(key, joint);
|
||||
}
|
||||
|
||||
public List<String> getKeys(String key, String keyJoint, Collection<String> multiByItem) {
|
||||
String keyPrefix = getKey(key, keyJoint);
|
||||
|
||||
List<String> list = new ArrayList<>();
|
||||
for (String item : multiByItem) {
|
||||
list.add(jointKey(keyPrefix, item));
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param key
|
||||
* @param spELExpressions
|
||||
* @return
|
||||
*/
|
||||
public String getKeys(String key, String[] spELExpressions) {
|
||||
// 根据keyJoint 判断是否需要拼接
|
||||
if (spELExpressions == null || spELExpressions.length == 0) {
|
||||
return key;
|
||||
}
|
||||
|
||||
// 获取所有需要拼接的元素, 组装进集合中
|
||||
List<String> list = new ArrayList<>(spELExpressions.length + 1);
|
||||
list.add(key);
|
||||
for (String joint : spELExpressions) {
|
||||
String s = parseSpEL(joint);
|
||||
Assert.notNull(s, "Key joint cannot be null!");
|
||||
list.add(s);
|
||||
}
|
||||
|
||||
// 拼接后返回
|
||||
return jointKey(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析SPEL
|
||||
* @param field
|
||||
* @return
|
||||
*/
|
||||
public String parseSpEL(String field) {
|
||||
return SpELUtil.parseValueToString(spElContext, field);
|
||||
return jointKey(keyPrefix, joint);
|
||||
}
|
||||
|
||||
/**
|
||||
* 拼接key, 默认使用 :作为分隔符
|
||||
* @param list
|
||||
* @return
|
||||
* @param keyItems 用于拼接 key 的元素列表
|
||||
* @return 拼接完成的 key
|
||||
*/
|
||||
public String jointKey(List<String> list) {
|
||||
return String.join(CachePropertiesHolder.delimiter(), list);
|
||||
public String jointKey(List<String> keyItems) {
|
||||
return String.join(CachePropertiesHolder.delimiter(), keyItems);
|
||||
}
|
||||
|
||||
/**
|
||||
* 拼接key, 默认使用 :作为分隔符
|
||||
* @param items
|
||||
* @return
|
||||
* @param keyItems 用于拼接 key 的元素列表
|
||||
* @return 拼接完成的 key
|
||||
*/
|
||||
public String jointKey(String... items) {
|
||||
return jointKey(Arrays.asList(items));
|
||||
public String jointKey(String... keyItems) {
|
||||
return jointKey(Arrays.asList(keyItems));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user