feat: initial iShare project code

This commit is contained in:
purovps
2026-02-16 23:20:59 +08:00
parent 8c83a6fd46
commit 6f270a972e
1910 changed files with 218015 additions and 0 deletions

View File

@@ -0,0 +1,45 @@
<?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">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.pig4cloud</groupId>
<artifactId>pigx-common</artifactId>
<version>5.2.0</version>
</parent>
<artifactId>pigx-common-idempotent</artifactId>
<packaging>jar</packaging>
<description>pigx 幂等插件</description>
<properties>
<redisson.version>3.17.3</redisson.version>
</properties>
<dependencies>
<dependency>
<groupId>com.pig4cloud</groupId>
<artifactId>pigx-common-core</artifactId>
</dependency>
<!--aop-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<scope>provided</scope>
</dependency>
<!--redisson-->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>${redisson.version}</version>
</dependency>
<!--servlet 依赖-->
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,41 @@
package com.pig4cloud.pigx.common.idempotent;
import com.pig4cloud.pigx.common.idempotent.aspect.IdempotentAspect;
import com.pig4cloud.pigx.common.idempotent.expression.ExpressionResolver;
import com.pig4cloud.pigx.common.idempotent.expression.KeyResolver;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author lengleng
* @date 2020/9/25
* <p>
* 幂等插件初始化
*/
@Configuration(proxyBeanMethods = false)
@AutoConfigureAfter(RedisAutoConfiguration.class)
public class IdempotentAutoConfiguration {
/**
* 切面 拦截处理所有 @Idempotent
* @return Aspect
*/
@Bean
public IdempotentAspect idempotentAspect() {
return new IdempotentAspect();
}
/**
* key 解析器
* @return KeyResolver
*/
@Bean
@ConditionalOnMissingBean(KeyResolver.class)
public KeyResolver keyResolver() {
return new ExpressionResolver();
}
}

View File

@@ -0,0 +1,44 @@
package com.pig4cloud.pigx.common.idempotent.annotation;
import java.lang.annotation.*;
import java.util.concurrent.TimeUnit;
/**
* @author ITyunqing
*/
@Inherited
@Target(ElementType.METHOD)
@Retention(value = RetentionPolicy.RUNTIME)
public @interface Idempotent {
/**
* 幂等操作的唯一标识使用spring el表达式 用#来引用方法参数
* @return Spring-EL expression
*/
String key() default "";
/**
* 有效期 默认1 有效期要大于程序执行时间,否则请求还是可能会进来
* @return expireTime
*/
int expireTime() default 1;
/**
* 时间单位 默认s
* @return TimeUnit
*/
TimeUnit timeUnit() default TimeUnit.SECONDS;
/**
* 提示信息,可自定义
* @return String
*/
String info() default "重复请求,请稍后重试";
/**
* 是否在业务完成后删除key true:删除 false:不删除
* @return boolean
*/
boolean delKey() default false;
}

View File

