REQ-2299 【开发】 创建接口防重复提交 & 校验参数
This commit is contained in:
parent
7570211c34
commit
979b597cef
@ -0,0 +1,37 @@
|
||||
package cn.axzo.tyr.client.common.annotation;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* 重复提交
|
||||
**/
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface RepeatedSubmit {
|
||||
|
||||
/**
|
||||
* 是否加锁,同时只有一人可提交数据.否则每个人同时只能提交一次
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
boolean unique() default false;
|
||||
|
||||
/**
|
||||
* 支持spEl表达式,自定义key,如果存在自定义key,刚使用自定义key
|
||||
* unique则无效
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
String spElKey() default "";
|
||||
|
||||
/**
|
||||
* 重复请求报错信息
|
||||
* 默认: 请勿重复提交
|
||||
*/
|
||||
String msg() default "请勿重复提交";
|
||||
}
|
||||
@ -2,6 +2,7 @@ package cn.axzo.tyr.client.feign;
|
||||
|
||||
import cn.axzo.basics.common.page.PageResult;
|
||||
import cn.axzo.framework.domain.web.result.ApiResult;
|
||||
import cn.axzo.tyr.client.common.annotation.RepeatedSubmit;
|
||||
import cn.axzo.tyr.client.model.req.CreateDataObjectReq;
|
||||
import cn.axzo.tyr.client.model.req.DeleteDataObjectReq;
|
||||
import cn.axzo.tyr.client.model.req.EditDataObjectReq;
|
||||
|
||||
@ -4,6 +4,7 @@ import cn.axzo.basics.auth.enums.WorkspaceTypeWithLegacyEnum;
|
||||
import cn.axzo.basics.common.page.PageResult;
|
||||
import cn.axzo.framework.domain.ServiceException;
|
||||
import cn.axzo.framework.domain.web.result.ApiResult;
|
||||
import cn.axzo.tyr.client.common.annotation.RepeatedSubmit;
|
||||
import cn.axzo.tyr.client.common.enums.AttrPermissionEnum;
|
||||
import cn.axzo.tyr.client.common.enums.EnumTypeEnum;
|
||||
import cn.axzo.tyr.client.common.enums.ReturnCodeEnum;
|
||||
@ -36,7 +37,7 @@ import java.util.stream.Collectors;
|
||||
public class DataObjectController implements DataObjectApi {
|
||||
private final DataObjectService dataObjectService;
|
||||
|
||||
|
||||
@RepeatedSubmit(spElKey = "'createDataObject:' + #req.dataObjectCode")
|
||||
public ApiResult<Long> createDataObject(CreateDataObjectReq req) {
|
||||
return ApiResult.ok(dataObjectService.createDataObject(req));
|
||||
}
|
||||
|
||||
@ -66,10 +66,10 @@ public class DataObjectServiceImpl implements DataObjectService {
|
||||
// 校验
|
||||
// objectName、objectCode不能重复
|
||||
checkObjectNameOrCodeUnique(req.getDataObjectCode(), req.getDataObjectName());
|
||||
// // 对象属性名和code不能重复
|
||||
// checkObjectAttrNameOrCodeUnique(req);
|
||||
// // 规则名 不能重复
|
||||
// checkRuleNameUnique(req);
|
||||
// 对象属性名和code不能重复
|
||||
checkObjectAttrNameOrCodeUnique(req);
|
||||
// 规则名 不能重复
|
||||
checkRuleNameUnique(req);
|
||||
|
||||
// 准备
|
||||
DataObject dataObject = DataObjectMapper.INSTANCE.createReq2DataObject(req);
|
||||
|
||||
@ -0,0 +1,183 @@
|
||||
package cn.axzo.tyr.server.utils;
|
||||
|
||||
import cn.axzo.framework.auth.domain.ContextInfo;
|
||||
import cn.axzo.framework.auth.domain.ContextInfoHolder;
|
||||
import cn.axzo.framework.domain.ServiceException;
|
||||
import cn.axzo.tyr.client.common.annotation.RepeatedSubmit;
|
||||
import cn.hutool.core.text.CharSequenceUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.aspectj.lang.ProceedingJoinPoint;
|
||||
import org.aspectj.lang.annotation.Around;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.reflect.MethodSignature;
|
||||
import org.redisson.api.RLock;
|
||||
import org.redisson.api.RedissonClient;
|
||||
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.context.request.RequestAttributes;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.StringJoiner;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 重复提交切面
|
||||
*
|
||||
* @author yangzhi
|
||||
* @Date 2021/5/20 15:00
|
||||
**/
|
||||
@Aspect
|
||||
@Component
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class RepeatedSubmitAspect {
|
||||
|
||||
private final RedissonClient redissonClient;
|
||||
|
||||
@Around("@annotation(repeatedSubmit)")
|
||||
public Object lock(ProceedingJoinPoint joinPoint, RepeatedSubmit repeatedSubmit)
|
||||
throws Exception {
|
||||
String redisKey;
|
||||
RLock lock = null;
|
||||
try {
|
||||
RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
|
||||
HttpServletRequest httpServletRequest = ((ServletRequestAttributes) attributes).getRequest();
|
||||
String uri = httpServletRequest.getRequestURI();
|
||||
String methodName = httpServletRequest.getMethod();
|
||||
|
||||
String errorMsg = repeatedSubmit.msg();
|
||||
|
||||
StringJoiner stringJoiner = new StringJoiner(":");
|
||||
stringJoiner.add("URI_LIMIT");
|
||||
String spElKey = repeatedSubmit.spElKey();
|
||||
/*
|
||||
* 1.如果指定了key,将使用自定义的key
|
||||
* 2.否则使用uri当作redis的key
|
||||
*/
|
||||
if (CharSequenceUtil.isNotBlank(spElKey)) {
|
||||
getKeyBySpElExpression(stringJoiner, joinPoint, repeatedSubmit);
|
||||
} else {
|
||||
stringJoiner.add(uri).add(methodName);
|
||||
/*
|
||||
* 1.unique为true时,redis不添加用户id
|
||||
* 2.unique为false时,只针对当前用户做过滤
|
||||
*/
|
||||
if (!repeatedSubmit.unique()) {
|
||||
ContextInfo contextInfo = ContextInfoHolder.get();
|
||||
Long userId = null;
|
||||
if (null != contextInfo) {
|
||||
userId = contextInfo.getUserInfo().getPersonId();
|
||||
}
|
||||
if (null == userId) {
|
||||
throw new ServiceException("获取用户信息失败");
|
||||
}
|
||||
stringJoiner.add(String.valueOf(userId));
|
||||
}
|
||||
}
|
||||
redisKey = stringJoiner.toString();
|
||||
/*
|
||||
* redis加锁
|
||||
*/
|
||||
lock = redissonClient.getLock(redisKey);
|
||||
boolean flag = lock.tryLock(1500, TimeUnit.MILLISECONDS);
|
||||
if (!flag) {
|
||||
/*防止释放别人的锁,所以将其置空*/
|
||||
lock = null;
|
||||
log.info("请稍后重试!,redisKey={}" + redisKey);
|
||||
throw new ServiceException(errorMsg);
|
||||
}
|
||||
return joinPoint.proceed();
|
||||
} catch (Throwable throwable) {
|
||||
throw (Exception) throwable;
|
||||
} finally {
|
||||
if (null != lock) {
|
||||
try {
|
||||
//释放锁时,有可能释放别人的锁,所以捕获异常,不抛异常
|
||||
lock.unlock();
|
||||
} catch (Exception e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据spEl获取key值
|
||||
*
|
||||
* @param stringJoiner
|
||||
* @param joinPoint
|
||||
* @param repeatedSubmit
|
||||
*/
|
||||
private void getKeyBySpElExpression(StringJoiner stringJoiner, ProceedingJoinPoint joinPoint
|
||||
, RepeatedSubmit repeatedSubmit) {
|
||||
//读取spEl表达式中的值
|
||||
Object[] args = joinPoint.getArgs();
|
||||
Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
|
||||
String spElValue = getSpElValue(args, method, repeatedSubmit.spElKey());
|
||||
stringJoiner.add(spElValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取spEl值
|
||||
*
|
||||
* @param arguments
|
||||
* @param signatureMethod
|
||||
* @param spelKey
|
||||
* @return 返回spel值
|
||||
*/
|
||||
private String getSpElValue(Object[] arguments, Method signatureMethod, String spelKey) {
|
||||
EvaluationContext context = getContext(arguments, signatureMethod);
|
||||
if (null == context) {
|
||||
return CharSequenceUtil.EMPTY;
|
||||
}
|
||||
String value = getValue(context, spelKey, String.class);
|
||||
if (CharSequenceUtil.isBlank(value)) {
|
||||
return CharSequenceUtil.EMPTY;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取spel 定义的参数值
|
||||
*
|
||||
* @param context 参数容器
|
||||
* @param key key
|
||||
* @param clazz 需要返回的类型
|
||||
* @param <T> 返回泛型
|
||||
* @return 参数值
|
||||
*/
|
||||
private <T> T getValue(EvaluationContext context, String key, Class<T> clazz) {
|
||||
SpelExpressionParser spelExpressionParser = new SpelExpressionParser();
|
||||
Expression expression = spelExpressionParser.parseExpression(key);
|
||||
return expression.getValue(context, clazz);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取参数容器
|
||||
*
|
||||
* @param arguments 方法的参数列表
|
||||
* @param signatureMethod 被执行的方法体
|
||||
* @return 装载参数的容器
|
||||
*/
|
||||
private EvaluationContext getContext(Object[] arguments, Method signatureMethod) {
|
||||
String[] parameterNames = new LocalVariableTableParameterNameDiscoverer()
|
||||
.getParameterNames(signatureMethod);
|
||||
if (parameterNames == null) {
|
||||
return null;
|
||||
}
|
||||
EvaluationContext context = new StandardEvaluationContext();
|
||||
for (int i = 0; i < arguments.length; i++) {
|
||||
context.setVariable(parameterNames[i], arguments[i]);
|
||||
}
|
||||
return context;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user