Browse Source

feat:增加请求频率限制

master
Naccl 4 years ago
parent
commit
879ca68b5a
  1. 15
      README.md
  2. 5
      pom.xml
  3. BIN
      snapshoot2.png
  4. 30
      src/main/java/top/naccl/dwz/annotation/AccessLimit.java
  5. 23
      src/main/java/top/naccl/dwz/config/WebConfig.java
  6. 2
      src/main/java/top/naccl/dwz/controller/IndexController.java
  7. 68
      src/main/java/top/naccl/dwz/interceptor/AccessLimitInterceptor.java
  8. 54
      src/main/java/top/naccl/dwz/util/IpAddressUtils.java
  9. 57
      src/main/java/top/naccl/dwz/util/JacksonUtils.java

15
README.md

@ -27,3 +27,18 @@ base62:MurmurHash 生成的哈希值最长有 10 位十进制数,为了进
Redis:生成短链接后,通常在后续一段时间内此短链接的使用频率较高,则向 Redis 中添加带过期时间的缓存来减轻数据库压力。 Redis:生成短链接后,通常在后续一段时间内此短链接的使用频率较高,则向 Redis 中添加带过期时间的缓存来减轻数据库压力。
302 状态码:301 为永久重定向、302 为临时重定向,通常需要记录短链接访问次数或需要修改、删除短链接时,使用 302 临时重定向来处理,和服务器压力相比,数据的价值往往更大。 302 状态码:301 为永久重定向、302 为临时重定向,通常需要记录短链接访问次数或需要修改、删除短链接时,使用 302 临时重定向来处理,和服务器压力相比,数据的价值往往更大。
## 声明
本在线网站只用于项目展示,随时可能关闭,并不保证绝对的可用性,切勿用于商业用途或非法传播,因此产生的任何纠纷与本人无关。
近期服务器即将到期,正在迁移服务器数据,发现有一些异常的数据:
![](./snapshoot2.png)
我不知道是测试或是什么用途(不止这一类URL),还是很感谢这个频率😋,决定加上请求频率限制。
由于最近迁移服务器,将会丢失部分数据(镜像打的早,DNS 解析 TTL)。

5
pom.xml

@ -62,6 +62,11 @@
<artifactId>spring-boot-starter-test</artifactId> <artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.11</version>
</dependency>
</dependencies> </dependencies>
<build> <build>

BIN
snapshoot2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

30
src/main/java/top/naccl/dwz/annotation/AccessLimit.java

@ -0,0 +1,30 @@
package top.naccl.dwz.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @Description: 访问控制
* @Author: Naccl
* @Date: 2021-09-16
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AccessLimit {
/**
* 限制周期()
*/
int seconds();
/**
* 规定周期内限制次数
*/
int maxCount();
/**
* 触发限制时的消息提示
*/
String msg() default "操作频率过高";
}

23
src/main/java/top/naccl/dwz/config/WebConfig.java

@ -0,0 +1,23 @@
package top.naccl.dwz.config;
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.WebMvcConfigurer;
import top.naccl.dwz.interceptor.AccessLimitInterceptor;
/**
* @Description: 配置CORS跨域支持拦截器
* @Author: Naccl
* @Date: 2020-09-16
*/
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
AccessLimitInterceptor accessLimitInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(accessLimitInterceptor);
}
}

2
src/main/java/top/naccl/dwz/controller/IndexController.java

