SpringBoot + RedisでAPIへアクセス回数の制限機能を実装してみよう

### はじめに
   いまのところはAPIインターフェースを使う場合はめっちゃ多いと思います。ところが、APIを呼び出すの回数は多すぎたら、サーバー側の処理は遅延になるかもしれません。何か悪い影響が起こるということです。
　今回はAPIへアクセス回数を制限するために、よく調べたり、深く考えたりしたうえでこの記事を書いていました。

### 環境
  SpringBootの知識もRedisの知識も持っている必要です。

- Java
- SpringBoot
- Redis

### 方法
実装方針としては、主にSpringBootとRedisを用いてアノテイションの形で実装する。
細かいところはコメントアウトを見れば、分かると思います。

#### 1. アノテイションクラスを作成 

```java:AccessLimit.java
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

/**
 * @author Hyman
 * @date 2019/12/25 11:12
 */
@Retention(RUNTIME)
@Target(METHOD)
public @interface AccessLimit {

    // 制限時間(秒単位)
    int seconds();
    //　最大アクセス回数
    int maxCount();
    // ログイン状況
    boolean needLogin() default true;
}
```

#### 2. インターセプタークラスを作成 
```java:ApiLimitInterceptor.java
import com.alibaba.fastjson.JSON;
import com.example.demo.action.AccessLimit;
import com.example.demo.redis.RedisService;
import com.example.demo.result.CodeMsg;
import com.example.demo.result.Result;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.OutputStream;

/**
 * @author Hyman
 * @date 2019/12/25 11:22
 */
@Component
public class ApiLimitInterceptor extends HandlerInterceptorAdapter {

    @Autowired
    private RedisService redisService;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        //リクエストはHandlerMethodかどうかを判明する
        if(handler instanceof HandlerMethod){

            HandlerMethod hm = (HandlerMethod) handler;

            //　メソッドの上のアノテイションを取得し、AccessLimitアノテーションがあるかどうかが分かるよう
            AccessLimit accessLimit = hm.getMethodAnnotation(AccessLimit.class);
            if(accessLimit == null){
　　　　　　　　　// AccessLimitアノテーションがなければ、何もしない
                return true;
            }
　　　　　　 
            //  制限時間を取得する
            int seconds = accessLimit.seconds();
            //  最大アクセス回数を取得する
            int maxCount = accessLimit.maxCount();
            boolean login = accessLimit.needLogin();
            String key = request.getRequestURI();

            //　ログインが必須であれば、以下の処理を行う
            if(login){
                // ここでは認証の部分を省く
                // key+=""+"userInfo";
            }

            // redisからアクセス回数を取得する
            AccessKey ak = AccessKey.withExpire(seconds);
            Integer count = redisService.get(ak,key,Integer.class);

　　　　　　 // 分岐を作ってそれぞれの処理を行う
            if(count == null){
                //初めてアクセスする場合
                redisService.set(ak,key,1);
            }else if(count < maxCount){
                // アクセス回数＋1
                redisService.incr(ak,key);
            }else{
                //最大アクセス回数を超えたら、エラーメッセージを作って戻ります。
                render(response,"指定の時間以内、最大アクセス回数を超えます。"); //
                return false;
            }
        }

        return true;

    }
    
 /**
  * 最大アクセス回数を超えたら、エラーメッセージを作って戻ります。
  */
  private void render(HttpServletResponse response, CodeMsg cm)throws Exception {
        response.setContentType("application/json;charset=UTF-8");
        OutputStream out = response.getOutputStream();
        String str  = JSON.toJSONString(Result.error(cm));
        out.write(str.getBytes("UTF-8"));
        out.flush();
        out.close();
    }
}
```
#### 3. インターセプタークラスを配置
上記２で作ったインターセプタークラスをSpringBootに登録する。

```java:WebConfig.java
import com.example.demo.ExceptionHander.ApiLimitInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

/**
 * @author Hyman
 * @date 2019/11/31 15:58
 */
@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {

    @Autowired
    private ApiLimitInterceptor interceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(interceptor);
    }
}
```

#### 4. コントローラーを作成
コントローラーを作って、メソッドの上に@AccessLimitを付いてます。
それが終わったら、Postmanでテストを行いましょう。

```java:ApiLimitController.java
import com.example.demo.result.Result;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

/**
 * @author Hyman
 * @date 2019/12/25 13:21
 */
@Controller
public class ApiLimitController {

    // 時間：5秒 ,最大アクセス回数:5 ,ログイン状況：必須
    @AccessLimit(seconds=5, maxCount=5, needLogin=true)
    @RequestMapping("/ApiLimitTest")
    @ResponseBody
    public Result<String> ApiLimitTest(){
        return Result.success("リクエスト成功!");
    }
 }
```

### 最後に
最後まで読んでいただき、ありがとうございます。
おかしいと思う部分は遠慮なくご指摘いただければと思います。 
よろしくお願いします。