java springboot 重复提交,笔记

基于注解实现,也可以把注解去掉,直接aop全局使用

1.RepeatAnnotation.java

package com.repeat.annotation;

import java.lang.annotation.*;
import java.util.concurrent.TimeUnit;

/**
 * @author 
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface RepeatAnnotation{

    /**
     * redis 锁key的前缀
     *
     * @return redis 锁key的前缀
     */
    String prefix() default "";

    /**
     * 过期秒数,默认为10秒
     *
     * @return 轮询锁的时间
     */
    int expire() default 10;

    /**
     * 超时时间单位
     *
     * @return 秒
     */
    TimeUnit timeUnit() default TimeUnit.SECONDS;

    /**
     * 

Key的分隔符(默认 :)

*

生成的Key:N:SO1008:500

* * @return String */ String delimiter() default "_"; }

2.RepeatInterceptor.java

类中注释掉的代码替换对应功能的代码后几位全局请求进行拦截

package com.battcn.interceptor;

import com.battcn.annotation.CacheLock;
import com.battcn.utils.RedisLockHelper;
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.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.StringUtils;

import java.lang.reflect.Method;
import java.util.UUID;

/**
 * 重复提交实现
 *
 */
@Aspect
@Component
public class RepeatInterceptor{

    @Autowired
    public RepeatInterceptor(RedisLockHelper redisLockHelper) {
        this.redisLockHelper = redisLockHelper;
    }

    private final RedisLockHelper redisLockHelper;

	@Around("execution(public * *(..)) && @annotation(com.repeat.annotation.RepeatAnnotation)")
//    @Around("execution(public * com.battcn.controller.*.*(..))")
    public Object interceptor(ProceedingJoinPoint pjp) {
        MethodSignature signature = (MethodSignature) pjp.getSignature();
        Method method = signature.getMethod();
        RepeatAnnotation lock = method.getAnnotation(RepeatAnnotation.class);
        if (StringUtils.isEmpty(lock.prefix())) {
            throw new RuntimeException("lock key don't null...");
        }
        // 此处需要获取当前登录用户的有效token值
        String token = "45afwea556614511";

        final String lockKey = lock.prefix() + lock.delimiter() + token;
//        final String lockKey = "test_" + token;
        System.out.println("lockKeylockKeylockKey>>>"+lockKey);
        String value = UUID.randomUUID().toString();
        try {
            // 假设上锁成功,但是设置过期时间失效,以后拿到的都是 false
            final boolean success = redisLockHelper.lock(lockKey, value, lock.expire(), lock.timeUnit());
//        	final boolean success = redisLockHelper.lock(lockKey, value, 10, TimeUnit.SECONDS);
            if (!success) {// 返回false时,说明此时缓存仍在有效期内,直接返回重复提交
            	return "重复提交";
            }
            // 返回true时
            try {
                return pjp.proceed();
            } catch (Throwable throwable) {
                throw new RuntimeException("系统异常");
            }
        } finally {
            // 如果测试需要注释该代码;实际运行时应该放开,删除对应缓存
//            redisLockHelper.unlock(lockKey, value);
        }
    }
}

3.RedisLockHelper.java

package com.repeat.utils;

import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisStringCommands;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.types.Expiration;
import org.springframework.util.StringUtils;

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;

/**
 * 需要定义成 Bean
 */
@Configuration
@AutoConfigureAfter(RedisAutoConfiguration.class)
public class RedisLockHelper {


    private static final String DELIMITER = "|";

    /**
     * 如果要求比较高可以通过注入的方式分配
     */
    // 线程,任务调度,保证并发线程安全
    private static final ScheduledExecutorService EXECUTOR_SERVICE = Executors.newScheduledThreadPool(100);
    // StringRedisTemplate继承RedisTemplate,两者数据不共通,单独管理,如果存取的数据类型确定为字符串就用这个
    private final StringRedisTemplate stringRedisTemplate;

    public RedisLockHelper(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
    }