@ -8,6 +8,7 @@ import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseBody;
import top.naccl.dwz.annotation.AccessLimit;
import top.naccl.dwz.entity.R; import top.naccl.dwz.entity.R;
import top.naccl.dwz.service.UrlService; import top.naccl.dwz.service.UrlService;
import top.naccl.dwz.util.HashUtils; import top.naccl.dwz.util.HashUtils;
@ -34,6 +35,7 @@ public class IndexController {
return "index"; return "index";
} }
@AccessLimit(seconds = 10, maxCount = 1, msg = "10秒内只能生成一次短链接")
@PostMapping("/generate") @PostMapping("/generate")
@ResponseBody @ResponseBody
public R generateShortURL(@RequestParam String longURL) { public R generateShortURL(@RequestParam String longURL) {

68
src/main/java/top/naccl/dwz/interceptor/AccessLimitInterceptor.java

@ -0,0 +1,68 @@
package top.naccl.dwz.interceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import top.naccl.dwz.annotation.AccessLimit;
import top.naccl.dwz.entity.R;
import top.naccl.dwz.util.IpAddressUtils;
import top.naccl.dwz.util.JacksonUtils;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
import java.util.concurrent.TimeUnit;
/**
* @Description: 访问控制拦截器
* @Author: Naccl
* @Date: 2021-09-16
*/
@Component
public class AccessLimitInterceptor implements HandlerInterceptor {
@Autowired
StringRedisTemplate redisTemplate;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
AccessLimit accessLimit = handlerMethod.getMethodAnnotation(AccessLimit.class);
//方法上没有访问控制的注解,直接通过
if (accessLimit == null) {
return true;
}
int seconds = accessLimit.seconds();
int maxCount = accessLimit.maxCount();
String ip = IpAddressUtils.getIpAddress(request);
String method = request.getMethod();
String requestURI = request.getRequestURI();
String redisKey = ip + ":" + method + ":" + requestURI;
Object redisResult = redisTemplate.opsForValue().get(redisKey);
Integer count = JacksonUtils.convertValue(redisResult, Integer.class);
if (count == null) {
//在规定周期内第一次访问,存入redis
redisTemplate.opsForValue().increment(redisKey, 1);
redisTemplate.expire(redisKey, seconds, TimeUnit.SECONDS);
} else {
if (count >= maxCount) {
//超出访问限制次数
response.setContentType("application/json;charset=utf-8");
PrintWriter out = response.getWriter();
R result = R.create(403, accessLimit.msg());
out.write(JacksonUtils.writeValueAsString(result));
out.flush();
out.close();
return false;
} else {
//没超出访问限制次数
redisTemplate.opsForValue().increment(redisKey, 1);
}
}
}
return true;
}
}

54
src/main/java/top/naccl/dwz/util/IpAddressUtils.java

@ -0,0 +1,54 @@
package top.naccl.dwz.util;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.apache.commons.lang3.StringUtils;
import javax.servlet.http.HttpServletRequest;
import java.net.InetAddress;
import java.net.UnknownHostException;
/**
* @Description: ip记录
* @Author: Naccl
* @Date: 2020-08-18
*/
@Slf4j
@Component
public class IpAddressUtils {
/**
* 在Nginx等代理之后获取用户真实IP地址
*
* @param request
* @return
*/
public static String getIpAddress(HttpServletRequest request) {
String ip = request.getHeader("x-forwarded-for");
if (ip == null || ip.length() == 0 || "unknown".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.getHeader("HTTP_CLIENT_IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
if ("127.0.0.1".equals(ip) || "0:0:0:0:0:0:0:1".equals(ip)) {
//根据网卡取本机配置的IP
InetAddress inet = null;
try {
inet = InetAddress.getLocalHost();
} catch (UnknownHostException e) {
log.error("getIpAddress exception:", e);
}
ip = inet.getHostAddress();
}
}
return StringUtils.substringBefore(ip, ",");
}
}

57
src/main/java/top/naccl/dwz/util/JacksonUtils.java

@ -0,0 +1,57 @@
package top.naccl.dwz.util;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import java.io.InputStream;
/**
* @Description: Jackson Object Mapper
* @Author: Naccl
* @Date: 2020-11-07
*/
public class JacksonUtils {
private static ObjectMapper objectMapper = new ObjectMapper();
public static String writeValueAsString(Object value) {
try {
return objectMapper.writeValueAsString(value);
} catch (JsonProcessingException e) {
e.printStackTrace();
return "";
}
}
public static <T> T readValue(String content, Class<T> valueType) {
try {
return objectMapper.readValue(content, valueType);
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
public static <T> T readValue(String content, TypeReference<T> valueTypeRef) {
try {
return objectMapper.readValue(content, valueTypeRef);
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
public static <T> T readValue(InputStream src, Class<T> valueType) {
try {
return objectMapper.readValue(src, valueType);
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
public static <T> T convertValue(Object fromValue, Class<T> toValueType) {
return objectMapper.convertValue(fromValue, toValueType);
}
}
Loading…
Cancel
Save