diff --git a/heritage-plugins/heritage-limit/pom.xml b/heritage-plugins/heritage-limit/pom.xml
new file mode 100644
index 0000000..8c917e2
--- /dev/null
+++ b/heritage-plugins/heritage-limit/pom.xml
@@ -0,0 +1,32 @@
+
+
+ 4.0.0
+
+ org.leocoder.heritage
+ heritage-plugins
+ ${revision}
+
+
+
+ heritage-limit
+ heritage-limit
+ 限流插件
+
+
+
+
+ org.leocoder.heritage
+ heritage-common
+ ${revision}
+
+
+
+ org.springframework.boot
+ spring-boot-starter-aop
+
+
+
+
+
\ No newline at end of file
diff --git a/heritage-plugins/heritage-limit/src/main/java/org/leocoder/heritage/limit/anno/EnableCoderLimit.java b/heritage-plugins/heritage-limit/src/main/java/org/leocoder/heritage/limit/anno/EnableCoderLimit.java
new file mode 100755
index 0000000..112ab9d
--- /dev/null
+++ b/heritage-plugins/heritage-limit/src/main/java/org/leocoder/heritage/limit/anno/EnableCoderLimit.java
@@ -0,0 +1,15 @@
+package org.leocoder.heritage.limit.anno;
+
+import org.leocoder.heritage.limit.config.CoderRedisLimitUtil;
+import org.springframework.context.annotation.Import;
+
+import java.lang.annotation.*;
+
+@Target({ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@Inherited
+@Import({CoderRedisLimitUtil.class})
+public @interface EnableCoderLimit {
+
+}
diff --git a/heritage-plugins/heritage-limit/src/main/java/org/leocoder/heritage/limit/aspect/CoderLimitAspect.java b/heritage-plugins/heritage-limit/src/main/java/org/leocoder/heritage/limit/aspect/CoderLimitAspect.java
new file mode 100755
index 0000000..5ad053d
--- /dev/null
+++ b/heritage-plugins/heritage-limit/src/main/java/org/leocoder/heritage/limit/aspect/CoderLimitAspect.java
@@ -0,0 +1,83 @@
+package org.leocoder.heritage.limit.aspect;
+
+import jakarta.servlet.http.HttpServletRequest;
+import lombok.extern.slf4j.Slf4j;
+import org.aspectj.lang.JoinPoint;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Before;
+import org.aspectj.lang.reflect.MethodSignature;
+import org.leocoder.heritage.common.anno.CoderLimit;
+import org.leocoder.heritage.common.enmus.limit.LimitType;
+import org.leocoder.heritage.common.exception.coder.ParamsException;
+import org.leocoder.heritage.common.utils.ip.IpUtil;
+import org.leocoder.heritage.limit.config.CoderRedisLimitUtil;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.annotation.Order;
+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 Leocoder
+ * @description [CoderLimitAspect限流处理]
+ */
+@Slf4j
+@Aspect
+@Order(2)
+@Component
+public class CoderLimitAspect {
+
+ @Autowired
+ private CoderRedisLimitUtil CoderRedisLimitUtil;
+
+ /**
+ * @description [前置通知,判断是否超出限流次数】
+ */
+ @Before("@annotation(limit)")
+ public void doBefore(JoinPoint point, CoderLimit limit) {
+ try {
+ // log.info("限流开始进入 =>");
+ // 拼接key
+ String key = getCombineKey(limit, point);
+ // 判断是否超出限流次数
+ if (!CoderRedisLimitUtil.limit(key, limit.count(), limit.time())) {
+ throw new ParamsException(limit.message());
+ }
+ } catch (ParamsException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new RuntimeException("接口限流异常,请稍候再试");
+ }
+ }
+
+ /**
+ * @description [根据限流类型拼接key】
+ */
+ public String getCombineKey(CoderLimit limit, JoinPoint point) {
+ // 获取服务请求的对象
+ ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
+ HttpServletRequest request = requestAttributes.getRequest();
+ StringBuilder sb = new StringBuilder(limit.prefix());
+ // 按照IP限流
+ if (limit.type() == LimitType.IP) {
+ String ipAddr = IpUtil.getIpAddr(request);
+ // 检查字符串中是否包含逗号,这种情况设置waf会出现多个IP,第一个是真实IP,后面的都是阿里云IP
+ int commaIndex = ipAddr.indexOf(',');
+ if (commaIndex > -1) {
+ // 如果有逗号,取逗号前的部分
+ ipAddr = ipAddr.substring(0, commaIndex);
+ }
+ sb.append(ipAddr).append(":");
+ // log.info("限流IP:{}", ipAddr);
+ }
+ // 拼接类名和方法名
+ MethodSignature signature = (MethodSignature) point.getSignature();
+ Method method = signature.getMethod();
+ Class> targetClass = method.getDeclaringClass();
+ sb.append(targetClass.getName()).append("-").append(method.getName());
+ return sb.toString();
+ }
+
+}
diff --git a/heritage-plugins/heritage-limit/src/main/java/org/leocoder/heritage/limit/config/CoderRedisLimitUtil.java b/heritage-plugins/heritage-limit/src/main/java/org/leocoder/heritage/limit/config/CoderRedisLimitUtil.java
new file mode 100755
index 0000000..7fd411e
--- /dev/null
+++ b/heritage-plugins/heritage-limit/src/main/java/org/leocoder/heritage/limit/config/CoderRedisLimitUtil.java
@@ -0,0 +1,54 @@
+package org.leocoder.heritage.limit.config;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.core.StringRedisTemplate;
+import org.springframework.data.redis.core.script.DefaultRedisScript;
+import org.springframework.data.redis.core.script.RedisScript;
+import org.springframework.stereotype.Component;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * @author Leocoder
+ * @description [RedisLimitUtil接口限流]
+ */
+@Slf4j
+@Component
+public class CoderRedisLimitUtil {
+
+ @Autowired
+ private StringRedisTemplate redisTemplate;
+
+ /**
+ * @param key 键
+ * @param count 限流次数
+ * @param times 限流时间
+ * @description [限流]
+ * 通过 Lua 脚本,根据 Redis 中缓存的键值判断限流时间(也是 key 的过期时间)内,访问次数是否超出了限流次数,没超出则访问次数 +1,返回 true,超出了则返回 false。
+ */
+ public boolean limit(String key, int count, int times) {
+ try {
+ String script = "local lockKey = KEYS[1]\n" +
+ "local lockCount = KEYS[2]\n" +
+ "local lockExpire = KEYS[3]\n" +
+ "local currentCount = tonumber(redis.call('get', lockKey) or \"0\")\n" +
+ "if currentCount < tonumber(lockCount)\n" +
+ "then\n" +
+ " redis.call(\"INCRBY\", lockKey, \"1\")\n" +
+ " redis.call(\"expire\", lockKey, lockExpire)\n" +
+ " return true\n" +
+ "else\n" +
+ " return false\n" +
+ "end";
+ RedisScript redisScript = new DefaultRedisScript<>(script, Boolean.class);
+ List keys = Arrays.asList(key, String.valueOf(count), String.valueOf(times));
+ return Boolean.TRUE.equals(redisTemplate.execute(redisScript, keys));
+ } catch (Exception e) {
+ log.error("限流脚本执行失败:{}", e.getMessage());
+ }
+ return false;
+ }
+
+}
diff --git a/heritage-plugins/heritage-repect/pom.xml b/heritage-plugins/heritage-repect/pom.xml
new file mode 100644
index 0000000..53a73c4
--- /dev/null
+++ b/heritage-plugins/heritage-repect/pom.xml
@@ -0,0 +1,30 @@
+
+
+ 4.0.0
+
+ org.leocoder.heritage
+ heritage-plugins
+ ${revision}
+
+
+
+ heritage-repect
+ heritage-repect
+ 防重复提交插件
+
+
+
+
+ org.leocoder.heritage
+ heritage-common
+ ${revision}
+
+
+
+ org.springframework.boot
+ spring-boot-starter-aop
+
+
+
\ No newline at end of file
diff --git a/heritage-plugins/heritage-repect/src/main/java/org/leocoder/heritage/repect/anno/EnableCoderRepeatSubmit.java b/heritage-plugins/heritage-repect/src/main/java/org/leocoder/heritage/repect/anno/EnableCoderRepeatSubmit.java
new file mode 100755
index 0000000..e091269
--- /dev/null
+++ b/heritage-plugins/heritage-repect/src/main/java/org/leocoder/heritage/repect/anno/EnableCoderRepeatSubmit.java
@@ -0,0 +1,16 @@
+package org.leocoder.heritage.repect.anno;
+
+import org.leocoder.heritage.repect.aspect.RedisService;
+import org.leocoder.heritage.repect.aspect.RepeatSubmitAspect;
+import org.springframework.context.annotation.Import;
+
+import java.lang.annotation.*;
+
+@Target({ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@Inherited
+@Import({RepeatSubmitAspect.class, RedisService.class})
+public @interface EnableCoderRepeatSubmit {
+
+}
diff --git a/heritage-plugins/heritage-repect/src/main/java/org/leocoder/heritage/repect/aspect/RedisService.java b/heritage-plugins/heritage-repect/src/main/java/org/leocoder/heritage/repect/aspect/RedisService.java
new file mode 100755
index 0000000..6beead1
--- /dev/null
+++ b/heritage-plugins/heritage-repect/src/main/java/org/leocoder/heritage/repect/aspect/RedisService.java
@@ -0,0 +1,206 @@
+package org.leocoder.heritage.repect.aspect;
+
+import lombok.RequiredArgsConstructor;
+import org.springframework.data.redis.core.*;
+import org.springframework.stereotype.Component;
+
+import java.util.*;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author Leocoder
+ * @description [RedisService]
+ */
+@Component
+@RequiredArgsConstructor
+public class RedisService {
+
+ public final RedisTemplate redisTemplate;
+
+ /**
+ * @param key 缓存的键值
+ * @param value 缓存的值
+ * @return 缓存的对象
+ * @description [缓存基本的对象,Integer、String、实体类等]
+ */
+ public ValueOperations setCacheObject(String key, T value) {
+ ValueOperations operation = redisTemplate.opsForValue();
+ operation.set(key, value);
+ return operation;
+ }
+
+ /**
+ * @param key 缓存的键值
+ * @param value 缓存的值
+ * @param timeout 时间
+ * @param timeUnit 时间颗粒度
+ * @return 缓存的对象
+ * @description [缓存基本的对象,Integer、String、实体类等]
+ */
+ public ValueOperations setCacheObject(String key, T value, Integer timeout, TimeUnit timeUnit) {
+ ValueOperations operation = redisTemplate.opsForValue();
+ operation.set(key, value, timeout, timeUnit);
+ return operation;
+ }
+
+ /**
+ * @param key 缓存键值
+ * @return 缓存键值对应的数据
+ * @description [获得缓存的基本对象]
+ */
+ public T getCacheObject(String key) {
+ ValueOperations operation = redisTemplate.opsForValue();
+ return operation.get(key);
+ }
+
+ /**
+ * @description [删除单个对象]
+ */
+ public void deleteObject(String key) {
+ redisTemplate.delete(key);
+ }
+
+ /**
+ * @description [删除集合对象]
+ */
+ public void deleteObject(Collection collection) {
+ redisTemplate.delete(collection);
+ }
+
+ /**
+ * @param key 缓存的键值
+ * @param dataList 待缓存的List数据
+ * @return 缓存的对象
+ * @description [缓存List数据]
+ */
+ public ListOperations setCacheList(String key, List dataList) {
+ ListOperations listOperation = redisTemplate.opsForList();
+ if (null != dataList) {
+ int size = dataList.size();
+ for (int i = 0; i < size; i++) {
+ listOperation.leftPush(key, dataList.get(i));
+ }
+ }
+ return listOperation;
+ }
+
+ /**
+ * @param key 缓存的键值
+ * @return 缓存键值对应的数据
+ * @description [获得缓存的list对象]
+ */
+ public List getCacheList(String key) {
+ List dataList = new ArrayList<>();
+ ListOperations listOperation = redisTemplate.opsForList();
+ Long size = listOperation.size(key);
+
+ for (int i = 0; i < size; i++) {
+ dataList.add(listOperation.index(key, i));
+ }
+ return dataList;
+ }
+
+ /**
+ * @param key 缓存键值
+ * @param dataSet 缓存的数据
+ * @return 缓存数据的对象
+ * @description [缓存Set]
+ */
+ public BoundSetOperations setCacheSet(String key, Set dataSet) {
+ BoundSetOperations setOperation = redisTemplate.boundSetOps(key);
+ Iterator it = dataSet.iterator();
+ while (it.hasNext()) {
+ setOperation.add(it.next());
+ }
+ return setOperation;
+ }
+
+ /**
+ * @description [获得缓存的set]
+ */
+ public Set getCacheSet(String key) {
+ Set dataSet = new HashSet<>();
+ BoundSetOperations operation = redisTemplate.boundSetOps(key);
+ dataSet = operation.members();
+ return dataSet;
+ }
+
+ /**
+ * @description [缓存Map]
+ */
+ public HashOperations setCacheMap(String key, Map dataMap) {
+ HashOperations hashOperations = redisTemplate.opsForHash();
+ if (null != dataMap) {
+ for (Map.Entry entry : dataMap.entrySet()) {
+ hashOperations.put(key, entry.getKey(), entry.getValue());
+ }
+ }
+ return hashOperations;
+ }
+
+ /**
+ * @description [获得缓存的Map]
+ */
+ public Map getCacheMap(String key) {
+ Map map = redisTemplate.opsForHash().entries(key);
+ return map;
+ }
+
+ /**
+ * @param pattern 字符串前缀
+ * @return 对象列表
+ * @description [获得缓存的基本对象列表]
+ */
+ public Collection keys(String pattern) {
+ return redisTemplate.keys(pattern);
+ }
+
+ /**
+ * @description [此key是否存在]
+ */
+ public boolean haskey(String key) {
+ return redisTemplate.hasKey(key);
+ }
+
+ /**
+ * @description [获取key的过期时间]
+ */
+ public Long getExpire(String key) {
+ return redisTemplate.getExpire(key);
+ }
+
+
+ public ValueOperations setBillObject(String key, List