    /**
     * 获取锁(存在死锁风险)
     *
     * @param lockKey lockKey
     * @param value   value
     * @param time    超时时间
     * @param unit    过期单位
     * @return true or false
     */
    public boolean tryLock(final String lockKey, final String value, final long time, final TimeUnit unit) {
        return stringRedisTemplate.execute((RedisCallback) connection -> connection.set(lockKey.getBytes(), value.getBytes(), Expiration.from(time, unit), RedisStringCommands.SetOption.SET_IF_ABSENT));
    }

    /**
     * 获取锁
     *
     * @param lockKey lockKey
     * @param uuid    UUID
     * @param timeout 超时时间
     * @param unit    过期单位
     * @return true or false
     */
    public boolean lock(String lockKey, final String uuid, long timeout, final TimeUnit unit) {
    	// 获取过期毫秒数
        final long milliseconds = Expiration.from(timeout, unit).getExpirationTimeInMilliseconds();
        // 设置缓存 如果键不存在则新增,存在则不改变已经有的值。因为不删除所以设置会在第一次设置后变为false
        boolean success = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, (System.currentTimeMillis() + milliseconds) + DELIMITER + uuid);
        System.out.println("缓存设置::::"+success);
        if (success) {// 如果缓存设置成功,则给其设置过期时间
            stringRedisTemplate.expire(lockKey, timeout, TimeUnit.SECONDS);//设置过期时间
        } else {
        	//获取原来key键对应的值并重新赋新值。
            String oldVal = stringRedisTemplate.opsForValue().getAndSet(lockKey, (System.currentTimeMillis() + milliseconds) + DELIMITER + uuid);
            // 拆分旧值
            final String[] oldValues = oldVal.split(Pattern.quote(DELIMITER));
            // 如果旧值加+1 小于等于 当前系统时间 返回true 否则返回添加缓存状态false
            if (Long.parseLong(oldValues[0]) + 1 <= System.currentTimeMillis()) {
            	System.out.println("旧值小于 当前系统时间-1");
                return true;
            }
        }
        return success;
    }


    /**
     * @see Redis Documentation: SET
     */
    public void unlock(String lockKey, String value) {
        unlock(lockKey, value, 0, TimeUnit.MILLISECONDS);
    }

    /**
     * 延迟unlock
     *
     * @param lockKey   key
     * @param uuid      client(最好是唯一键的)
     * @param delayTime 延迟时间
     * @param unit      时间单位
     */
    public void unlock(final String lockKey, final String uuid, long delayTime, TimeUnit unit) {
        if (StringUtils.isEmpty(lockKey)) {
            return;
        }
        if (delayTime <= 0) {
            doUnlock(lockKey, uuid);
        } else {
            EXECUTOR_SERVICE.schedule(() -> doUnlock(lockKey, uuid), delayTime, unit);
        }
    }

    /**
     * @param lockKey key
     * @param uuid    client(最好是唯一键的)
     */
    private void doUnlock(final String lockKey, final String uuid) {
        String val = stringRedisTemplate.opsForValue().get(lockKey);
        final String[] values = val.split(Pattern.quote(DELIMITER));
        if (values.length <= 0) {
            return;
        }
        if (uuid.equals(values[1])) {
            stringRedisTemplate.delete(lockKey);
        }
    }

}

4.TestController.java

package com.repeat.controller;

import com.battcn.annotation.CacheLock;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;


@RestController
@RequestMapping("/test")
public class TestController {


    @CacheLock(prefix = "test")
    @GetMapping
    public String query() {
        return "提交成功";
    }

}

5.pom.xml 所需依赖



    4.0.0
    
        org.springframework.boot
        spring-boot-starter-parent
        2.0.2.RELEASE
        
    
    com.battcn
    chapter22
    0.0.1-SNAPSHOT
    jar


    
        UTF-8
        UTF-8
        1.8
    


    
        
            org.springframework.boot
            spring-boot-starter-web
        
        
            org.springframework.boot
            spring-boot-starter-aop
        
        
            org.springframework.boot
            spring-boot-starter-data-redis
        
        
			io.springfox
			springfox-swagger2
			2.7.0
		
		
			io.springfox
			springfox-swagger-ui
			2.7.0
		
		
    

    
        
            
                org.springframework.boot
                spring-boot-maven-plugin
            
        
    


 

 

 

 

 

 

 

 

 

你可能感兴趣的:(java springboot 重复提交,笔记)