From 24cd7537459badf59ae6f38d195f2cf2586d95bb Mon Sep 17 00:00:00 2001 From: anthonywj Date: Fri, 25 Aug 2023 14:38:58 +0800 Subject: [PATCH] =?UTF-8?q?=E8=AE=BE=E5=A4=87=E6=98=8E=E7=BB=86=E8=A1=A8?= =?UTF-8?q?=E5=AD=97=E6=AE=B5=E8=A1=A5=E5=85=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../glxp/api/aspect/RepeatSubmitAspect.java | 163 ++++++++++++++++++ .../glxp/api/controller/TestController.java | 6 + .../entity/inv/DeptDeviceDetailEntity.java | 23 ++- src/main/resources/schemas/schema_v2.2.sql | 5 + 4 files changed, 195 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/glxp/api/aspect/RepeatSubmitAspect.java diff --git a/src/main/java/com/glxp/api/aspect/RepeatSubmitAspect.java b/src/main/java/com/glxp/api/aspect/RepeatSubmitAspect.java new file mode 100644 index 000000000..e4a9b1b9c --- /dev/null +++ b/src/main/java/com/glxp/api/aspect/RepeatSubmitAspect.java @@ -0,0 +1,163 @@ +package com.glxp.api.aspect; + +import cn.dev33.satoken.SaManager; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.crypto.SecureUtil; +import com.glxp.api.annotation.RepeatSubmit; +import com.glxp.api.common.res.BaseResponse; +import com.glxp.api.common.util.ResultVOUtils; +import com.glxp.api.exception.ServiceException; +import com.glxp.api.util.*; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.annotation.AfterReturning; +import org.aspectj.lang.annotation.AfterThrowing; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; +import org.springframework.stereotype.Component; +import org.springframework.validation.BindingResult; +import org.springframework.web.multipart.MultipartFile; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.time.Duration; +import java.util.Collection; +import java.util.Map; + +/** + * 防止重复提交(参考美团GTIS防重系统) + */ +@Slf4j +//@RequiredArgsConstructor +//@Aspect +//@Component +public class RepeatSubmitAspect { + + @Resource + RedisUtil redisUtil; + + private static final ThreadLocal KEY_CACHE = new ThreadLocal<>(); + + @Before("@annotation(repeatSubmit)") + public void doBefore(JoinPoint point, RepeatSubmit repeatSubmit) throws Throwable { + // 如果注解不为0 则使用注解数值 + long interval = 0; + if (repeatSubmit.interval() > 0) { + interval = repeatSubmit.timeUnit().toMillis(repeatSubmit.interval()); + } + log.error("不要重复提交,谢谢--" + interval); + if (interval < 1000) { + log.error("不要重复提交,谢谢"); + throw new ServiceException("重复提交间隔时间不能小于'1'秒"); + } + HttpServletRequest request = ServletUtils.getRequest(); + String nowParams = argsArrayToString(point.getArgs()); + + // 请求地址(作为存放cache的key值) + String url = request.getRequestURI(); + + // 唯一值(没有消息头则使用请求地址) + String submitKey = StringUtils.trimToEmpty(request.getHeader(SaManager.getConfig().getTokenName())); + + submitKey = SecureUtil.md5(submitKey + ":" + nowParams); + // 唯一标识(指定key + url + 消息头) + String cacheRepeatKey = "REPEAT_SUBMIT_KEY" + url + submitKey; + String key = (String) redisUtil.get(cacheRepeatKey); + if (key == null) { + redisUtil.set(cacheRepeatKey, "", Duration.ofMillis(interval).toMillis()); + KEY_CACHE.set(cacheRepeatKey); + } else { + String message = repeatSubmit.message(); +// if (StringUtils.startsWith(message, "{") && StringUtils.endsWith(message, "}")) { +// message = MessageUtils.message(StringUtils.substring(message, 1, message.length() - 1)); +// } + throw new ServiceException("重复提交", 500); + } + } + + /** + * 处理完请求后执行 + * + * @param joinPoint 切点 + */ + @AfterReturning(pointcut = "@annotation(repeatSubmit)", returning = "jsonResult") + public void doAfterReturning(JoinPoint joinPoint, RepeatSubmit repeatSubmit, Object jsonResult) { + if (jsonResult instanceof BaseResponse) { + try { + BaseResponse r = (BaseResponse) jsonResult; + // 成功则不删除redis数据 保证在有效时间内无法重复提交 + if (r.getCode() == 20000) { + return; + } + redisUtil.del(KEY_CACHE.get()); + } finally { + KEY_CACHE.remove(); + } + } + } + + /** + * 拦截异常操作 + * + * @param joinPoint 切点 + * @param e 异常 + */ + @AfterThrowing(value = "@annotation(repeatSubmit)", throwing = "e") + public void doAfterThrowing(JoinPoint joinPoint, RepeatSubmit repeatSubmit, Exception e) { + if (KEY_CACHE.get() != null) { + redisUtil.del(KEY_CACHE.get()); + KEY_CACHE.remove(); + } + + } + + /** + * 参数拼装 + */ + private String argsArrayToString(Object[] paramsArray) { + StringBuilder params = new StringBuilder(); + if (paramsArray != null && paramsArray.length > 0) { + for (Object o : paramsArray) { + if (ObjectUtil.isNotNull(o) && !isFilterObject(o)) { + try { + params.append(JsonUtils.toJsonString(o)).append(" "); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + } + return params.toString().trim(); + } + + /** + * 判断是否需要过滤的对象。 + * + * @param o 对象信息。 + * @return 如果是需要过滤的对象,则返回true;否则返回false。 + */ + @SuppressWarnings("rawtypes") + public boolean isFilterObject(final Object o) { + Class clazz = o.getClass(); + if (clazz.isArray()) { + return clazz.getComponentType().isAssignableFrom(MultipartFile.class); + } else if (Collection.class.isAssignableFrom(clazz)) { + Collection collection = (Collection) o; + for (Object value : collection) { + return value instanceof MultipartFile; + } + } else if (Map.class.isAssignableFrom(clazz)) { + Map map = (Map) o; + for (Object value : map.entrySet()) { + Map.Entry entry = (Map.Entry) value; + return entry.getValue() instanceof MultipartFile; + } + } + return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse + || o instanceof BindingResult; + } + +} diff --git a/src/main/java/com/glxp/api/controller/TestController.java b/src/main/java/com/glxp/api/controller/TestController.java index 794044c16..27ef732b3 100644 --- a/src/main/java/com/glxp/api/controller/TestController.java +++ b/src/main/java/com/glxp/api/controller/TestController.java @@ -1,5 +1,6 @@ package com.glxp.api.controller; +import com.glxp.api.annotation.RepeatSubmit; import com.glxp.api.common.res.BaseResponse; import com.glxp.api.common.util.ResultVOUtils; import com.glxp.api.req.sync.BasicExportStatusRequest; @@ -25,4 +26,9 @@ public class TestController { return ResultVOUtils.success("生成成功!" + start + "=====" + end + "\n-------" + (start - end)); } + @RepeatSubmit() + @GetMapping("/test/repeat") + public BaseResponse testRepeat(String message) { + return ResultVOUtils.success("hello"); + } } diff --git a/src/main/java/com/glxp/api/entity/inv/DeptDeviceDetailEntity.java b/src/main/java/com/glxp/api/entity/inv/DeptDeviceDetailEntity.java index 6bb860e8a..98049293f 100644 --- a/src/main/java/com/glxp/api/entity/inv/DeptDeviceDetailEntity.java +++ b/src/main/java/com/glxp/api/entity/inv/DeptDeviceDetailEntity.java @@ -116,7 +116,7 @@ public class DeptDeviceDetailEntity { private String supName; /** - * 状态(1:正常;2:报修;3:养护中;4:已养护;5:已报废) + * 状态(1:正常;2:报修;3:巡检中;4、维修中;5、报废;6、使用中;7:空闲;) */ @TableField(value = "`status`") private Integer status; @@ -165,4 +165,23 @@ public class DeptDeviceDetailEntity { @TableField(value = "remark") private String remark; -} \ No newline at end of file + + /** + * 当前使用人 + */ + @TableField(value = "curUser") + private String curUser; + + /** + * 位置 + */ + @TableField(value = "location") + private String location; + + /** + * 详细位置 + */ + @TableField(value = "detailLocation") + private String detailLocation; + +} diff --git a/src/main/resources/schemas/schema_v2.2.sql b/src/main/resources/schemas/schema_v2.2.sql index 6ba6201a0..fd6742c00 100644 --- a/src/main/resources/schemas/schema_v2.2.sql +++ b/src/main/resources/schemas/schema_v2.2.sql @@ -238,3 +238,8 @@ CREATE TABLE IF NOT EXISTS `device_asset_usage` ( `updateTime` datetime(0) NULL DEFAULT NULL COMMENT '更新时间', PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic; + + +CALL Pro_Temp_ColumnWork('dept_device_detail', 'curUser', 'varchar(255)', 1); +CALL Pro_Temp_ColumnWork('dept_device_detail', 'location', 'varchar(255)', 1); +CALL Pro_Temp_ColumnWork('dept_device_detail', 'detailLocation', 'varchar(255)', 1);