Commit 8fa9ba8ec671639a89e517c7abca509f0af8d1a5

Authored by YunaiV
1 parent 3bf45886

基于 Redis 实现幂等性操作

Showing 22 changed files with 273 additions and 191 deletions
@@ -197,6 +197,11 @@ @@ -197,6 +197,11 @@
197 <artifactId>hutool-http</artifactId> 197 <artifactId>hutool-http</artifactId>
198 <version>${hutool.version}</version> 198 <version>${hutool.version}</version>
199 </dependency> 199 </dependency>
  200 + <dependency>
  201 + <groupId>cn.hutool</groupId>
  202 + <artifactId>hutool-crypto</artifactId>
  203 + <version>${hutool.version}</version>
  204 + </dependency>
200 205
201 <dependency> 206 <dependency>
202 <groupId>com.alibaba</groupId> 207 <groupId>com.alibaba</groupId>
ruoyi-common/src/main/java/com/ruoyi/common/filter/RepeatSubmit.java deleted
1 -package com.ruoyi.common.annotation;  
2 -  
3 -import java.lang.annotation.Documented;  
4 -import java.lang.annotation.ElementType;  
5 -import java.lang.annotation.Inherited;  
6 -import java.lang.annotation.Retention;  
7 -import java.lang.annotation.RetentionPolicy;  
8 -import java.lang.annotation.Target;  
9 -  
10 -/**  
11 - * 自定义注解防止表单重复提交  
12 - *  
13 - * @author ruoyi  
14 - */  
15 -@Inherited  
16 -@Target(ElementType.METHOD)  
17 -@Retention(RetentionPolicy.RUNTIME)  
18 -@Documented  
19 -public @interface RepeatSubmit {  
20 -  
21 -}  
ruoyi-common/src/main/java/com/ruoyi/common/filter/RepeatSubmitInterceptor.java deleted
1 -package com.ruoyi.framework.interceptor;  
2 -  
3 -import java.lang.reflect.Method;  
4 -import javax.servlet.http.HttpServletRequest;  
5 -import javax.servlet.http.HttpServletResponse;  
6 -  
7 -import org.springframework.stereotype.Component;  
8 -import org.springframework.web.method.HandlerMethod;  
9 -import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;  
10 -import com.alibaba.fastjson.JSONObject;  
11 -import com.ruoyi.common.annotation.RepeatSubmit;  
12 -import com.ruoyi.common.core.domain.AjaxResult;  
13 -import com.ruoyi.common.utils.ServletUtils;  
14 -  
15 -/**  
16 - * 防止重复提交拦截器  
17 - *  
18 - * @author ruoyi  
19 - */  
20 -@Component  
21 -public abstract class RepeatSubmitInterceptor extends HandlerInterceptorAdapter {  
22 - @Override  
23 - public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {  
24 - if (handler instanceof HandlerMethod) {  
25 - HandlerMethod handlerMethod = (HandlerMethod) handler;  
26 - Method method = handlerMethod.getMethod();  
27 - RepeatSubmit annotation = method.getAnnotation(RepeatSubmit.class);  
28 - if (annotation != null) {  
29 - if (this.isRepeatSubmit(request)) {  
30 - AjaxResult ajaxResult = AjaxResult.error("不允许重复提交,请稍后再试");  
31 - ServletUtils.renderString(response, JSONObject.toJSONString(ajaxResult));  
32 - return false;  
33 - }  
34 - }  
35 - return true;  
36 - } else {  
37 - return super.preHandle(request, response, handler);  
38 - }  
39 - }  
40 -  
41 - /**  
42 - * 验证是否重复提交由子类实现具体的防重复提交的规则  
43 - *  
44 - * @param request  
45 - * @return  
46 - * @throws Exception  
47 - */  
48 - public abstract boolean isRepeatSubmit(HttpServletRequest request);  
49 -}  
ruoyi-common/src/main/java/com/ruoyi/common/filter/SameUrlDataInterceptor.java deleted
1 -package com.ruoyi.framework.interceptor.impl;  
2 -  
3 -import java.util.HashMap;  
4 -import java.util.Map;  
5 -import java.util.concurrent.TimeUnit;  
6 -import javax.servlet.http.HttpServletRequest;  
7 -  
8 -import org.springframework.beans.factory.annotation.Autowired;  
9 -import org.springframework.beans.factory.annotation.Value;  
10 -import org.springframework.stereotype.Component;  
11 -import com.alibaba.fastjson.JSONObject;  
12 -import com.ruoyi.common.constant.Constants;  
13 -import com.ruoyi.common.core.redis.RedisCache;  
14 -import com.ruoyi.common.filter.RepeatedlyRequestWrapper;  
15 -import com.ruoyi.common.utils.StringUtils;  
16 -import com.ruoyi.common.utils.http.HttpHelper;  
17 -import com.ruoyi.framework.interceptor.RepeatSubmitInterceptor;  
18 -  
19 -/**  
20 - * 判断请求url和数据是否和上一次相同,  
21 - * 如果和上次相同,则是重复提交表单。 有效时间为10秒内。  
22 - *  
23 - * @author ruoyi  
24 - */  
25 -@Component  
26 -public class SameUrlDataInterceptor extends RepeatSubmitInterceptor {  
27 - public final String REPEAT_PARAMS = "repeatParams";  
28 -  
29 - public final String REPEAT_TIME = "repeatTime";  
30 -  
31 - // 令牌自定义标识  
32 - @Value("${token.header}")  
33 - private String header;  
34 -  
35 - @Autowired  
36 - private RedisCache redisCache;  
37 -  
38 - /**  
39 - * 间隔时间,单位:秒 默认10秒  
40 - * <p>  
41 - * 两次相同参数的请求,如果间隔时间大于该参数,系统不会认定为重复提交的数据  
42 - */  
43 - private int intervalTime = 10;  
44 -  
45 - public void setIntervalTime(int intervalTime) {  
46 - this.intervalTime = intervalTime;  
47 - }  
48 -  
49 - @SuppressWarnings("unchecked")  
50 - @Override  
51 - public boolean isRepeatSubmit(HttpServletRequest request) {  
52 - String nowParams = "";  
53 - if (request instanceof RepeatedlyRequestWrapper) {  
54 - RepeatedlyRequestWrapper repeatedlyRequest = (RepeatedlyRequestWrapper) request;  
55 - nowParams = HttpHelper.getBodyString(repeatedlyRequest);  
56 - }  
57 -  
58 - // body参数为空,获取Parameter的数据  
59 - if (StringUtils.isEmpty(nowParams)) {  
60 - nowParams = JSONObject.toJSONString(request.getParameterMap());  
61 - }  
62 - Map<String, Object> nowDataMap = new HashMap<String, Object>();  
63 - nowDataMap.put(REPEAT_PARAMS, nowParams);  
64 - nowDataMap.put(REPEAT_TIME, System.currentTimeMillis());  
65 -  
66 - // 请求地址(作为存放cache的key值)  
67 - String url = request.getRequestURI();  
68 -  
69 - // 唯一值(没有消息头则使用请求地址)  
70 - String submitKey = request.getHeader(header);  
71 - if (StringUtils.isEmpty(submitKey)) {  
72 - submitKey = url;  
73 - }  
74 -  
75 - // 唯一标识(指定key + 消息头)  
76 - String cache_repeat_key = Constants.REPEAT_SUBMIT_KEY + submitKey;  
77 -  
78 - Object sessionObj = redisCache.getCacheObject(cache_repeat_key);  
79 - if (sessionObj != null) {  
80 - Map<String, Object> sessionMap = (Map<String, Object>) sessionObj;  
81 - if (sessionMap.containsKey(url)) {  
82 - Map<String, Object> preDataMap = (Map<String, Object>) sessionMap.get(url);  
83 - if (compareParams(nowDataMap, preDataMap) && compareTime(nowDataMap, preDataMap)) {  
84 - return true;  
85 - }  
86 - }  
87 - }  
88 - Map<String, Object> cacheMap = new HashMap<String, Object>();  
89 - cacheMap.put(url, nowDataMap);  
90 - redisCache.setCacheObject(cache_repeat_key, cacheMap, intervalTime, TimeUnit.SECONDS);  
91 - return false;  
92 - }  
93 -  
94 - /**  
95 - * 判断参数是否相同  
96 - */  
97 - private boolean compareParams(Map<String, Object> nowMap, Map<String, Object> preMap) {  
98 - String nowParams = (String) nowMap.get(REPEAT_PARAMS);  
99 - String preParams = (String) preMap.get(REPEAT_PARAMS);  
100 - return nowParams.equals(preParams);  
101 - }  
102 -  
103 - /**  
104 - * 判断两次间隔时间  
105 - */  
106 - private boolean compareTime(Map<String, Object> nowMap, Map<String, Object> preMap) {  
107 - long time1 = (Long) nowMap.get(REPEAT_TIME);  
108 - long time2 = (Long) preMap.get(REPEAT_TIME);  
109 - if ((time1 - time2) < (this.intervalTime * 1000)) {  
110 - return true;  
111 - }  
112 - return false;  
113 - }  
114 -}  
src/main/java/cn/iocoder/dashboard/common/exception/enums/GlobalErrorCodeConstants.java
@@ -28,6 +28,9 @@ public interface GlobalErrorCodeConstants { @@ -28,6 +28,9 @@ public interface GlobalErrorCodeConstants {
28 28
29 ErrorCode INTERNAL_SERVER_ERROR = new ErrorCode(500, "系统异常"); 29 ErrorCode INTERNAL_SERVER_ERROR = new ErrorCode(500, "系统异常");
30 30
  31 + // ========== 自定义错误段 ==========
  32 + ErrorCode REPEATED_REQUESTS = new ErrorCode(900, "重复请求");
  33 +
31 ErrorCode UNKNOWN = new ErrorCode(999, "未知错误"); 34 ErrorCode UNKNOWN = new ErrorCode(999, "未知错误");
32 35
33 static boolean isMatch(Integer code) { 36 static boolean isMatch(Integer code) {
src/main/java/cn/iocoder/dashboard/framework/dict/package-info.java
1 /** 1 /**
2 - * 字典数据模块 2 + * 字典数据模块,提供 {@link cn.iocoder.dashboard.framework.dict.core.util.DictUtils} 工具类
3 * 3 *
4 * 通过将字典缓存在内存中,保证性能 4 * 通过将字典缓存在内存中,保证性能
5 */ 5 */
src/main/java/cn/iocoder/dashboard/framework/idempotent/config/IdempotentConfiguration.java 0 → 100644
  1 +package cn.iocoder.dashboard.framework.idempotent.config;
  2 +
  3 +import cn.iocoder.dashboard.framework.idempotent.core.aop.IdempotentAspect;
  4 +import cn.iocoder.dashboard.framework.idempotent.core.keyresolver.DefaultIdempotentKeyResolver;
  5 +import cn.iocoder.dashboard.framework.idempotent.core.keyresolver.ExpressionIdempotentKeyResolver;
  6 +import cn.iocoder.dashboard.framework.idempotent.core.keyresolver.IdempotentKeyResolver;
  7 +import cn.iocoder.dashboard.framework.idempotent.core.redis.IdempotentRedisDAO;
  8 +import org.springframework.boot.autoconfigure.AutoConfigureAfter;
  9 +import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
  10 +import org.springframework.context.annotation.Bean;
  11 +import org.springframework.context.annotation.Configuration;
  12 +import org.springframework.data.redis.core.StringRedisTemplate;
  13 +
  14 +import java.util.List;
  15 +
  16 +@Configuration(proxyBeanMethods = false)
  17 +@AutoConfigureAfter(RedisAutoConfiguration.class)
  18 +public class IdempotentConfiguration {
  19 +
  20 + @Bean
  21 + public IdempotentAspect idempotentAspect(List<IdempotentKeyResolver> keyResolvers, IdempotentRedisDAO idempotentRedisDAO) {
  22 + return new IdempotentAspect(keyResolvers, idempotentRedisDAO);
  23 + }
  24 +
  25 + @Bean
  26 + public IdempotentRedisDAO idempotentRedisDAO(StringRedisTemplate stringRedisTemplate) {
  27 + return new IdempotentRedisDAO(stringRedisTemplate);
  28 + }
  29 +
  30 + // ========== 各种 IdempotentKeyResolver Bean ==========
  31 +
  32 + @Bean
  33 + public DefaultIdempotentKeyResolver defaultIdempotentKeyResolver() {
  34 + return new DefaultIdempotentKeyResolver();
  35 + }
  36 +
  37 + @Bean
  38 + public ExpressionIdempotentKeyResolver expressionIdempotentKeyResolver() {
  39 + return new ExpressionIdempotentKeyResolver();
  40 + }
  41 +
  42 +}
src/main/java/cn/iocoder/dashboard/framework/idempotent/core/annotation/Idempotent.java 0 → 100644
  1 +package cn.iocoder.dashboard.framework.idempotent.core.annotation;
  2 +
  3 +import cn.iocoder.dashboard.framework.idempotent.core.keyresolver.DefaultIdempotentKeyResolver;
  4 +import cn.iocoder.dashboard.framework.idempotent.core.keyresolver.IdempotentKeyResolver;
  5 +
  6 +import java.lang.annotation.ElementType;
  7 +import java.lang.annotation.Retention;
  8 +import java.lang.annotation.RetentionPolicy;
  9 +import java.lang.annotation.Target;
  10 +import java.util.concurrent.TimeUnit;
  11 +
  12 +/**
  13 + * 幂等注解
  14 + *
  15 + * @author 芋道源码
  16 + */
  17 +@Target({ElementType.METHOD})
  18 +@Retention(RetentionPolicy.RUNTIME)
  19 +public @interface Idempotent {
  20 +
  21 + /**
  22 + * 幂等的超时时间,默认为 1 秒
  23 + *
  24 + * 注意,如果执行时间超过它,请求还是会进来
  25 + */
  26 + int timeout() default 1;
  27 + /**
  28 + * 时间单位,默认为 SECONDS 秒
  29 + */
  30 + TimeUnit timeUnit() default TimeUnit.SECONDS;
  31 +
  32 + /**
  33 + * 提示信息,正在执行中的提示
  34 + */
  35 + String message() default "重复请求,请稍后重试";
  36 +
  37 + /**
  38 + * 使用的 Key 解析器
  39 + */
  40 + Class<? extends IdempotentKeyResolver> keyResolver() default DefaultIdempotentKeyResolver.class;
  41 + /**
  42 + * 使用的 Key 参数
  43 + */
  44 + String keyArg() default "";
  45 +
  46 +}
src/main/java/cn/iocoder/dashboard/framework/idempotent/core/aop/IdempotentAspect.java 0 → 100644
  1 +package cn.iocoder.dashboard.framework.idempotent.core.aop;
  2 +
  3 +import cn.iocoder.dashboard.common.exception.ServiceException;
  4 +import cn.iocoder.dashboard.common.exception.enums.GlobalErrorCodeConstants;
  5 +import cn.iocoder.dashboard.framework.idempotent.core.annotation.Idempotent;
  6 +import cn.iocoder.dashboard.framework.idempotent.core.keyresolver.IdempotentKeyResolver;
  7 +import cn.iocoder.dashboard.framework.idempotent.core.redis.IdempotentRedisDAO;
  8 +import cn.iocoder.dashboard.util.collection.CollectionUtils;
  9 +import lombok.extern.slf4j.Slf4j;
  10 +import org.aspectj.lang.JoinPoint;
  11 +import org.aspectj.lang.annotation.Aspect;
  12 +import org.aspectj.lang.annotation.Before;
  13 +import org.springframework.util.Assert;
  14 +
  15 +import java.util.List;
  16 +import java.util.Map;
  17 +
  18 +/**
  19 + * 拦截声明了 {@link Idempotent} 注解的方法,实现幂等操作
  20 + *
  21 + * @author 芋道源码
  22 + */
  23 +@Aspect
  24 +@Slf4j
  25 +public class IdempotentAspect {
  26 +
  27 + /**
  28 + * IdempotentKeyResolver 集合
  29 + */
  30 + private final Map<Class<? extends IdempotentKeyResolver>, IdempotentKeyResolver> keyResolvers;
  31 +
  32 + private final IdempotentRedisDAO idempotentRedisDAO;
  33 +
  34 + public IdempotentAspect(List<IdempotentKeyResolver> keyResolvers, IdempotentRedisDAO idempotentRedisDAO) {
  35 + this.keyResolvers = CollectionUtils.convertMap(keyResolvers, IdempotentKeyResolver::getClass);
  36 + this.idempotentRedisDAO = idempotentRedisDAO;
  37 + }
  38 +
  39 + @Before("@annotation(idempotent)")
  40 + public void beforePointCut(JoinPoint joinPoint, Idempotent idempotent) {
  41 + // 获得 IdempotentKeyResolver
  42 + IdempotentKeyResolver keyResolver = keyResolvers.get(idempotent.keyResolver());
  43 + Assert.notNull(keyResolver, "找不到对应的 IdempotentKeyResolver");
  44 + // 解析 Key
  45 + String key = keyResolver.resolver(joinPoint, idempotent);
  46 +
  47 + // 锁定 Key。
  48 + boolean success = idempotentRedisDAO.setIfAbsent(key, idempotent.timeout(), idempotent.timeUnit());
  49 + // 锁定失败,抛出异常
  50 + if (!success) {
  51 + log.info("[beforePointCut][方法({}) 参数({}) 存在重复请求]", joinPoint.getSignature().toString(), joinPoint.getArgs());
  52 + throw new ServiceException(GlobalErrorCodeConstants.REPEATED_REQUESTS.getCode(), idempotent.message());
  53 + }
  54 + }
  55 +
  56 +}
src/main/java/cn/iocoder/dashboard/framework/idempotent/core/keyresolver/DefaultIdempotentKeyResolver.java 0 → 100644
  1 +package cn.iocoder.dashboard.framework.idempotent.core.keyresolver;
  2 +
  3 +import cn.hutool.core.util.StrUtil;
  4 +import cn.hutool.crypto.SecureUtil;
  5 +import cn.iocoder.dashboard.framework.idempotent.core.annotation.Idempotent;
  6 +import org.aspectj.lang.JoinPoint;
  7 +
  8 +/**
  9 + * 默认幂等 Key 解析器,使用方法名 + 方法参数,组装成一个 Key
  10 + *
  11 + * 为了避免 Key 过长,使用 MD5 进行“压缩”
  12 + *
  13 + * @author 芋道源码
  14 + */
  15 +public class DefaultIdempotentKeyResolver implements IdempotentKeyResolver {
  16 +
  17 + @Override
  18 + public String resolver(JoinPoint joinPoint, Idempotent idempotent) {
  19 + String methodName = joinPoint.getSignature().toString();
  20 + String argsStr = StrUtil.join(",", joinPoint.getArgs());
  21 + return SecureUtil.md5(methodName + argsStr);
  22 + }
  23 +
  24 +}
src/main/java/cn/iocoder/dashboard/framework/idempotent/core/keyresolver/ExpressionIdempotentKeyResolver.java 0 → 100644
  1 +package cn.iocoder.dashboard.framework.idempotent.core.keyresolver;
  2 +
  3 +import cn.iocoder.dashboard.framework.idempotent.core.annotation.Idempotent;
  4 +import org.aspectj.lang.JoinPoint;
  5 +
  6 +/**
  7 + * 基于 Spring EL 表达式,
  8 + *
  9 + * @author 芋道源码
  10 + */
  11 +public class ExpressionIdempotentKeyResolver implements IdempotentKeyResolver {
  12 +
  13 + @Override
  14 + public String resolver(JoinPoint joinPoint, Idempotent idempotent) {
  15 + // TODO 稍后实现
  16 + return null;
  17 + }
  18 +
  19 +}
src/main/java/cn/iocoder/dashboard/framework/idempotent/core/keyresolver/IdempotentKeyResolver.java 0 → 100644
  1 +package cn.iocoder.dashboard.framework.idempotent.core.keyresolver;
  2 +
  3 +import cn.iocoder.dashboard.framework.idempotent.core.annotation.Idempotent;
  4 +import org.aspectj.lang.JoinPoint;
  5 +
  6 +/**
  7 + * 幂等 Key 解析器接口
  8 + *
  9 + * @author 芋道源码
  10 + */
  11 +public interface IdempotentKeyResolver {
  12 +
  13 + /**
  14 + * 解析一个 Key
  15 + *
  16 + * @param idempotent 幂等注解
  17 + * @param joinPoint AOP 切面
  18 + * @return Key
  19 + */
  20 + String resolver(JoinPoint joinPoint, Idempotent idempotent);
  21 +
  22 +}
src/main/java/cn/iocoder/dashboard/framework/idempotent/core/redis/IdempotentRedisDAO.java 0 → 100644
  1 +package cn.iocoder.dashboard.framework.idempotent.core.redis;
  2 +
  3 +import cn.iocoder.dashboard.framework.redis.core.RedisKeyDefine;
  4 +import lombok.AllArgsConstructor;
  5 +import org.springframework.data.redis.core.StringRedisTemplate;
  6 +
  7 +import java.util.concurrent.TimeUnit;
  8 +
  9 +import static cn.iocoder.dashboard.framework.redis.core.RedisKeyDefine.KeyTypeEnum.STRING;
  10 +
  11 +/**
  12 + * 幂等 Redis DAO
  13 + *
  14 + * @author 芋道源码
  15 + */
  16 +@AllArgsConstructor
  17 +public class IdempotentRedisDAO {
  18 +
  19 + private static final RedisKeyDefine IDEMPOTENT = new RedisKeyDefine("幂等操作",
  20 + "idempotent:%s", // 参数为 uuid
  21 + STRING, String.class, RedisKeyDefine.TimeoutTypeEnum.DYNAMIC);
  22 +
  23 + private final StringRedisTemplate redisTemplate;
  24 +
  25 + public Boolean setIfAbsent(String key, long timeout, TimeUnit timeUnit) {
  26 + String redisKey = formatKey(key);
  27 + return redisTemplate.opsForValue().setIfAbsent(redisKey, "", timeout, timeUnit);
  28 + }
  29 +
  30 + private static String formatKey(String key) {
  31 + return String.format(IDEMPOTENT.getKeyTemplate(), key);
  32 + }
  33 +
  34 +}
src/main/java/cn/iocoder/dashboard/framework/idempotent/package-info.java 0 → 100644
  1 +/**
  2 + * 幂等组件,参考 https://github.com/it4alla/idempotent 项目实现
  3 + */
  4 +package cn.iocoder.dashboard.framework.idempotent;
src/main/java/cn/iocoder/dashboard/framework/logger/operatelog/core/annotations/OperateLog.java
@@ -9,6 +9,11 @@ import java.lang.annotation.Retention; @@ -9,6 +9,11 @@ import java.lang.annotation.Retention;
9 import java.lang.annotation.RetentionPolicy; 9 import java.lang.annotation.RetentionPolicy;
10 import java.lang.annotation.Target; 10 import java.lang.annotation.Target;
11 11
  12 +/**
  13 + * 操作日志注解
  14 + *
  15 + * @author 芋道源码
  16 + */
12 @Target({ElementType.METHOD}) 17 @Target({ElementType.METHOD})
13 @Retention(RetentionPolicy.RUNTIME) 18 @Retention(RetentionPolicy.RUNTIME)
14 public @interface OperateLog { 19 public @interface OperateLog {
src/main/java/cn/iocoder/dashboard/modules/infra/controller/config/InfConfigController.java
@@ -4,6 +4,7 @@ import cn.iocoder.dashboard.common.exception.util.ServiceExceptionUtil; @@ -4,6 +4,7 @@ import cn.iocoder.dashboard.common.exception.util.ServiceExceptionUtil;
4 import cn.iocoder.dashboard.common.pojo.CommonResult; 4 import cn.iocoder.dashboard.common.pojo.CommonResult;
5 import cn.iocoder.dashboard.common.pojo.PageResult; 5 import cn.iocoder.dashboard.common.pojo.PageResult;
6 import cn.iocoder.dashboard.framework.excel.core.util.ExcelUtils; 6 import cn.iocoder.dashboard.framework.excel.core.util.ExcelUtils;
  7 +import cn.iocoder.dashboard.framework.idempotent.core.annotation.Idempotent;
7 import cn.iocoder.dashboard.modules.infra.controller.config.vo.*; 8 import cn.iocoder.dashboard.modules.infra.controller.config.vo.*;
8 import cn.iocoder.dashboard.modules.infra.convert.config.InfConfigConvert; 9 import cn.iocoder.dashboard.modules.infra.convert.config.InfConfigConvert;
9 import cn.iocoder.dashboard.modules.infra.dal.dataobject.config.InfConfigDO; 10 import cn.iocoder.dashboard.modules.infra.dal.dataobject.config.InfConfigDO;
@@ -91,7 +92,7 @@ public class InfConfigController { @@ -91,7 +92,7 @@ public class InfConfigController {
91 @PostMapping("/create") 92 @PostMapping("/create")
92 // @PreAuthorize("@ss.hasPermi('infra:config:add')") 93 // @PreAuthorize("@ss.hasPermi('infra:config:add')")
93 // @Log(title = "参数管理", businessType = BusinessType.INSERT) 94 // @Log(title = "参数管理", businessType = BusinessType.INSERT)
94 -// @RepeatSubmit 95 + @Idempotent(timeout = 10)
95 public CommonResult<Long> createConfig(@Validated @RequestBody InfConfigCreateReqVO reqVO) { 96 public CommonResult<Long> createConfig(@Validated @RequestBody InfConfigCreateReqVO reqVO) {
96 return success(configService.createConfig(reqVO)); 97 return success(configService.createConfig(reqVO));
97 } 98 }
@@ -100,7 +101,8 @@ public class InfConfigController { @@ -100,7 +101,8 @@ public class InfConfigController {
100 @PutMapping("/update") 101 @PutMapping("/update")
101 // @PreAuthorize("@ss.hasPermi('infra:config:edit')") 102 // @PreAuthorize("@ss.hasPermi('infra:config:edit')")
102 // @Log(title = "参数管理", businessType = BusinessType.UPDATE) 103 // @Log(title = "参数管理", businessType = BusinessType.UPDATE)
103 - public CommonResult<Boolean> edit(@Validated @RequestBody InfConfigUpdateReqVO reqVO) { 104 + @Idempotent(timeout = 60)
  105 + public CommonResult<Boolean> updateConfig(@Validated @RequestBody InfConfigUpdateReqVO reqVO) {
104 configService.updateConfig(reqVO); 106 configService.updateConfig(reqVO);
105 return success(true); 107 return success(true);
106 } 108 }
src/main/java/cn/iocoder/dashboard/modules/infra/controller/config/vo/InfConfigUpdateReqVO.java
@@ -4,12 +4,14 @@ import io.swagger.annotations.ApiModel; @@ -4,12 +4,14 @@ import io.swagger.annotations.ApiModel;
4 import io.swagger.annotations.ApiModelProperty; 4 import io.swagger.annotations.ApiModelProperty;
5 import lombok.Data; 5 import lombok.Data;
6 import lombok.EqualsAndHashCode; 6 import lombok.EqualsAndHashCode;
  7 +import lombok.ToString;
7 8
8 import javax.validation.constraints.NotNull; 9 import javax.validation.constraints.NotNull;
9 10
10 @ApiModel("参数配置创建 Request VO") 11 @ApiModel("参数配置创建 Request VO")
11 @Data 12 @Data
12 @EqualsAndHashCode(callSuper = true) 13 @EqualsAndHashCode(callSuper = true)
  14 +@ToString(callSuper = true)
13 public class InfConfigUpdateReqVO extends InfConfigBaseVO { 15 public class InfConfigUpdateReqVO extends InfConfigBaseVO {
14 16
15 @ApiModelProperty(value = "参数配置序号", required = true, example = "1024") 17 @ApiModelProperty(value = "参数配置序号", required = true, example = "1024")
src/main/java/cn/iocoder/dashboard/modules/system/dal/redis/RedisKeyConstants.java renamed to src/main/java/cn/iocoder/dashboard/modules/system/dal/redis/SysRedisKeyConstants.java
@@ -12,7 +12,7 @@ import static cn.iocoder.dashboard.framework.redis.core.RedisKeyDefine.KeyTypeEn @@ -12,7 +12,7 @@ import static cn.iocoder.dashboard.framework.redis.core.RedisKeyDefine.KeyTypeEn
12 * 12 *
13 * @author 芋道源码 13 * @author 芋道源码
14 */ 14 */
15 -public interface RedisKeyConstants { 15 +public interface SysRedisKeyConstants {
16 16
17 RedisKeyDefine LOGIN_USER = new RedisKeyDefine("登陆用户的缓存", 17 RedisKeyDefine LOGIN_USER = new RedisKeyDefine("登陆用户的缓存",
18 "login_user:%s", // 参数为 sessionId 18 "login_user:%s", // 参数为 sessionId
src/main/java/cn/iocoder/dashboard/modules/system/dal/redis/auth/SysLoginUserRedisDAO.java
@@ -7,7 +7,7 @@ import org.springframework.stereotype.Repository; @@ -7,7 +7,7 @@ import org.springframework.stereotype.Repository;
7 7
8 import javax.annotation.Resource; 8 import javax.annotation.Resource;
9 9
10 -import static cn.iocoder.dashboard.modules.system.dal.redis.RedisKeyConstants.LOGIN_USER; 10 +import static cn.iocoder.dashboard.modules.system.dal.redis.SysRedisKeyConstants.LOGIN_USER;
11 11
12 /** 12 /**
13 * {@link LoginUser} 的 RedisDAO 13 * {@link LoginUser} 的 RedisDAO
src/main/java/cn/iocoder/dashboard/modules/system/dal/redis/common/SysCaptchaRedisDAO.java
@@ -6,7 +6,7 @@ import org.springframework.stereotype.Repository; @@ -6,7 +6,7 @@ import org.springframework.stereotype.Repository;
6 import javax.annotation.Resource; 6 import javax.annotation.Resource;
7 import java.time.Duration; 7 import java.time.Duration;
8 8
9 -import static cn.iocoder.dashboard.modules.system.dal.redis.RedisKeyConstants.CAPTCHA_CODE; 9 +import static cn.iocoder.dashboard.modules.system.dal.redis.SysRedisKeyConstants.CAPTCHA_CODE;
10 10
11 /** 11 /**
12 * 验证码的 Redis DAO 12 * 验证码的 Redis DAO
src/main/resources/application-dev.yaml
@@ -98,6 +98,8 @@ spring: @@ -98,6 +98,8 @@ spring:
98 # Spring Boot Admin Client 客户端的相关配置 98 # Spring Boot Admin Client 客户端的相关配置
99 client: 99 client:
100 url: http://127.0.0.1:${server.port}/${spring.boot.admin.context-path} # 设置 Spring Boot Admin Server 地址 100 url: http://127.0.0.1:${server.port}/${spring.boot.admin.context-path} # 设置 Spring Boot Admin Server 地址
  101 + instance:
  102 + prefer-ip: true # 注册实例时,优先使用 IP
101 # Spring Boot Admin Server 服务端的相关配置 103 # Spring Boot Admin Server 服务端的相关配置
102 context-path: /admin # 配置 Spring 104 context-path: /admin # 配置 Spring
103 105
src/main/resources/application-local.yaml
@@ -99,7 +99,7 @@ spring: @@ -99,7 +99,7 @@ spring:
99 client: 99 client:
100 url: http://127.0.0.1:${server.port}/${spring.boot.admin.context-path} # 设置 Spring Boot Admin Server 地址 100 url: http://127.0.0.1:${server.port}/${spring.boot.admin.context-path} # 设置 Spring Boot Admin Server 地址
101 instance: 101 instance:
102 - prefer-ip: true 102 + prefer-ip: true # 注册实例时,优先使用 IP
103 # Spring Boot Admin Server 服务端的相关配置 103 # Spring Boot Admin Server 服务端的相关配置
104 context-path: /admin # 配置 Spring 104 context-path: /admin # 配置 Spring
105 105