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.basics.common.page.PageResult;
|
||||||
import cn.axzo.framework.domain.web.result.ApiResult;
|
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.CreateDataObjectReq;
|
||||||
import cn.axzo.tyr.client.model.req.DeleteDataObjectReq;
|
import cn.axzo.tyr.client.model.req.DeleteDataObjectReq;
|
||||||
import cn.axzo.tyr.client.model.req.EditDataObjectReq;
|
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.basics.common.page.PageResult;
|
||||||
import cn.axzo.framework.domain.ServiceException;
|
import cn.axzo.framework.domain.ServiceException;
|
||||||
import cn.axzo.framework.domain.web.result.ApiResult;
|
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.AttrPermissionEnum;
|
||||||
import cn.axzo.tyr.client.common.enums.EnumTypeEnum;
|
import cn.axzo.tyr.client.common.enums.EnumTypeEnum;
|
||||||
import cn.axzo.tyr.client.common.enums.ReturnCodeEnum;
|
import cn.axzo.tyr.client.common.enums.ReturnCodeEnum;
|
||||||
@ -36,7 +37,7 @@ import java.util.stream.Collectors;
|
|||||||
public class DataObjectController implements DataObjectApi {
|
public class DataObjectController implements DataObjectApi {
|
||||||
private final DataObjectService dataObjectService;
|
private final DataObjectService dataObjectService;
|
||||||
|
|
||||||
|
@RepeatedSubmit(spElKey = "'createDataObject:' + #req.dataObjectCode")
|
||||||
public ApiResult<Long> createDataObject(CreateDataObjectReq req) {
|
public ApiResult<Long> createDataObject(CreateDataObjectReq req) {
|
||||||
return ApiResult.ok(dataObjectService.createDataObject(req));
|
return ApiResult.ok(dataObjectService.createDataObject(req));
|
||||||
}
|
}
|
||||||
|
|||||||
@ -66,10 +66,10 @@ public class DataObjectServiceImpl implements DataObjectService {
|
|||||||
// 校验
|
// 校验
|
||||||
// objectName、objectCode不能重复
|
// objectName、objectCode不能重复
|
||||||
checkObjectNameOrCodeUnique(req.getDataObjectCode(), req.getDataObjectName());
|
checkObjectNameOrCodeUnique(req.getDataObjectCode(), req.getDataObjectName());
|
||||||
// // 对象属性名和code不能重复
|
// 对象属性名和code不能重复
|
||||||
// checkObjectAttrNameOrCodeUnique(req);
|
checkObjectAttrNameOrCodeUnique(req);
|
||||||
// // 规则名 不能重复
|
// 规则名 不能重复
|
||||||
// checkRuleNameUnique(req);
|
checkRuleNameUnique(req);
|
||||||
|
|
||||||
// 准备
|
// 准备
|
||||||
DataObject dataObject = DataObjectMapper.INSTANCE.createReq2DataObject(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