REQ-2299 【开发】 创建接口防重复提交 & 校验参数

This commit is contained in:
yangheng 2024-04-25 14:06:06 +08:00
parent 7570211c34
commit 979b597cef
5 changed files with 227 additions and 5 deletions

View File

@ -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 "请勿重复提交";
}

View File

@ -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;

View File

@ -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));
}

View File

@ -66,10 +66,10 @@ public class DataObjectServiceImpl implements DataObjectService {
// 校验
// objectNameobjectCode不能重复
checkObjectNameOrCodeUnique(req.getDataObjectCode(), req.getDataObjectName());
// // 对象属性名和code不能重复
// checkObjectAttrNameOrCodeUnique(req);
// // 规则名 不能重复
// checkRuleNameUnique(req);
// 对象属性名和code不能重复
checkObjectAttrNameOrCodeUnique(req);
// 规则名 不能重复
checkRuleNameUnique(req);
// 准备
DataObject dataObject = DataObjectMapper.INSTANCE.createReq2DataObject(req);

View File

@ -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;
}
}