From a3a09d2aeb03f5654015caed0c9aa665e0cdeedd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=A9=AC=E5=BA=9C=E5=BC=BA?= <8569561+fuqiangma@user.noreply.gitee.com> Date: Wed, 12 Apr 2023 22:45:41 +0800 Subject: [PATCH] =?UTF-8?q?guava=E9=99=90=E6=B5=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- guavalimit/.gitignore | 33 +++++++++ guavalimit/README.md | 11 +++ guavalimit/pom.xml | 61 ++++++++++++++++ .../demo/DemoRequestScopeApplication.java | 13 ++++ .../java/com/example/demo/aop/LimitAop.java | 73 +++++++++++++++++++ .../demo/controller/LimitController.java | 27 +++++++ .../java/com/example/demo/limit/Limit.java | 35 +++++++++ .../java/com/example/demo/util/WebUtils.java | 54 ++++++++++++++ .../java/com/example/demo/vo/ResultData.java | 68 +++++++++++++++++ .../java/com/example/demo/vo/ReturnCode.java | 60 +++++++++++++++ .../src/main/resources/application.properties | 1 + 11 files changed, 436 insertions(+) create mode 100644 guavalimit/.gitignore create mode 100644 guavalimit/README.md create mode 100644 guavalimit/pom.xml create mode 100644 guavalimit/src/main/java/com/example/demo/DemoRequestScopeApplication.java create mode 100644 guavalimit/src/main/java/com/example/demo/aop/LimitAop.java create mode 100644 guavalimit/src/main/java/com/example/demo/controller/LimitController.java create mode 100644 guavalimit/src/main/java/com/example/demo/limit/Limit.java create mode 100644 guavalimit/src/main/java/com/example/demo/util/WebUtils.java create mode 100644 guavalimit/src/main/java/com/example/demo/vo/ResultData.java create mode 100644 guavalimit/src/main/java/com/example/demo/vo/ReturnCode.java create mode 100644 guavalimit/src/main/resources/application.properties diff --git a/guavalimit/.gitignore b/guavalimit/.gitignore new file mode 100644 index 0000000..549e00a --- /dev/null +++ b/guavalimit/.gitignore @@ -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/ diff --git a/guavalimit/README.md b/guavalimit/README.md new file mode 100644 index 0000000..c912aec --- /dev/null +++ b/guavalimit/README.md @@ -0,0 +1,11 @@ +# demo-request-scope + +《基于ThreadLocal实现一个上下文管理组件》技术博客对应源码 + +https://juejin.cn/post/7153287656624324638 + +本文基于`ThreadLocal`原理,实现了一个上下文状态管理组件`Scope`,通过开启一个自定义的`Scope`,在`Scope`范围内,可以通过`Scope`各个方法读写数据; + +通过自定义线程池实现上下文状态数据的线程间传递; + +提出了一种基于`Filter`和`Scope`的`Request`粒度的上下文管理方案。 diff --git a/guavalimit/pom.xml b/guavalimit/pom.xml new file mode 100644 index 0000000..65b54a8 --- /dev/null +++ b/guavalimit/pom.xml @@ -0,0 +1,61 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.7.4 + + + com.example + demo-request-scope + 0.0.1-SNAPSHOT + demo-request-scope + Demo project for Spring Boot + + 8 + + + + + + + com.google.guava + guava + 30.1-jre + + + + org.springframework.boot + spring-boot-starter-aop + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-test + test + + + + org.projectlombok + lombok + true + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/guavalimit/src/main/java/com/example/demo/DemoRequestScopeApplication.java b/guavalimit/src/main/java/com/example/demo/DemoRequestScopeApplication.java new file mode 100644 index 0000000..4c0d58d --- /dev/null +++ b/guavalimit/src/main/java/com/example/demo/DemoRequestScopeApplication.java @@ -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); + } + +} diff --git a/guavalimit/src/main/java/com/example/demo/aop/LimitAop.java b/guavalimit/src/main/java/com/example/demo/aop/LimitAop.java new file mode 100644 index 0000000..16cd939 --- /dev/null +++ b/guavalimit/src/main/java/com/example/demo/aop/LimitAop.java @@ -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 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 resultData = ResultData.fail(ReturnCode.LIMIT_ERROR.getCode(), msg); + WebUtils.writeJson(response, resultData); + } +} \ No newline at end of file diff --git a/guavalimit/src/main/java/com/example/demo/controller/LimitController.java b/guavalimit/src/main/java/com/example/demo/controller/LimitController.java new file mode 100644 index 0000000..5e2ab47 --- /dev/null +++ b/guavalimit/src/main/java/com/example/demo/controller/LimitController.java @@ -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"; + } + +} \ No newline at end of file diff --git a/guavalimit/src/main/java/com/example/demo/limit/Limit.java b/guavalimit/src/main/java/com/example/demo/limit/Limit.java new file mode 100644 index 0000000..8d3a0ad --- /dev/null +++ b/guavalimit/src/main/java/com/example/demo/limit/Limit.java @@ -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 "系统繁忙,请稍后再试."; +} \ No newline at end of file diff --git a/guavalimit/src/main/java/com/example/demo/util/WebUtils.java b/guavalimit/src/main/java/com/example/demo/util/WebUtils.java new file mode 100644 index 0000000..3676d4c --- /dev/null +++ b/guavalimit/src/main/java/com/example/demo/util/WebUtils.java @@ -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; + +/** + *

