思路分析
- 想要实现用户对接口访问的限制,就要考虑实现功能的两个维度:
- 固定时间、
- 固定访问次数
- redis设置key过期时间的方法–>expire key seconds
- redis设置键的数字值递增的方法–>incr key
那么我们可以用请求用户的ip这一唯一标识作为redis的key,通过increment方法实现访问次数的记录
代码实现
@Retention(RUNTIME)
@Target(METHOD)
public @interface AccessLimit {
int seconds();
int maxCount();
}
- 拦截器(MyInterceptor.java)
控制访问接口频率的业务逻辑主要靠拦截器实现
@Slf4j
public class MyInterceptor implements HandlerInterceptor {
@Resource
RedisService redisService;
@Override
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object handler) throws Exception {
System.out.println("request=====>"+httpServletRequest);
System.out.println("response=====>"+httpServletResponse);
System.out.println("handler=====>"+handler);
if (handler instanceof HandlerMethod) {
HandlerMethod hm = (HandlerMethod) handler;
AccessLimit accessLimit = hm.getMethodAnnotation(AccessLimit.class);
if (accessLimit != null) {
long seconds = accessLimit.seconds();
int maxCount = accessLimit.maxCount();
String key = SystemUtil.getClientIp(httpServletRequest) + hm.getMethod().getName();
try {
long q = redisService.incr(key, seconds);
if (q > maxCount) {
render(httpServletResponse, new ResponseMsg(0, "请求过于频繁,请稍候再试", null));
return false;
}
return true;
} catch (RedisConnectionFailureException e) {
log.info("redis错误" + e.getMessage().toString());
return true;
}
}
}
return false;
}
private void render(HttpServletResponse response, ResponseMsg cm) throws Exception {
response.setContentType("application/json;charset=UTF-8");
OutputStream out = response.getOutputStream();
String str = new Gson().toJson(cm);
out.write(str.getBytes("UTF-8"));
out.flush();
out.close();
}
}
- 拦截器注册(MyWebConfig.java)
当然,有了拦截器还得注册进web中,否则无法生效
@SpringBootConfiguration
public class MyWebConfig implements WebMvcConfigurer {
@Bean
public MyInterceptor getMyInterceptor(){
return new MyInterceptor();
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(getMyInterceptor()).addPathPatterns("/**");
}
}
- 操作redis的服务层(RedisServiceImpl.java)
public interface RedisService {
boolean set(String key, String value);
String get(String key);
boolean expire(String key, long expire);
boolean remove(String key);
Long incr(String key,long time);
}
@Service
public class RedisServiceImpl implements RedisService{
@Resource
private RedisTemplate<String, ?> redisTemplate;
@Override
public boolean set(final String key, final String value) {
boolean result = redisTemplate.execute(new RedisCallback<Boolean>() {
@Override
public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
RedisSerializer<String> serializer = redisTemplate.getStringSerializer();
connection.set(serializer.serialize(key), serializer.serialize(value));
return true;
}
});
return result;
}
@Override
public String get(final String key) {
String result = redisTemplate.execute(new RedisCallback<String>() {
@Override
public String doInRedis(RedisConnection connection) throws DataAccessException {
RedisSerializer<String> serializer = redisTemplate.getStringSerializer();
byte[] value = connection.get(serializer.serialize(key));
return serializer.deserialize(value);
}
});
return result;
}
@Override
public boolean expire(final String key, long expire) {
return redisTemplate.expire(key, expire, TimeUnit.SECONDS);
}
@Override
public boolean remove(final String key) {
boolean result = redisTemplate.execute(new RedisCallback<Boolean>() {
@Override
public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
RedisSerializer<String> serializer = redisTemplate.getStringSerializer();
connection.del(key.getBytes());
return true;
}
});
return result;
}
@Override
public Long incr(String key,long time){
redisTemplate.setKeySerializer(new StringRedisSerializer());
long count = redisTemplate.opsForValue().increment(key, 1);
if (count == 1) {
set(key,"1");
redisTemplate.expire(key, time, TimeUnit.SECONDS);
}
return count;
}
}
@RestController
@RequestMapping
public class LimitTestController {
@RequestMapping("testlimit")
@AccessLimit(seconds = 6,maxCount = 2)
public String testLimit(){
System.out.println("进入方法");
return "测试方法";
}
}
public class SystemUtil {
public static String getClientIp(HttpServletRequest request) {
String ip = request.getHeader("x-forwarded-for");
if (ip == null || ip.length() == 0 || "unknow".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
if (ip.equals("127.0.0.1")) {
InetAddress inet = null;
try {
inet = InetAddress.getLocalHost();
} catch (Exception e) {
e.printStackTrace();
}
ip = inet.getHostAddress();
}
}
if (ip != null && ip.length() > 15) {
if (ip.indexOf(",") > 0) {
ip = ip.substring(0, ip.indexOf(","));
}
}
return ip;
}
}
访问测试接口
- 访问
http://localhost:8080/testlimit
正常返回
. ![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/67db1f39fc15b1f82e3bb92d53601ce7.png)
- 访问频率超过接口定义
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/86d1aea1adffe04d05c5d3ea406c4a6a.png)