/**
* 自定义注解防止表单重复提交
*
* @author LZJ
*/
@Inherited
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RepeatSubmit {
/**
* 间隔时间(ms),小于此时间视为重复提交
*/
int interval() default 3000;
/**
* 间隔时间单位(毫秒)
*/
TimeUnit timeUnit() default TimeUnit.MILLISECONDS;
/**
* 提示消息
*/
String message() default "不允许重复提交,请稍候再试";
}
/**
* 幂等功能配置
*
* @author LZJ
*/
@Configuration
public class IdempotentConfig {
@Bean
public RepeatSubmitAspect repeatSubmitAspect() {
return new RepeatSubmitAspect();
}
}
/**
* 防止重复提交
*
* @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 {
// 如果注解不为0 则使用注解数值
long interval = 0;
if (repeatSubmit.interval() > 0) {
interval = repeatSubmit.timeUnit().toMillis(repeatSubmit.interval());
}
if (interval < 1000) {
throw new ServiceException("重复提交间隔时间不能小于'1'秒");
}
HttpServletRequest request = ServletUtils.getRequest();
String nowParams = argsArrayToString(point.getArgs());
// 请求地址(作为存放cache的key值)
String url = request.getRequestURI();
// 唯一值(没有消息头则使用请求地址)
String submitKey = request.getHeader(RequestHeaderName.TOKEN);
submitKey = SecureUtil.md5(submitKey + ":" + nowParams);
// 唯一标识(指定key + url + 消息头)
String cacheRepeatKey = GlobalConstants.REPEAT_SUBMIT_KEY + url + submitKey;
log.info("重复提交验证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());
}
}
/**
* 处理完请求后执行
*
* @param joinPoint 切点
*/
@AfterReturning(pointcut = "@annotation(repeatSubmit)", returning = "jsonResult")
public void doAfterReturning(JoinPoint joinPoint, RepeatSubmit repeatSubmit, Object jsonResult) {
if (jsonResult instanceof R r) {
try {
// 成功则不删除redis数据 保证在有效时间内无法重复提交
if (r.getCode() == R.SUCCESS) {
return;
}
redisTemplate.delete(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) {
redisTemplate.delete(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;
}
}
全局异常处理
@RestControllerAdvice
public class GlobalExceptionHandle {
private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandle.class);
/**
* 权限码异常
*/
@ExceptionHandler(NotPermissionException.class)
public ResponseBean handleNotPermissionException(NotPermissionException e, HttpServletRequest request)
{
String requestURI = request.getRequestURI();
log.error("请求地址'{}',权限码校验失败'{}'", requestURI, e.getMessage());
return ResponseBean.failed(HttpStatus.FORBIDDEN, "没有访问权限,请联系管理员授权");
}
/**
* 角色权限异常
*/
@ExceptionHandler(NotRoleException.class)
public ResponseBean handleNotRoleException(NotRoleException e, HttpServletRequest request)
{
String requestURI = request.getRequestURI();
log.error("请求地址'{}',角色权限校验失败'{}'", requestURI, e.getMessage());
return ResponseBean.failed(HttpStatus.FORBIDDEN, "没有访问权限,请联系管理员授权");
}
/**
* 请求方式不支持
*/
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
public ResponseBean handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException e,
HttpServletRequest request)
{
String requestURI = request.getRequestURI();
log.error("请求地址'{}',不支持'{}'请求", requestURI, e.getMethod());
return ResponseBean.failed(e.getMessage());
}
/**
* 业务异常
*/
@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());
}
/**
* 拦截未知的运行时异常
*/
@ExceptionHandler(RuntimeException.class)
public ResponseBean handleRuntimeException(RuntimeException e, HttpServletRequest request)
{
String requestURI = request.getRequestURI();
log.error("请求地址'{}',发生未知异常.", requestURI, e);
return ResponseBean.failed(e.getMessage());
}
/**
* 系统异常
*/
@ExceptionHandler(Exception.class)
public ResponseBean handleException(Exception e, HttpServletRequest request)
{
String requestURI = request.getRequestURI();
log.error("请求地址'{}',发生系统异常.", requestURI, e);
return ResponseBean.failed(e.getMessage());
}
/**
* 自定义验证异常
*/
@ExceptionHandler(BindException.class)
public ResponseBean handleBindException(BindException e)
{
log.error(e.getMessage(), e);
String message = e.getAllErrors().get(0).getDefaultMessage();
return ResponseBean.failed(message);
}
/**
* 自定义验证异常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);
}
/**
* 内部认证异常
*/
@ExceptionHandler(InnerAuthException.class)
public ResponseBean handleInnerAuthException(InnerAuthException e)
{
return ResponseBean.failed(e.getMessage());
}
}
/**
* 全局异常处理
*/
@ControllerAdvice(annotations = {RestController.class, Controller.class})
@ResponseBody
@Slf4j
public class GlobalExceptionHandler extends GlobalExceptionHandle {
}