+ * WebUtils + *

+ * 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); + } + } +} diff --git a/guavalimit/src/main/java/com/example/demo/vo/ResultData.java b/guavalimit/src/main/java/com/example/demo/vo/ResultData.java new file mode 100644 index 0000000..a5ee6f9 --- /dev/null +++ b/guavalimit/src/main/java/com/example/demo/vo/ResultData.java @@ -0,0 +1,68 @@ +package com.example.demo.vo; + +import lombok.Data; + +@Data +public class ResultData { + /** + * 结果状态 ,具体状态码参见ResultData.java + */ + private int status; + /** + * 响应消息 + **/ + private String message; + /** + * 响应数据 + **/ + private T data; + /** + * 接口请求时间 + **/ + private long timestamp; + + + public ResultData() { + this.timestamp = System.currentTimeMillis(); + } + + + public static ResultData success(String message) { + ResultData resultData = new ResultData<>(); + resultData.setStatus(ReturnCode.RC100.getCode()); + resultData.setMessage(message); + return resultData; + } + + public static ResultData success() { + ResultData resultData = new ResultData<>(); + resultData.setStatus(ReturnCode.RC100.getCode()); + resultData.setMessage(ReturnCode.RC100.getMessage()); + return resultData; + } + + public static ResultData success(T data) { + ResultData resultData = new ResultData<>(); + resultData.setStatus(ReturnCode.RC100.getCode()); + resultData.setMessage(ReturnCode.RC100.getMessage()); + resultData.setData(data); + return resultData; + } + + public static ResultData fail(String message) { + ResultData resultData = new ResultData<>(); + resultData.setStatus(ReturnCode.RC999.getCode()); + resultData.setMessage(message); + return resultData; + } + + public static ResultData fail(int code, String message) { + ResultData resultData = new ResultData<>(); + resultData.setStatus(code); + resultData.setMessage(message); + return resultData; + } + + + +} diff --git a/guavalimit/src/main/java/com/example/demo/vo/ReturnCode.java b/guavalimit/src/main/java/com/example/demo/vo/ReturnCode.java new file mode 100644 index 0000000..bf0a469 --- /dev/null +++ b/guavalimit/src/main/java/com/example/demo/vo/ReturnCode.java @@ -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; + } +} diff --git a/guavalimit/src/main/resources/application.properties b/guavalimit/src/main/resources/application.properties new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/guavalimit/src/main/resources/application.properties @@ -0,0 +1 @@ +