diff --git a/epmet-commons/epmet-commons-tools/src/main/java/com/epmet/commons/tools/aop/NoRepeatSubmit.java b/epmet-commons/epmet-commons-tools/src/main/java/com/epmet/commons/tools/aop/NoRepeatSubmit.java index aad5ee733a..d4f32340ab 100644 --- a/epmet-commons/epmet-commons-tools/src/main/java/com/epmet/commons/tools/aop/NoRepeatSubmit.java +++ b/epmet-commons/epmet-commons-tools/src/main/java/com/epmet/commons/tools/aop/NoRepeatSubmit.java @@ -13,5 +13,9 @@ import java.lang.annotation.Target; @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface NoRepeatSubmit { + /** + * 持锁时长 单位:毫秒 默认10秒 + */ + long leaseTime() default 10*1000; -} \ No newline at end of file +} diff --git a/epmet-commons/epmet-commons-tools/src/main/java/com/epmet/commons/tools/aop/NoRepeatSubmitAop.java b/epmet-commons/epmet-commons-tools/src/main/java/com/epmet/commons/tools/aop/NoRepeatSubmitAop.java index 7dfbd88a75..6b21bbaaae 100644 --- a/epmet-commons/epmet-commons-tools/src/main/java/com/epmet/commons/tools/aop/NoRepeatSubmitAop.java +++ b/epmet-commons/epmet-commons-tools/src/main/java/com/epmet/commons/tools/aop/NoRepeatSubmitAop.java @@ -1,15 +1,16 @@ package com.epmet.commons.tools.aop; import com.epmet.commons.tools.constant.NumConstant; +import com.epmet.commons.tools.distributedlock.DistributedLock; import com.epmet.commons.tools.exception.EpmetErrorCode; import com.epmet.commons.tools.exception.RenException; -import com.google.common.cache.Cache; -import com.google.common.cache.CacheBuilder; +import com.epmet.commons.tools.redis.RedisKeys; import lombok.extern.slf4j.Slf4j; -import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; -import org.aspectj.lang.annotation.Before; -import org.aspectj.lang.annotation.Pointcut; +import org.redisson.api.RLock; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; @@ -18,6 +19,7 @@ import javax.servlet.http.HttpServletRequest; import java.util.concurrent.TimeUnit; /** + * desc:利用用户token及请求url 防止用户重复提交,需要在没有唯一索引的保存或修改操作 方法入口上加上@NoRepeatSubMit注解 * @author zhaoqifeng * @dscription * @date 2020/11/24 9:59 @@ -26,51 +28,49 @@ import java.util.concurrent.TimeUnit; @Configuration @Slf4j public class NoRepeatSubmitAop { + private static final String AUTHORIZATION_TOKEN_HEADER_KEY = "Authorization"; + @Autowired + private DistributedLock distributedLock; - /** - * 重复提交判断时间为2s - */ - private static final Cache CACHES = CacheBuilder.newBuilder() - // 最大缓存 100 个 - .maximumSize(100) - // 设置写缓存后 5 秒钟过期 - .expireAfterWrite(5, TimeUnit.SECONDS) - .build(); + @Around("@annotation(noRepeatSubmit)") + public Object around(ProceedingJoinPoint pjp,NoRepeatSubmit noRepeatSubmit) { - @Pointcut("@annotation(com.epmet.commons.tools.aop.NoRepeatSubmit)") - public void doAspect() { + try { + ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + assert attributes != null; + HttpServletRequest request = attributes.getRequest(); + String internalToken = request.getHeader(AUTHORIZATION_TOKEN_HEADER_KEY); + String key = getKey(request.getRequestURI(), internalToken); + // 如果缓存中有这个url视为重复提交 + Object result = null; + try { + long leaseTime = noRepeatSubmit.leaseTime(); + //如果获取不到锁等待0秒直接返回 持锁时间为leaseTime + RLock lock = distributedLock.getLock(RedisKeys.getNoRepeatSubmitKey(key), leaseTime, NumConstant.ZERO_L, TimeUnit.MILLISECONDS); + try { + //因为getLock如果获取失败抛异常 所以不做锁状态的判断 + result = pjp.proceed(); + } finally { + distributedLock.unLock(lock); + } + } catch (Exception e) { + log.warn("noRepeatSubmit exception",e); + //"未获取到锁,重复提交了 + throw new RenException(EpmetErrorCode.REPEAT_SUBMIT.getCode()); + } + return result; + } catch (RenException e) { + throw e; + } catch (Throwable e) { + log.error("验证重复提交时出现未知异常!"); + throw new RenException(EpmetErrorCode.SERVER_ERROR.getCode()); + } } - @Before("doAspect()") - public void around(JoinPoint pjp) { - try { - ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); - assert attributes != null; - HttpServletRequest request = attributes.getRequest(); - String key = getKey(request.getRequestURI(), pjp.getArgs()); - // 如果缓存中有这个url视为重复提交 - if (CACHES.getIfPresent(key) == null) { - CACHES.put(key, NumConstant.ZERO); - } else { - log.error("重复提交"); - throw new RenException(EpmetErrorCode.REPEATED_SUBMIT_ERROR.getCode()); - } - } catch (RenException e) { - throw e; - } catch (Throwable e) { - log.error("验证重复提交时出现未知异常!"); - throw new RenException(EpmetErrorCode.SERVER_ERROR.getCode()); - } + private String getKey(String keyExpress,String token) { + return keyExpress+token; } - private String getKey(String keyExpress, Object[] args) { - for (int i = 0; i < args.length; i++) { - keyExpress = keyExpress.replace("arg[" + i + "]", args[i].toString()); - } - return keyExpress; - } - - } diff --git a/epmet-commons/epmet-commons-tools/src/main/java/com/epmet/commons/tools/distributedlock/DistributedLock.java b/epmet-commons/epmet-commons-tools/src/main/java/com/epmet/commons/tools/distributedlock/DistributedLock.java index 85a2e61c84..60e17175f1 100644 --- a/epmet-commons/epmet-commons-tools/src/main/java/com/epmet/commons/tools/distributedlock/DistributedLock.java +++ b/epmet-commons/epmet-commons-tools/src/main/java/com/epmet/commons/tools/distributedlock/DistributedLock.java @@ -1,6 +1,7 @@ package com.epmet.commons.tools.distributedlock; import com.epmet.commons.tools.exception.RenException; +import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.redisson.api.RLock; import org.redisson.api.RedissonClient; @@ -13,6 +14,7 @@ import java.util.concurrent.TimeUnit; * @Author zxc * @DateTime 2020/10/28 9:05 上午 */ +@Slf4j @Component public class DistributedLock { @@ -45,18 +47,31 @@ public class DistributedLock { * @return */ public RLock getLock(String name, Long leaseTime, Long waitTime, TimeUnit timeUnit) { - RLock lock = null; - if (StringUtils.isNotBlank(name) && leaseTime > 0 && waitTime > 0) { - lock = redissonClient.getLock(name); - Boolean lockStatus; - try { - lockStatus = lock.tryLock(waitTime, leaseTime, timeUnit); - if (!lockStatus) { - throw new RenException("获取锁🔒失败了......"); - } - } catch (InterruptedException e) { - e.printStackTrace(); + RLock lock = redissonClient.getLock(name); + Boolean lockStatus; + try { + lockStatus = lock.tryLock(waitTime, leaseTime, timeUnit); + if (!lockStatus) { + throw new RenException("获取锁🔒失败了......"); } + } catch (InterruptedException e) { + log.error("getLock interruptedException"+name,e); + } + + return lock; + } + + /** + * 取锁 + * + * @param name 锁key + * @return + */ + public RLock tryLock(String name) { + RLock lock = redissonClient.getLock(name); + Boolean lockStatus = lock.tryLock(); + if (!lockStatus) { + throw new RenException("获取锁🔒失败了......"); } return lock; } diff --git a/epmet-commons/epmet-commons-tools/src/main/java/com/epmet/commons/tools/exception/EpmetErrorCode.java b/epmet-commons/epmet-commons-tools/src/main/java/com/epmet/commons/tools/exception/EpmetErrorCode.java index 96558433fc..8ba25b63e6 100644 --- a/epmet-commons/epmet-commons-tools/src/main/java/com/epmet/commons/tools/exception/EpmetErrorCode.java +++ b/epmet-commons/epmet-commons-tools/src/main/java/com/epmet/commons/tools/exception/EpmetErrorCode.java @@ -64,6 +64,7 @@ public enum EpmetErrorCode { ACTUAL_TIME(8124,"请录入实际开始时间,实际结束时间"), ACTUAL_NOT_FINISHED(8125,"请先结束活动"), ACT_SIGN_UP_END_TIME_EARLIER_NOW_EERROR(8121,"活动报名截止时间应晚于当前时间"), + REPEAT_SUBMIT(8126,"重复请求,请稍后重试"), CANNOT_AUDIT_WARM(8201, "请完善居民信息"), NOT_DEL_AGENCY(8202, "该机关存在下级机关,不允许删除"), diff --git a/epmet-commons/epmet-commons-tools/src/main/java/com/epmet/commons/tools/redis/RedisKeys.java b/epmet-commons/epmet-commons-tools/src/main/java/com/epmet/commons/tools/redis/RedisKeys.java index 6d5a5f0cfb..046802e321 100644 --- a/epmet-commons/epmet-commons-tools/src/main/java/com/epmet/commons/tools/redis/RedisKeys.java +++ b/epmet-commons/epmet-commons-tools/src/main/java/com/epmet/commons/tools/redis/RedisKeys.java @@ -481,4 +481,13 @@ public class RedisKeys { public static String getProjectChangedMsgDistinceKey(String customerId) { return rootPrefix.concat("project_changed:consume:").concat(customerId); } + + /** + * desc:获取防重复提交redis key + * @param key + * @return + */ + public static String getNoRepeatSubmitKey(String key) { + return rootPrefix.concat("no_repeat_submit:").concat(key); + } } diff --git a/epmet-module/gov-access/gov-access-server/src/main/java/com/epmet/controller/AccessConfigController.java b/epmet-module/gov-access/gov-access-server/src/main/java/com/epmet/controller/AccessConfigController.java index 71ce7c3ec5..2b9d99f615 100644 --- a/epmet-module/gov-access/gov-access-server/src/main/java/com/epmet/controller/AccessConfigController.java +++ b/epmet-module/gov-access/gov-access-server/src/main/java/com/epmet/controller/AccessConfigController.java @@ -1,5 +1,6 @@ package com.epmet.controller; +import com.epmet.commons.tools.aop.NoRepeatSubmit; import com.epmet.commons.tools.utils.Result; import com.epmet.commons.tools.validator.ValidatorUtils; import com.epmet.dto.form.*; @@ -8,11 +9,9 @@ import com.epmet.dto.result.AccessConfigOpesResultDTO; import com.epmet.dto.result.AccessConfigOptionsResultDTO; import com.epmet.dto.result.RoleOperationDefaultResultDTO; import com.epmet.service.AccessConfigService; -import oracle.jdbc.proxy.annotation.Post; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; -import javax.validation.constraints.NotBlank; import java.util.List; import java.util.Set; @@ -49,6 +48,7 @@ public class AccessConfigController { * 保存角色的操作权限列表 * @return */ + @NoRepeatSubmit @PostMapping("saveroleopes") public Result saveRoleOpes(@RequestBody AccessConfigOpesFormDTO formDTO) { accessConfigService.saveRoleOpes(formDTO.getRoleId(), formDTO.getOpes(), formDTO.getCustomerId()); @@ -71,6 +71,7 @@ public class AccessConfigController { * @param config * @return */ + @NoRepeatSubmit @PostMapping("saveconfig") public Result saveSettings(@RequestBody AccessConfigSaveConfigDTO config) { ValidatorUtils.validateEntity(config); @@ -87,6 +88,7 @@ public class AccessConfigController { * @param form * @return */ + @NoRepeatSubmit @PostMapping("roledefaultopes/save") public Result saveRoleDefaultOperations(@RequestBody AccessConfigRoleDefaultOpesFormDTO form) { ValidatorUtils.validateEntity(form); @@ -111,6 +113,7 @@ public class AccessConfigController { * @param form * @return */ + @NoRepeatSubmit @PostMapping("opedefaultscopes/save") public Result saveOpeDefaultScopes(@RequestBody AccessConfigOpeDefaultScopesFormDTO form) { ValidatorUtils.validateEntity(form, AccessConfigOpeDefaultScopesFormDTO.SaveOpeDefaultScopesGroup.class); @@ -124,6 +127,7 @@ public class AccessConfigController { * @author wxz * @date 2020.11.17 17:41 */ + @NoRepeatSubmit @PostMapping("add-ope-and-scopes-4role") public Result addOpeAndScopes4Role(@RequestBody AccessConfigAdd4RoletFormDTO form) { ValidatorUtils.validateEntity(form, AccessConfigAdd4RoletFormDTO.AddSingleOperation4RoleGroup.class); @@ -141,6 +145,7 @@ public class AccessConfigController { * @author wxz * @date 2021.07.01 16:07 */ + @NoRepeatSubmit @PostMapping("add-ope-and-scopes-4role/by-default-conf") public Result addOpeAndScopes4RoleByDefaultConf(@RequestBody AccessConfigAdd4RoletFormDTO form) { ValidatorUtils.validateEntity(form, AccessConfigAdd4RoletFormDTO.AddSingleOperations4RoleByDefault.class); @@ -157,6 +162,7 @@ public class AccessConfigController { * @author wxz * @date 2020.12.02 16:20 */ + @NoRepeatSubmit @PostMapping("add-opes-and-scopes-4role") public Result addOpesAndScopes4Role(@RequestBody AccessConfigAdd4RoletFormDTO form) { ValidatorUtils.validateEntity(form, AccessConfigAdd4RoletFormDTO.AddMultiOperations4RoleGroup.class); diff --git a/epmet-module/gov-access/gov-access-server/src/main/java/com/epmet/controller/TestController.java b/epmet-module/gov-access/gov-access-server/src/main/java/com/epmet/controller/TestController.java index b18beb3aab..6ad185da6b 100644 --- a/epmet-module/gov-access/gov-access-server/src/main/java/com/epmet/controller/TestController.java +++ b/epmet-module/gov-access/gov-access-server/src/main/java/com/epmet/controller/TestController.java @@ -1,11 +1,22 @@ package com.epmet.controller; import com.epmet.commons.tools.annotation.ExternalRequestAuth; -import com.epmet.commons.tools.exception.RenException; +import com.epmet.commons.tools.aop.NoRepeatSubmit; +import com.epmet.commons.tools.utils.HttpClientManager; +import com.epmet.commons.tools.utils.Result; import com.epmet.service.TestService; import lombok.Data; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; @RestController @RequestMapping("test") @@ -14,13 +25,41 @@ public class TestController { @Autowired private TestService testService; + @NoRepeatSubmit @ExternalRequestAuth @PostMapping("test") - public void test(@RequestBody Input input) { - if (true) { - throw new RenException("测试报错"); + public Result test(@RequestBody(required = false) Input input) { + try { + Thread.sleep(5000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + String s = "这里是测试结果!"; + return new Result().ok(s); + } + @PostMapping("repeat") + public Result testRepeatSubmit(){ + System.out.println("开始执行多线程=========="); + CountDownLatch countDownLatch = new CountDownLatch(1); + ExecutorService executorService = Executors.newFixedThreadPool(10); + String url = "http://localhost:8099/gov/access/test/test"; + for (int i = 0; i < 10 ; i++) { + + executorService.submit(()->{ + try { + countDownLatch.await(); + Map headersMap = new HashMap<>(); + headersMap.put("Authorization","token"); + Result stringResult = HttpClientManager.getInstance().sendPostByJSONAndHeader(url, "", headersMap); + System.out.println(stringResult.getData()); + } catch (InterruptedException e) { + e.printStackTrace(); + } + }); + } - testService.test(); + countDownLatch.countDown(); + return new Result().ok(true); } @Data diff --git a/epmet-user/epmet-user-server/src/main/java/com/epmet/controller/UserController.java b/epmet-user/epmet-user-server/src/main/java/com/epmet/controller/UserController.java index a88a812828..d4a4148450 100644 --- a/epmet-user/epmet-user-server/src/main/java/com/epmet/controller/UserController.java +++ b/epmet-user/epmet-user-server/src/main/java/com/epmet/controller/UserController.java @@ -1,6 +1,7 @@ package com.epmet.controller; import com.epmet.commons.tools.annotation.LoginUser; +import com.epmet.commons.tools.aop.NoRepeatSubmit; import com.epmet.commons.tools.security.dto.TokenDto; import com.epmet.commons.tools.utils.Result; import com.epmet.commons.tools.validator.ValidatorUtils; @@ -50,6 +51,7 @@ public class UserController { * @Description * @Date 2020/3/16 15:49 **/ + @NoRepeatSubmit @PostMapping("saveOrUpdateUserWechatDTO") public Result saveOrUpdateUserWechatDTO(@RequestBody UserWechatDTO userWechatDTO) { //效验数据 @@ -77,6 +79,7 @@ public class UserController { * @Author sun * @Description 居民端个人信息-同步用户微信信息 **/ + @NoRepeatSubmit @PostMapping("updatewxuserinfo") public Result updateWxUserInfo(@LoginUser TokenDto tokenDTO, @RequestBody WxUserInfoFormDTO wxUserInfoFormDTO) { wxUserInfoFormDTO.setUserId(tokenDTO.getUserId()); @@ -124,6 +127,7 @@ public class UserController { * @Author sun * @Description 小程序微信用户登陆,新增或更新用户信息 **/ + @NoRepeatSubmit @PostMapping("savewxuser") public Result saveWxUser(@RequestBody WxUserFormDTO formDTO){ return new Result().ok(userService.saveWxUser(formDTO)); @@ -138,6 +142,7 @@ public class UserController { * @Author zhangyong * @Date 14:51 2020-07-23 **/ + @NoRepeatSubmit @PostMapping("updateUserBaseAndWxUserInfo") public Result updateUserBaseAndWxUserInfo(@RequestBody WxUserInfoFormDTO wxUserInfoFormDTO) { ValidatorUtils.validateEntity(wxUserInfoFormDTO); @@ -160,6 +165,7 @@ public class UserController { * @author zxc * @date 2021/1/19 上午10:35 */ + @NoRepeatSubmit @PostMapping("saveuserinfo") public Result saveUserInfo(@RequestBody UserInfoFormDTO formDTO){ ValidatorUtils.validateEntity(formDTO, UserInfoFormDTO.UserInfoForm.class); diff --git a/epmet-user/epmet-user-server/src/main/java/com/epmet/service/impl/UserServiceImpl.java b/epmet-user/epmet-user-server/src/main/java/com/epmet/service/impl/UserServiceImpl.java index b8ee97c440..515f2ccba9 100644 --- a/epmet-user/epmet-user-server/src/main/java/com/epmet/service/impl/UserServiceImpl.java +++ b/epmet-user/epmet-user-server/src/main/java/com/epmet/service/impl/UserServiceImpl.java @@ -182,7 +182,7 @@ public class UserServiceImpl extends BaseServiceImpl implem if(null != myselfMsg){ MyResiUserInfoResultDTO result = ConvertUtils.sourceToTarget(myselfMsg,MyResiUserInfoResultDTO.class); //registerFlag 是否已注册居民,true ,false - result.setRegisterFlag(StringUtils.isNotBlank(myselfMsg.getResiId()) ? true : false); + result.setRegisterFlag(StringUtils.isNotBlank(myselfMsg.getResiId())); result.setRegisterGridName(ModuleConstant.EMPTY_STR); if(StringUtils.isNotBlank(myselfMsg.getGridId())){ //registerGridName 当前在哪个网格,显示哪个网格的名称