马府强
2 years ago
11 changed files with 436 additions and 0 deletions
@ -0,0 +1,33 @@ |
|||||
|
HELP.md |
||||
|
target/ |
||||
|
!.mvn/wrapper/maven-wrapper.jar |
||||
|
!**/src/main/**/target/ |
||||
|
!**/src/test/**/target/ |
||||
|
|
||||
|
### STS ### |
||||
|
.apt_generated |
||||
|
.classpath |
||||
|
.factorypath |
||||
|
.project |
||||
|
.settings |
||||
|
.springBeans |
||||
|
.sts4-cache |
||||
|
|
||||
|
### IntelliJ IDEA ### |
||||
|
.idea |
||||
|
*.iws |
||||
|
*.iml |
||||
|
*.ipr |
||||
|
|
||||
|
### NetBeans ### |
||||
|
/nbproject/private/ |
||||
|
/nbbuild/ |
||||
|
/dist/ |
||||
|
/nbdist/ |
||||
|
/.nb-gradle/ |
||||
|
build/ |
||||
|
!**/src/main/**/build/ |
||||
|
!**/src/test/**/build/ |
||||
|
|
||||
|
### VS Code ### |
||||
|
.vscode/ |
@ -0,0 +1,11 @@ |
|||||
|
# demo-request-scope |
||||
|
|
||||
|
《基于ThreadLocal实现一个上下文管理组件》技术博客对应源码 |
||||
|
|
||||
|
https://juejin.cn/post/7153287656624324638 |
||||
|
|
||||
|
本文基于`ThreadLocal`原理,实现了一个上下文状态管理组件`Scope`,通过开启一个自定义的`Scope`,在`Scope`范围内,可以通过`Scope`各个方法读写数据; |
||||
|
|
||||
|
通过自定义线程池实现上下文状态数据的线程间传递; |
||||
|
|
||||
|
提出了一种基于`Filter`和`Scope`的`Request`粒度的上下文管理方案。 |
@ -0,0 +1,61 @@ |
|||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> |
||||
|
<modelVersion>4.0.0</modelVersion> |
||||
|
<parent> |
||||
|
<groupId>org.springframework.boot</groupId> |
||||
|
<artifactId>spring-boot-starter-parent</artifactId> |
||||
|
<version>2.7.4</version> |
||||
|
<relativePath/> <!-- lookup parent from repository --> |
||||
|
</parent> |
||||
|
<groupId>com.example</groupId> |
||||
|
<artifactId>demo-request-scope</artifactId> |
||||
|
<version>0.0.1-SNAPSHOT</version> |
||||
|
<name>demo-request-scope</name> |
||||
|
<description>Demo project for Spring Boot</description> |
||||
|
<properties> |
||||
|
<java.version>8</java.version> |
||||
|
</properties> |
||||
|
|
||||
|
|
||||
|
<dependencies> |
||||
|
|
||||
|
<dependency> |
||||
|
<groupId>com.google.guava</groupId> |
||||
|
<artifactId>guava</artifactId> |
||||
|
<version>30.1-jre</version> |
||||
|
</dependency> |
||||
|
|
||||
|
<dependency> |
||||
|
<groupId>org.springframework.boot</groupId> |
||||
|
<artifactId>spring-boot-starter-aop</artifactId> |
||||
|
</dependency> |
||||
|
|
||||
|
<dependency> |
||||
|
<groupId>org.springframework.boot</groupId> |
||||
|
<artifactId>spring-boot-starter-web</artifactId> |
||||
|
</dependency> |
||||
|
|
||||
|
<dependency> |
||||
|
<groupId>org.springframework.boot</groupId> |
||||
|
<artifactId>spring-boot-starter-test</artifactId> |
||||
|
<scope>test</scope> |
||||
|
</dependency> |
||||
|
|
||||
|
<dependency> |
||||
|
<groupId>org.projectlombok</groupId> |
||||
|
<artifactId>lombok</artifactId> |
||||
|
<optional>true</optional> |
||||
|
</dependency> |
||||
|
</dependencies> |
||||
|
|
||||
|
<build> |
||||
|
<plugins> |
||||
|
<plugin> |
||||
|
<groupId>org.springframework.boot</groupId> |
||||
|
<artifactId>spring-boot-maven-plugin</artifactId> |
||||
|
</plugin> |
||||
|
</plugins> |
||||
|
</build> |
||||
|
|
||||
|
</project> |
@ -0,0 +1,13 @@ |
|||||
|
package com.example.demo; |
||||
|
|
||||
|
import org.springframework.boot.SpringApplication; |
||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication; |
||||
|
|
||||
|
@SpringBootApplication |
||||
|
public class DemoRequestScopeApplication { |
||||
|
|
||||
|
public static void main(String[] args) { |
||||
|
SpringApplication.run(DemoRequestScopeApplication.class, args); |
||||
|
} |
||||
|
|
||||
|
} |
@ -0,0 +1,73 @@ |
|||||
|
package com.example.demo.aop; |
||||
|
|
||||
|
|
||||
|
import com.example.demo.limit.Limit; |
||||
|
import com.example.demo.util.WebUtils; |
||||
|
import com.example.demo.vo.ResultData; |
||||
|
import com.example.demo.vo.ReturnCode; |
||||
|
import com.google.common.collect.Maps; |
||||
|
import com.google.common.util.concurrent.RateLimiter; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.aspectj.lang.ProceedingJoinPoint; |
||||
|
import org.aspectj.lang.annotation.Around; |
||||
|
import org.aspectj.lang.annotation.Aspect; |
||||
|
import org.aspectj.lang.reflect.MethodSignature; |
||||
|
import org.springframework.stereotype.Component; |
||||
|
import org.springframework.web.context.request.RequestContextHolder; |
||||
|
import org.springframework.web.context.request.ServletRequestAttributes; |
||||
|
|
||||
|
import javax.servlet.http.HttpServletResponse; |
||||
|
import java.lang.reflect.Method; |
||||
|
import java.util.Map; |
||||
|
|
||||
|
@Slf4j |
||||
|
@Aspect |
||||
|
@Component |
||||
|
public class LimitAop { |
||||
|
/** |
||||
|
* 不同的接口,不同的流量控制 |
||||
|
* map的key为 Limiter.key |
||||
|
*/ |
||||
|
private final Map<String, RateLimiter> limitMap = Maps.newConcurrentMap(); |
||||
|
|
||||
|
@Around("@annotation(com.example.demo.limit.Limit)") |
||||
|
public Object around(ProceedingJoinPoint joinPoint) throws Throwable { |
||||
|
MethodSignature signature = (MethodSignature) joinPoint.getSignature(); |
||||
|
Method method = signature.getMethod(); |
||||
|
//拿limit的注解
|
||||
|
Limit limit = method.getAnnotation(Limit.class); |
||||
|
if (limit != null) { |
||||
|
//key作用:不同的接口,不同的流量控制
|
||||
|
String key = limit.key(); |
||||
|
RateLimiter rateLimiter = null; |
||||
|
//验证缓存是否有命中key
|
||||
|
if (!limitMap.containsKey(key)) { |
||||
|
// 创建令牌桶
|
||||
|
rateLimiter = RateLimiter.create(limit.permitsPerSecond()); |
||||
|
limitMap.put(key, rateLimiter); |
||||
|
log.info("新建了令牌桶={},容量={}", key, limit.permitsPerSecond()); |
||||
|
} |
||||
|
rateLimiter = limitMap.get(key); |
||||
|
// 拿令牌
|
||||
|
boolean acquire = rateLimiter.tryAcquire(limit.timeout(), limit.timeunit()); |
||||
|
// 拿不到命令,直接返回异常提示
|
||||
|
if (!acquire) { |
||||
|
log.debug("令牌桶={},获取令牌失败", key); |
||||
|
this.responseFail(limit.msg()); |
||||
|
return null; |
||||
|
} |
||||
|
} |
||||
|
return joinPoint.proceed(); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 直接向前端抛出异常 |
||||
|
* |
||||
|
* @param msg 提示信息 |
||||
|
*/ |
||||
|
private void responseFail(String msg) { |
||||
|
HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse(); |
||||
|
ResultData<Object> resultData = ResultData.fail(ReturnCode.LIMIT_ERROR.getCode(), msg); |
||||
|
WebUtils.writeJson(response, resultData); |
||||
|
} |
||||
|
} |
@ -0,0 +1,27 @@ |
|||||
|
package com.example.demo.controller; |
||||
|
|
||||
|
import com.example.demo.limit.Limit; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.springframework.web.bind.annotation.GetMapping; |
||||
|
import org.springframework.web.bind.annotation.RequestMapping; |
||||
|
import org.springframework.web.bind.annotation.RestController; |
||||
|
import java.util.concurrent.TimeUnit; |
||||
|
|
||||
|
/** |
||||
|
* SpringBoot 如何进行限流? 单机可以这么玩 |
||||
|
* Guava限流工具类RateLimiter |
||||
|
*/ |
||||
|
@Slf4j |
||||
|
@RestController |
||||
|
@RequestMapping("/limit") |
||||
|
public class LimitController { |
||||
|
|
||||
|
|
||||
|
@GetMapping("/test") |
||||
|
@Limit(key = "limit3", permitsPerSecond = 1, timeout = 50, timeunit = TimeUnit.MILLISECONDS,msg = "系统繁忙,请稍后再试!") |
||||
|
public String limit3() { |
||||
|
log.info("令牌桶limit3获取令牌成功"); |
||||
|
return "ok"; |
||||
|
} |
||||
|
|
||||
|
} |
@ -0,0 +1,35 @@ |
|||||
|
package com.example.demo.limit; |
||||
|
|
||||
|
import java.lang.annotation.*; |
||||
|
import java.util.concurrent.TimeUnit; |
||||
|
|
||||
|
@Retention(RetentionPolicy.RUNTIME) |
||||
|
@Target({ElementType.METHOD}) |
||||
|
@Documented |
||||
|
public @interface Limit { |
||||
|
/** |
||||
|
* 资源的key,唯一 |
||||
|
* 作用:不同的接口,不同的流量控制 |
||||
|
*/ |
||||
|
String key() default ""; |
||||
|
|
||||
|
/** |
||||
|
* 最多的访问限制次数 |
||||
|
*/ |
||||
|
double permitsPerSecond(); |
||||
|
|
||||
|
/** |
||||
|
* 获取令牌最大等待时间 |
||||
|
*/ |
||||
|
long timeout(); |
||||
|
|
||||
|
/** |
||||
|
* 获取令牌最大等待时间,单位(例:分钟/秒/毫秒) 默认:毫秒 |
||||
|
*/ |
||||
|
TimeUnit timeunit() default TimeUnit.MILLISECONDS; |
||||
|
|
||||
|
/** |
||||
|
* 得不到令牌的提示语 |
||||
|
*/ |
||||
|
String msg() default "系统繁忙,请稍后再试."; |
||||
|
} |
@ -0,0 +1,54 @@ |
|||||
|
package com.example.demo.util; |
||||
|
|
||||
|
|
||||
|
import com.fasterxml.jackson.databind.ObjectMapper; |
||||
|
import lombok.SneakyThrows; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.springframework.http.MediaType; |
||||
|
|
||||
|
import javax.servlet.http.HttpServletResponse; |
||||
|
import java.io.IOException; |
||||
|
|
||||
|
/** |
||||
|
* <p> |
||||
|
* <code>WebUtils</code> |
||||
|
* </p> |
||||
|
* Description: |
||||
|
* WEB工具类 |
||||
|
* |
||||
|
* @author woniu |
||||
|
* @date 2023/4/12 10:56 |
||||
|
*/ |
||||
|
@Slf4j |
||||
|
public class WebUtils { |
||||
|
private static ObjectMapper objectMapper = new ObjectMapper(); |
||||
|
|
||||
|
/** |
||||
|
* 向客户端输出JSON字符串 |
||||
|
* |
||||
|
* @param response HttpServletResponse |
||||
|
* @param object 输出的数据 |
||||
|
*/ |
||||
|
@SneakyThrows |
||||
|
public static void writeJson(HttpServletResponse response, Object object) { |
||||
|
writeData(response, objectMapper.writeValueAsString(object), MediaType.APPLICATION_JSON_VALUE); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 客户端返回字符串 |
||||
|
* |
||||
|
* @param response HttpServletResponse |
||||
|
* @param data 需要返回的数据 |
||||
|
*/ |
||||
|
public static void writeData(HttpServletResponse response, String data, String type) { |
||||
|
try { |
||||
|
response.setContentType(type); |
||||
|
response.setCharacterEncoding("utf-8"); |
||||
|
response.getWriter().print(data); |
||||
|
response.getWriter().flush(); |
||||
|
response.getWriter().close(); |
||||
|
} catch (IOException e) { |
||||
|
log.error("writeData error", e); |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,68 @@ |
|||||
|
package com.example.demo.vo; |
||||
|
|
||||
|
import lombok.Data; |
||||
|
|
||||
|
@Data |
||||
|
public class ResultData<T> { |
||||
|
/** |
||||
|
* 结果状态 ,具体状态码参见ResultData.java |
||||
|
*/ |
||||
|
private int status; |
||||
|
/** |
||||
|
* 响应消息 |
||||
|
**/ |
||||
|
private String message; |
||||
|
/** |
||||
|
* 响应数据 |
||||
|
**/ |
||||
|
private T data; |
||||
|
/** |
||||
|
* 接口请求时间 |
||||
|
**/ |
||||
|
private long timestamp; |
||||
|
|
||||
|
|
||||
|
public ResultData() { |
||||
|
this.timestamp = System.currentTimeMillis(); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
public static <T> ResultData<T> success(String message) { |
||||
|
ResultData<T> resultData = new ResultData<>(); |
||||
|
resultData.setStatus(ReturnCode.RC100.getCode()); |
||||
|
resultData.setMessage(message); |
||||
|
return resultData; |
||||
|
} |
||||
|
|
||||
|
public static <T> ResultData<T> success() { |
||||
|
ResultData<T> resultData = new ResultData<>(); |
||||
|
resultData.setStatus(ReturnCode.RC100.getCode()); |
||||
|
resultData.setMessage(ReturnCode.RC100.getMessage()); |
||||
|
return resultData; |
||||
|
} |
||||
|
|
||||
|
public static <T> ResultData<T> success(T data) { |
||||
|
ResultData<T> resultData = new ResultData<>(); |
||||
|
resultData.setStatus(ReturnCode.RC100.getCode()); |
||||
|
resultData.setMessage(ReturnCode.RC100.getMessage()); |
||||
|
resultData.setData(data); |
||||
|
return resultData; |
||||
|
} |
||||
|
|
||||
|
public static <T> ResultData<T> fail(String message) { |
||||
|
ResultData<T> resultData = new ResultData<>(); |
||||
|
resultData.setStatus(ReturnCode.RC999.getCode()); |
||||
|
resultData.setMessage(message); |
||||
|
return resultData; |
||||
|
} |
||||
|
|
||||
|
public static <T> ResultData<T> fail(int code, String message) { |
||||
|
ResultData<T> resultData = new ResultData<>(); |
||||
|
resultData.setStatus(code); |
||||
|
resultData.setMessage(message); |
||||
|
return resultData; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
|
||||
|
} |
@ -0,0 +1,60 @@ |
|||||
|
package com.example.demo.vo; |
||||
|
|
||||
|
/** |
||||
|
* 状态码集合 |
||||
|
* |
||||
|
* @author woniu |
||||
|
* @date 2023/4/12 14:33 |
||||
|
*/ |
||||
|
public enum ReturnCode { |
||||
|
/** |
||||
|
* 操作成功 |
||||
|
**/ |
||||
|
RC100(100, "操作成功"), |
||||
|
/** |
||||
|
* 操作失败 |
||||
|
**/ |
||||
|
RC999(999, "操作失败"), |
||||
|
|
||||
|
/** |
||||
|
* 服务限流 |
||||
|
**/ |
||||
|
LIMIT_ERROR(2001, "系统繁忙,请稍后再试!"), |
||||
|
|
||||
|
/** |
||||
|
* 服务异常 |
||||
|
**/ |
||||
|
RC500(500, "系统异常,请稍后重试"), |
||||
|
|
||||
|
ILLEGAL_ARGUMENT(3001, "非法参数"), |
||||
|
|
||||
|
ILLEGAL_HEADER(4001, "非法请求头,请添加合适的签名"), |
||||
|
|
||||
|
REPLAY_ERROR(4002, "访问已过期,请重新访问!"), |
||||
|
|
||||
|
ARGUMENT_ERROR(4003, "您正在尝试恶意访问,已记录IP备案!"); |
||||
|
|
||||
|
|
||||
|
/** |
||||
|
* 自定义状态码 |
||||
|
**/ |
||||
|
private final int code; |
||||
|
/** |
||||
|
* 自定义描述 |
||||
|
**/ |
||||
|
private final String message; |
||||
|
|
||||
|
ReturnCode(int code, String message) { |
||||
|
this.code = code; |
||||
|
this.message = message; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
public int getCode() { |
||||
|
return code; |
||||
|
} |
||||
|
|
||||
|
public String getMessage() { |
||||
|
return message; |
||||
|
} |
||||
|
} |
@ -0,0 +1 @@ |
|||||
|
|
Loading…
Reference in new issue