/** * Custom annotations to prevent repeated submission of forms * * @author LZJ */ @Inherited @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface RepeatSubmit { /** * Interval time (ms), less than this time is considered a repeated submission */ int interval() default 3000; /** * Interval time unit (milliseconds) */ TimeUnit timeUnit() default TimeUnit.MILLISECONDS; /** * Prompt message */ String message() default "Repeated submissions are not allowed, please try again later"; }
/** * Idempotent function configuration * * @author LZJ */ @Configuration public class IdempotentConfig { @Bean public RepeatSubmitAspect repeatSubmitAspect() { return new RepeatSubmitAspect(); } }
/** * Prevent duplicate submissions * * @author LZJ */ @Aspect @Slf4j public class RepeatSubmitAspect { private static final ThreadLocal<String> KEY_CACHE = new ThreadLocal<>(); @Autowired public RedisTemplate redisTemplate; @Before("@annotation(repeatSubmit)") public void doBefore(JoinPoint point, RepeatSubmit repeatSubmit) throws Throwable { // If the annotation is not 0, use the annotation value long interval = 0; if (repeatSubmit.interval() > 0) { interval = repeatSubmit.timeUnit().toMillis(repeatSubmit.interval()); } if (interval < 1000) { throw new ServiceException("The interval between repeated submissions cannot be less than '1' second"); } HttpServletRequest request = ServletUtils.getRequest(); String nowParams = argsArrayToString(point.getArgs()); //Request address (as the key value for storing cache) String url = request.getRequestURI(); // Unique value (if there is no message header, the request address is used) String submitKey = request.getHeader(RequestHeaderName.TOKEN); submitKey = SecureUtil.md5(submitKey + ":" + nowParams); // Unique identification (specify key + url + message header) String cacheRepeatKey = GlobalConstants.REPEAT_SUBMIT_KEY + url + submitKey; log.info("Repeated submission verification key: {}", cacheRepeatKey); Object key = redisTemplate.opsForValue().get(cacheRepeatKey); if (key == null) { DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); redisTemplate.opsForValue().set(cacheRepeatKey, dateTimeFormatter.format(LocalDateTime.now()), interval, repeatSubmit.timeUnit()); KEY_CACHE.set(cacheRepeatKey); } else { throw new ServiceException(repeatSubmit.message()); } } /** * Executed after processing the request * * @param joinPoint cut point */ @AfterReturning(pointcut = "@annotation(repeatSubmit)", returning = "jsonResult") public void doAfterReturning(JoinPoint joinPoint, RepeatSubmit repeatSubmit, Object jsonResult) { if (jsonResult instanceof R r) { try { // If successful, the redis data will not be deleted to ensure that it cannot be submitted repeatedly within the valid time. if (r.getCode() == R.SUCCESS) { return; } redisTemplate.delete(KEY_CACHE.get()); } finally { KEY_CACHE.remove(); } } } /** * Intercept abnormal operations * * @param joinPoint cut point * @param e exception */ @AfterThrowing(value = "@annotation(repeatSubmit)", throwing = "e") public void doAfterThrowing(JoinPoint joinPoint, RepeatSubmit repeatSubmit, Exception e) { redisTemplate.delete(KEY_CACHE.get()); KEY_CACHE.remove(); } /** * Parameter assembly */ private String argsArrayToString(Object[] paramsArray) { StringBuilder params = new StringBuilder(); if (paramsArray != null & amp; & amp; paramsArray.length > 0) { for (Object o : paramsArray) { if (ObjectUtil.isNotNull(o) & amp; & amp; !isFilterObject(o)) { try { params.append(JsonUtils.toJsonString(o)).append(" "); } catch (Exception e) { e.printStackTrace(); } } } } return params.toString().trim(); } /** * Determine whether filtering is required. * * @param o Object information. * @return If it is an object that needs to be filtered, return true; otherwise, return 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; } }
Global exception handling
@RestControllerAdvice public class GlobalExceptionHandle { private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandle.class); /** * Abnormal permission code */ @ExceptionHandler(NotPermissionException.class) public ResponseBean handleNotPermissionException(NotPermissionException e, HttpServletRequest request) { String requestURI = request.getRequestURI(); log.error("Request address '{}', permission code verification failed '{}'", requestURI, e.getMessage()); return ResponseBean.failed(HttpStatus.FORBIDDEN, "No access rights, please contact the administrator for authorization"); } /** * Abnormal role permissions */ @ExceptionHandler(NotRoleException.class) public ResponseBean handleNotRoleException(NotRoleException e, HttpServletRequest request) { String requestURI = request.getRequestURI(); log.error("Request address '{}', role permission verification failed '{}'", requestURI, e.getMessage()); return ResponseBean.failed(HttpStatus.FORBIDDEN, "No access rights, please contact the administrator for authorization"); } /** * The request method is not supported */ @ExceptionHandler(HttpRequestMethodNotSupportedException.class) public ResponseBean handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException e, HttpServletRequest request) { String requestURI = request.getRequestURI(); log.error("Request address '{}', '{}' request is not supported", requestURI, e.getMethod()); return ResponseBean.failed(e.getMessage()); } /** * Business abnormality */ @ExceptionHandler(ServiceException.class) public ResponseBean handleServiceException(ServiceException e, HttpServletRequest request) { log.error(e.getMessage(), e); Integer code = e.getCode(); return StringUtils.isNotNull(code) ? ResponseBean.failed(code, e.getMessage()) : ResponseBean.failed(e.getMessage()); } /** * Intercept unknown runtime exceptions */ @ExceptionHandler(RuntimeException.class) public ResponseBean handleRuntimeException(RuntimeException e, HttpServletRequest request) { String requestURI = request.getRequestURI(); log.error("Request address '{}', unknown exception occurred.", requestURI, e); return ResponseBean.failed(e.getMessage()); } /** * System exception */ @ExceptionHandler(Exception.class) public ResponseBean handleException(Exception e, HttpServletRequest request) { String requestURI = request.getRequestURI(); log.error("Request address '{}', system exception occurred.", requestURI, e); return ResponseBean.failed(e.getMessage()); } /** * Custom validation exception */ @ExceptionHandler(BindException.class) public ResponseBean handleBindException(BindException e) { log.error(e.getMessage(), e); String message = e.getAllErrors().get(0).getDefaultMessage(); return ResponseBean.failed(message); } /** * Custom validation exception ConstraintViolationException */ @ExceptionHandler(MethodArgumentNotValidException.class) public Object handleMethodArgumentNotValidException(MethodArgumentNotValidException e) { log.error(e.getMessage(), e); String message = e.getBindingResult().getFieldError().getDefaultMessage(); return ResponseBean.failed(message); } @ExceptionHandler(ConstraintViolationException.class) public Object handleMethodConstraintViolationException(ConstraintViolationException e) { log.error(e.getMessage(), e); String message = e.getMessage(); return ResponseBean.failed(message); } /** * Internal authentication exception */ @ExceptionHandler(InnerAuthException.class) public ResponseBean handleInnerAuthException(InnerAuthException e) { return ResponseBean.failed(e.getMessage()); } }
/** * Global exception handling */ @ControllerAdvice(annotations = {RestController.class, Controller.class}) @ResponseBody @Slf4j public class GlobalExceptionHandler extends GlobalExceptionHandle { }