1.在进入页面的时候提交修改时间,然后进行提交的时候,将修改时间带入到后台,然后查询数据库,当两个时间相同的时候,进行操作,否则就提示已操作.
2.看到一种更广泛的方式,就是在进入页面的时候生成一个token,然后赋值到session中,同时带入到页面中,然后提交过程中,将页面带入的token与session中的token比较,相同则通过校验并删除session。
3、使用redis分布式锁,这边写了一个通用方法,只需要标注下CustomerLock就可以了
@RequestMapping("test")
@CustomerLock(key="#id",lockTimeOut = 3000)
public void test(@Param("id") @RequiredParam Long id) {
}
@RequestMapping("test")
@CustomerLock(key="#testVo.id",lockTimeOut = 3000)
public void test(TestVo testVo) {
}
首先定义一个CustomerLock注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CustomerLock {
String key() ;
long lockTimeOut() default 9000 ;
String errorMessage() default "请勿重复提交";
}
然后定义一个RedisLock
@Component
@Slf4j
public class RedisLock {
@Autowired
private RedisTemplate<String, String> redisTemplate;
/**
* 加锁
* @param key
* @param value 当前时间+超时时间
* @return
*/
public boolean lock(String key, String value) {
if(redisTemplate.opsForValue().setIfAbsent(key, value)) {
return true;
}
//currentValue=A 这两个线程的value都是B 其中一个线程拿到锁
String currentValue = redisTemplate.opsForValue().get(key);
//如果锁过期
if (!StringUtils.isEmpty(currentValue)
&& Long.parseLong(currentValue) < System.currentTimeMillis()) {
//获取上一个锁的时间
String oldValue = redisTemplate.opsForValue().getAndSet(key, value);
if (!StringUtils.isEmpty(oldValue) && oldValue.equals(currentValue)) {
return true;
}
}
return false;
}
/**
* 解锁
* @param key
* @param value
*/
public void unlock(String key, String value) {
try {
String currentValue = redisTemplate.opsForValue().get(key);
if (!StringUtils.isEmpty(currentValue) && currentValue.equals(value)) {
redisTemplate.opsForValue().getOperations().delete(key);
}
}catch (Exception e) {
log.error("【redis分布式锁】解锁异常, {}", e);
}
}
}
如果是springmvc项目则可以使用如下aop接入
@Aspect
@Component
public class CustomerLockAspect {
@Autowired
private RedisLock lock;
private ExpressionEvaluator <String> evaluator = new ExpressionEvaluator<>();
@Around("@annotation(customerLock)")
public Object invokeWithRedisLock(ProceedingJoinPoint pjp, CustomerLock customerLock) throws Throwable {
String methodName = getMethodName(pjp);
String key = getAttachmentId(pjp,customerLock);
String lockName ="reception_boss:"+methodName +":"+key;
long expires = System.currentTimeMillis() + customerLock.lockTimeOut();
try {
Boolean flag = lock.lock(lockName,String.valueOf(expires));
if(flag){
return pjp.proceed();
}else{
return WebResult.fail("500",customerLock.errorMessage());
}
} finally {
lock.unlock(lockName,String.valueOf(expires));
}
}
private String getMethodName(ProceedingJoinPoint pjp) {
String methodName= pjp.getTarget().getClass().getName()+":"+((MethodSignature) pjp.getSignature()).getMethod().getName();
return methodName;
}
private String getAttachmentId(JoinPoint joinPoint, CustomerLock customerLock) {
if (joinPoint.getArgs() == null) {
return null;
}
EvaluationContext evaluationContext = evaluator.createEvaluationContext(joinPoint.getTarget(), joinPoint.getTarget().getClass(), ((MethodSignature) joinPoint.getSignature()).getMethod(), joinPoint.getArgs());
AnnotatedElementKey methodKey = new AnnotatedElementKey(((MethodSignature) joinPoint.getSignature()).getMethod(), joinPoint.getTarget().getClass());
return evaluator.condition(customerLock.key(), methodKey, evaluationContext, String.class);
}
}
public class ExpressionEvaluator<T> extends CachedExpressionEvaluator {
private final ParameterNameDiscoverer discoverer = new DefaultParameterNameDiscoverer();
private final Map<AnnotatedElementKey, Method> methodCache = new ConcurrentHashMap<>(128);
private final Map<ExpressionKey, Expression> conditionCache = new ConcurrentHashMap<>(128);
public T condition(String conditionExpression, AnnotatedElementKey elementKey, EvaluationContext evalContext, Class<T> clazz) {
return getExpression(this.conditionCache, elementKey, conditionExpression).getValue(evalContext, clazz);
}
public EvaluationContext createEvaluationContext(Object object, Class<?> targetClass, Method method, Object[] args) {
Method targetMethod = getTargetMethod(targetClass, method);
RootObject root = new RootObject(object, args);
return new MethodBasedEvaluationContext(root, targetMethod, args, this.discoverer);
}
private Method getTargetMethod(Class<?> targetClass, Method method) {
AnnotatedElementKey methodKey = new AnnotatedElementKey(method, targetClass);
Method targetMethod = this.methodCache.get(methodKey);
if (targetMethod == null) {
targetMethod = AopUtils.getMostSpecificMethod(method, targetClass);
if (targetMethod == null) {
targetMethod = method;
}
this.methodCache.put(methodKey, targetMethod);
}
return targetMethod;
}
class RootObject {
private final Object object;
private final Object[] args;
public RootObject(Object object, Object[] args) {
this.object = object;
this.args = args;
}
public Object getobject() {
return object;
}
public Object[] getArgs() {
return args;
}
}
}