马府强
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