com.nxyfan.framework.common.idempotent.RepeatSubmitAspect Maven / Gradle / Ivy
package com.nxyfan.framework.common.idempotent;
import cn.dev33.satoken.SaManager;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import org.apache.commons.lang3.StringUtils;
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 com.alibaba.fastjson.JSON;
import com.nxyfan.framework.common.annotation.CommonRepeatSubmit;
import com.nxyfan.framework.common.cache.CommonCacheOperator;
import com.nxyfan.framework.common.constant.CommonConstant;
import com.nxyfan.framework.common.exception.CommonException;
import com.nxyfan.framework.common.pojo.CommonResult;
import com.nxyfan.framework.common.util.CommonIpAddressUtil;
import com.nxyfan.framework.common.util.CommonServletUtil;
import java.util.Collection;
import java.util.Map;
import java.util.StringJoiner;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
*
* Describe: 防止重复提交(参考美团GTIS防重系统)
* Author: Administrator
* Create Time: 2024年4月27日 下午3:05:23
* Copyright @ 2024 51LIFE
*/
@Aspect
@Component
public class RepeatSubmitAspect {
private static final ThreadLocal KEY_CACHE = new ThreadLocal<>();
@Resource
private CommonCacheOperator commonCacheOperator;
@Before("@annotation(com.nxyfan.framework.common.annotation.CommonRepeatSubmit)")
public void doBefore(JoinPoint point, CommonRepeatSubmit repeatSubmit) throws Throwable {
// 如果注解不为0 则使用注解数值
long interval = repeatSubmit.timeUnit().toMillis(repeatSubmit.interval());
if(interval < 1000) {
throw CommonException.bizException("重复提交间隔时间不能小于'1'秒");
}
// 请求对象
HttpServletRequest request = CommonServletUtil.getRequest();
// 请求ip
String ip = CommonIpAddressUtil.getIp(request);
// 请求参数
String nowParams = argsArrayToString(point.getArgs());
// 请求地址(作为存放cache的key值)
String url = request.getRequestURI();
// 唯一值(没有消息头则使用请求地址)
String tokenName = StringUtils.trimToEmpty(request.getHeader(SaManager.getConfig().getTokenName()));
String submitKey = "";
if(StrUtil.isNotBlank(tokenName)) {
submitKey = tokenName + ":" + ip + ":" + url + ":" + nowParams;
}else {
submitKey = ip + ":" + url + ":" + nowParams;
}
// 唯一标识(指定key + url + 消息头)
String cacheRepeatKey = CommonConstant.REPEAT_SUBMIT_KEY_PREFIX + SecureUtil.md5(submitKey);
// 缓存判断
Object cacheRepeatValue = this.commonCacheOperator.get(cacheRepeatKey);
if(ObjectUtil.isEmpty(cacheRepeatValue)) {
this.commonCacheOperator.put(cacheRepeatKey, submitKey, interval / 1000);
KEY_CACHE.set(cacheRepeatKey);
}else {
String message = "不允许重复提交,请稍候再试!";
Object configMessage = this.commonCacheOperator.get(CommonConstant.REDIS_CONFIG_CACHE_KEY + CommonConstant.CONFIG_KEY_SYS_REPEAT_SUBMIT_MESSAGE);
if(ObjectUtil.isNotEmpty(configMessage)) {
message = Convert.toStr(configMessage);
}
throw CommonException.bizException(message);
}
}
/**
* 处理完请求后执行
*
* @param joinPoint 切点
*/
@AfterReturning(pointcut = "@annotation(com.nxyfan.framework.common.annotation.CommonRepeatSubmit)", returning = "jsonResult")
public void doAfterReturning(JoinPoint joinPoint, CommonRepeatSubmit repeatSubmit, Object jsonResult) {
if(jsonResult instanceof CommonResult>) {
try {
// 成功则不删除redis数据 保证在有效时间内无法重复提交
CommonResult> r = (CommonResult>) jsonResult;
if(r.getRspCode() == 100) {
return;
}
this.commonCacheOperator.remove(KEY_CACHE.get());
}finally {
KEY_CACHE.remove();
}
}
}
/**
* 拦截异常操作
*
* @param joinPoint 切点
* @param e 异常
*/
@AfterThrowing(value = "@annotation(com.nxyfan.framework.common.annotation.CommonRepeatSubmit)", throwing = "e")
public void doAfterThrowing(JoinPoint joinPoint, CommonRepeatSubmit repeatSubmit, Exception e) {
this.commonCacheOperator.remove(KEY_CACHE.get());
KEY_CACHE.remove();
}
/**
* 参数拼装
*/
private String argsArrayToString(Object[] paramsArray) {
StringJoiner params = new StringJoiner(" ");
if(ArrayUtil.isEmpty(paramsArray)) {
return params.toString();
}
for(Object o : paramsArray) {
if(ObjectUtil.isNotNull(o) && !isFilterObject(o)) {
params.add(JSON.toJSONString(o));
}
}
return params.toString();
}
/**
* 判断是否需要过滤的对象。
*
* @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.values()) {
return value instanceof MultipartFile;
}
}
return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse || o instanceof BindingResult;
}
}