前言
在日常开发工作中,我们常有接口会暴露出来,虽然我们增加了各种检验和拦截可以拦截大多数恶意访问,但是你不能保证对接方的猿子不会造出一个死循环来访问你的接口,尤其是我们的程序作为一个平台使用的时候,别人的一个误操作可能会造成服务器宕机,到时候成千上万的客户都会受到影响,所以在这种对接过程中一定要对对方的接口访问次数进行限制!这种方式可以理解为微服务中的服务降级!
安排栗子
新建一个注释类:
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface LimitTime { // 访问次数,默认为10次 int time() default 10; // 过期时间,时间戳间隔 long timeout() default 1; }
定义一个存放调用信息的DTO:
@Data public class LimitDTO { //最近一次刷新时间戳 private Long refreshTime; //剩余访问次数 private Integer time; }
新建一个切面类:
注意在存储访问状态对象的时候一定要使用ConcurrentHashMap,此为线程安全的map,支持并发访问!
@Component @Order @Aspect public class LimitTimeAspect { private ConcurrentHashMap<String, LimitDTO> limitMap = new ConcurrentHashMap<>(); @Pointcut("@annotation(limitTime)") public void limit(LimitTime limitTime) { } @Around("limit(limitTime)") public Object aroundLog(ProceedingJoinPoint joinpoint, LimitTime limitTime) throws Throwable { //获取传入的最大访问次数 int time= limitKey.time(); //获取计算时间 long timeout = limitKey.timeout(); //获取访问方法 Object target = joinpoint.getTarget().getClass().getName(); String key= target.toString(); //如果第一次访问该方法 if (limitMap.get(key) == null) { //新建一次对象存放访问信息 LimitDTO limitDTO=new LimitDTO(); limitDTO.setTime(time- 1); limitDTO.setRefreshTime(new Date().getTime()); limitMap.put(key, limitDTO); }else { //如果不是第一次访问,获取上次访问的信息 LimitDTO limitDTO=limitMap.get(key); //如果和上次刷新时间比已经过期 if (new Date().getTime() - limitDTO.getRefreshTime() > timeout) { //将对象中的刷新时间和访问次数刷新 limitDTO.setRefreshTime(new Date().getTime()); limitDTO.setTime(time); limitMap.put(key, limitDTO); } //获取当前访问对象中的剩余访问次数 int t = (int) limitMap.get(key).getTime; //如果访问次数大于0 if (t > 0) { //允许访问,并将访问次数-1 limitDTO.setTime(--t); } else { //如果已经没有访问次数,返回错误信息 ResultBO<Object> resultBO = new ResultBO<>(); resultBO.setCode(1); resultBO.setMsg("已达最大访问次数"); return resultBO; } } //打印信息 System.err.println("剩余次数:" + limitMap.get(key).getTime + " 方法名称:" + key); return joinpoint.proceed(); }
使用该注释:
@RequestMapping(value = "/sendCmd", method = RequestMethod.POST) @ResponseBody @LimitKey(time= 10, timeout = 10000)//10秒内可以访问10次 public int sendCmd(@RequestBody List<CmdDO> cmds) { //do something return new ResultBO<>(); }
测试:
撒花!完成!