自定义注解(Annotation)+redis组合,防止接口的重复请求

喜夏-厌秋 提交于 2020-03-06 03:43:05

自定义注解+redis组合的使用达到接口访问限制的用法

一,前言

本文介绍如何使用自定义注解(Annotation)+redis来解决接口请求限制的方法

二,自定义注解(Annotation)

随着springboot的流行,以前基于XML的spring配置用的越来越少,JavaConfig形式的使用越来越多,类似于:

@Configuration
public class AppConfig {    
    @Bean(name="helloBean")
    public HelloWorld helloWorld() {
        return new HelloWorldImpl();
    }   
}

可以看出更多的是基于注解(Annotation)实现的,包括springboot的入口类**Application。Java注解不仅让我们减少了项目中XML文件,方便了维护,同时也使我们代码更简洁。那么项目中我们如何阅读注解以及如何创造自己的注解呢? 

三,注解(Annotation)说明

Java注解又称Java标注,是Java语言5.0版本开始支持加入源代码的特殊语法元数据。为我们在代码中添加信息提供了一种形式化的方法,使我们可以在稍后某个时刻非常方便的使用这些数据。
Java语言中的类、方法、变量、参数和包等都可以被标注。和Javadoc不同,Java标注可以通过反射获取注解内容。在编译器生成类文件时,注解可以被嵌入到字节码中。Java虚拟机可以保留注解内容,在运行时可以获取到注解内容。

四,正式步入主题

1,自定义CacheLock注解。属性:redis 锁key的前缀;过期时间;超时时间单位;生成Key的分隔符(默认 :)

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface CacheLock {
    /**
     * redis 锁key的前缀
     *
     * @return redis 锁key的前缀
     */
    String prefix() default "";

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

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

    /**
     * <p>Key的分隔符(默认 :)</p>
     * @return String
     */
    String delimiter() default ":";
}

2,自定义CacheParam注解。属性:字段名称name,获取的是你某个接口的请求参数的值

@Target({ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface CacheParam {
    /**
     * 字段名称
     *
     * @return String
     */
    String name() default "";
}

 3,创建一个类标识为一个切面供容器读取

@Around注解可以用来在调用一个具体方法前和调用后来完成一些具体的任务。
@Log4j2
@Aspect
@Configuration
public class LockMethodAspect {

    @Autowired
    private  RedisLockHelperUtil redisLockHelper;
    @Autowired
    private CacheKeyGenerator cacheKeyGenerator;

    @Around("execution(public * *(..)) && @annotation(com.common.annotation.CacheLock)")
    public Object interceptor(ProceedingJoinPoint pjp) {
        MethodSignature signature = (MethodSignature) pjp.getSignature();
        Method method = signature.getMethod();
        CacheLock lock = method.getAnnotation(CacheLock.class);
        if (StringUtils.isEmpty(lock.prefix())) {
            throw new RuntimeException("lock key don't null...");
        }
        final String lockKey = cacheKeyGenerator.getLockKey(pjp);
        String value = UUID.randomUUID().toString();
        try {
            // 假设上锁成功,但是设置过期时间失效,以后拿到的都是 false
            final boolean success = redisLockHelper.lock(lockKey, value, lock.expire(), lock.timeUnit());
            if (!success) {
                try{
                    throw new MyException("操作异常");
                }catch (MyException e){
                    log.error(e);
                    Map<String, Object> result = new HashMap<String, Object>();
                    result.put(ResponseJsonKeyEnum.CODE.key(), ResponseCodeEnum.STORAGE_FAILURE.code());
                    result.put(ResponseJsonKeyEnum.MSG.key(), "系统数据处理中...,请稍后");
                    return GsonUtil.GSON.toJson(result);
                }
            }
            try {
                return pjp.proceed();
            } catch (Throwable throwable) {
                throw new RuntimeException("系统异常");
            }
        } finally {
            redisLockHelper.unlock(lockKey, value);
        }
    }
}

4,RedisLockHelperUtil  工具类

@Configuration
@AutoConfigureAfter(RedisAutoConfiguration.class)
public class RedisLockHelperUtil {


    private static final String DELIMITER = "-";

    /**
     * 如果要求比较高可以通过注入的方式分配
     */
    private static final ScheduledExecutorService EXECUTOR_SERVICE = Executors.newScheduledThreadPool(10);

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

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

    /**
     * 获取锁
     *
     * @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) {
        TimeUnit timeUnit = TimeUnit.MILLISECONDS;
        final long milliseconds = timeUnit.convert(timeout, TimeUnit.SECONDS);
        boolean success = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, (System.currentTimeMillis() + milliseconds) + DELIMITER + uuid);
        if (success) {
            stringRedisTemplate.expire(lockKey, timeout, TimeUnit.SECONDS);
        } else {
            String oldVal = stringRedisTemplate.opsForValue().getAndSet(lockKey, (System.currentTimeMillis() + milliseconds) + DELIMITER + uuid);
            final String[] oldValues = oldVal.split(Pattern.quote(DELIMITER));
            if (Long.parseLong(oldValues[0]) + 1 <= System.currentTimeMillis()) {
                return true;
            }
        }
        return success;
    }


    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);
        if (!StringUtils.isEmpty(val)){
            final String[] values = val.split(Pattern.quote(DELIMITER));
            if (values.length <= 0) {
                return;
            }
            if (uuid.equals(values[1])) {
                stringRedisTemplate.delete(lockKey);
            }
        }
    }
}

5,CacheKeyGenerator 工具类,获取AOP参数,生成指定缓存Key

import org.aspectj.lang.ProceedingJoinPoint;

/**
 * key生成器
 */
public interface CacheKeyGenerator {
    /**
     * 获取AOP参数,生成指定缓存Key
     *
     * @param pjp PJP
     * @return 缓存KEY
     */
    String getLockKey(ProceedingJoinPoint pjp);
}

6,应用

    @CacheLock(prefix = "bbb")
    @RequestMapping(value = "/aaa", method = RequestMethod.POST)
    public String bbb(@Valid CccRequest cccRequest, BindingResult paramValid) throws Exception {
        String error = RequestParamValid.valid(paramValid);
        if (StringUtils.isNotEmpty(error)) {
            return error;
        }
        Map<String, Object> result = new HashMap<>();
        try {
           ...
        } catch (Exception e) {
            log.error(e);
            result.put(ResponseJsonKeyEnum.CODE.key(), ResponseCodeEnum.FAILURE.code());
            result.put(ResponseJsonKeyEnum.MSG.key(), ResponseCodeEnum.FAILURE.msg());
        }

        return GsonUtil.GSON.toJson(result);
    }
@Setter
@Getter
public class CccRequest {
    @CacheParam(name = "id")
    @NotNull(message = "订单id不能为空")
    private Long id;
    //备注
    private String remark;
}

五,完毕

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!