@@ -0,0 +1,145 @@
package com.pig4cloud.pigx.common.idempotent.aspect;
import cn.hutool.core.util.StrUtil;
import com.pig4cloud.pigx.common.core.util.KeyStrResolver;
import com.pig4cloud.pigx.common.idempotent.annotation.Idempotent;
import com.pig4cloud.pigx.common.idempotent.exception.IdempotentException;
import com.pig4cloud.pigx.common.idempotent.expression.KeyResolver;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.Redisson;
import org.redisson.api.RMapCache;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
/**
* The Idempotent Aspect
*
* @author ITyunqing
*/
@Aspect
public class IdempotentAspect {
private static final Logger LOGGER = LoggerFactory.getLogger(IdempotentAspect.class);
private static final ThreadLocal<Map<String, Object>> THREAD_CACHE = ThreadLocal.withInitial(HashMap::new);
private static final String RMAPCACHE_KEY = "idempotent";
private static final String KEY = "key";
private static final String DELKEY = "delKey";
@Autowired
private Redisson redisson;
@Autowired
private KeyResolver keyResolver;
@Autowired
private Optional<KeyStrResolver> keyStrResolverOptional;
@Pointcut("@annotation(com.pig4cloud.pigx.common.idempotent.annotation.Idempotent)")
public void pointCut() {
}
@Before("pointCut()")
public void beforePointCut(JoinPoint joinPoint) {
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder
.getRequestAttributes();
HttpServletRequest request = requestAttributes.getRequest();
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
if (!method.isAnnotationPresent(Idempotent.class)) {
return;
}
Idempotent idempotent = method.getAnnotation(Idempotent.class);
String key;
// 若没有配置 幂等 标识编号,则使用 url + 参数列表作为区分
if (!StringUtils.hasLength(idempotent.key())) {
String url = request.getRequestURL().toString();
String argString = Arrays.asList(joinPoint.getArgs()).toString();
key = url + argString;
}
else {
// 使用jstl 规则区分
key = keyResolver.resolver(idempotent, joinPoint);
}
if (keyStrResolverOptional.isPresent()) {
key = keyStrResolverOptional.get().extract(key, StrUtil.COLON);
}
long expireTime = idempotent.expireTime();
String info = idempotent.info();
TimeUnit timeUnit = idempotent.timeUnit();
boolean delKey = idempotent.delKey();
// do not need check null
RMapCache<String, Object> rMapCache = redisson.getMapCache(RMAPCACHE_KEY);
String value = LocalDateTime.now().toString().replace("T", " ");
Object v1;
if (null != rMapCache.get(key)) {
// had stored
throw new IdempotentException("[idempotent]:" + info);
}
synchronized (this) {
v1 = rMapCache.putIfAbsent(key, value, expireTime, timeUnit);
if (null != v1) {
throw new IdempotentException("[idempotent]:" + info);
}
else {
LOGGER.info("[idempotent]:has stored key={},value={},expireTime={}{},now={}", key, value, expireTime,
timeUnit, LocalDateTime.now().toString());
}
}
Map<String, Object> map = THREAD_CACHE.get();
map.put(KEY, key);
map.put(DELKEY, delKey);
}
@After("pointCut()")
public void afterPointCut(JoinPoint joinPoint) {
Map<String, Object> map = THREAD_CACHE.get();
if (CollectionUtils.isEmpty(map)) {
return;
}
RMapCache<Object, Object> mapCache = redisson.getMapCache(RMAPCACHE_KEY);
if (mapCache.size() == 0) {
return;
}
String key = map.get(KEY).toString();
boolean delKey = (boolean) map.get(DELKEY);
if (delKey) {
mapCache.fastRemove(key);
LOGGER.info("[idempotent]:has removed key={}", key);
}
THREAD_CACHE.remove();
}
}

View File

@@ -0,0 +1,32 @@
package com.pig4cloud.pigx.common.idempotent.exception;
/**
* Idempotent Exception If there is a custom global exception, you need to inherit the
* custom global exception.
*
* @author ITyunqing
*/
public class IdempotentException extends RuntimeException {
public IdempotentException() {
super();
}
public IdempotentException(String message) {
super(message);
}
public IdempotentException(String message, Throwable cause) {
super(message, cause);
}
public IdempotentException(Throwable cause) {
super(cause);
}
protected IdempotentException(String message, Throwable cause, boolean enableSuppression,
boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}

View File

@@ -0,0 +1,66 @@
package com.pig4cloud.pigx.common.idempotent.expression;
/**
* @author lengleng
* @date 2020/9/25
*/
import com.pig4cloud.pigx.common.idempotent.annotation.Idempotent;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import java.lang.reflect.Method;
/**
* @author lengleng
* <p>
* 默认key 抽取, 优先根据 spel 处理
* @date 2020-09-25
*/
public class ExpressionResolver implements KeyResolver {
private static final SpelExpressionParser PARSER = new SpelExpressionParser();
private static final LocalVariableTableParameterNameDiscoverer DISCOVERER = new LocalVariableTableParameterNameDiscoverer();
@Override
public String resolver(Idempotent idempotent, JoinPoint point) {
Object[] arguments = point.getArgs();
String[] params = DISCOVERER.getParameterNames(getMethod(point));
StandardEvaluationContext context = new StandardEvaluationContext();
if (params != null && params.length > 0) {
for (int len = 0; len < params.length; len++) {
context.setVariable(params[len], arguments[len]);
}
}
Expression expression = PARSER.parseExpression(idempotent.key());
return expression.getValue(context, String.class);
}
/**
* 根据切点解析方法信息
* @param joinPoint 切点信息
* @return Method 原信息
*/
private Method getMethod(JoinPoint joinPoint) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
if (method.getDeclaringClass().isInterface()) {
try {
method = joinPoint.getTarget().getClass().getDeclaredMethod(joinPoint.getSignature().getName(),
method.getParameterTypes());
}
catch (SecurityException | NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
return method;
}
}

View File

@@ -0,0 +1,22 @@
package com.pig4cloud.pigx.common.idempotent.expression;
import com.pig4cloud.pigx.common.idempotent.annotation.Idempotent;
import org.aspectj.lang.JoinPoint;
/**
* @author lengleng
* @date 2020/9/25
* <p>
* 唯一标志处理器
*/
public interface KeyResolver {
/**
* 解析处理 key
* @param idempotent 接口注解标识
* @param point 接口切点信息
* @return 处理结果
*/
String resolver(Idempotent idempotent, JoinPoint point);
}

View File

@@ -0,0 +1 @@
com.pig4cloud.pigx.common.idempotent.IdempotentAutoConfiguration