本平台对接某某平台的接口,保证接口的稳定性和安全性
实战:
首先我们初始化一个Demo,SpringBoot初始化教程略,初始化后的效果如下:
1.引入依赖
这里我们使用 commons-httpclient 3
<!-- https://mvnrepository.com/artifact/commons-httpclient/commons-httpclient -->
<dependency>
<groupId>commons-httpclient</groupId>
<artifactId>commons-httpclient</artifactId>
<version>3.1</version>
</dependency>
<!-- 使用goole的json转化工具 -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.6</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
2.编写工具类(HttpClientUtils):
Get请求:
根据需求,这里我们需要两个参数,一个是token,一个是url参数 + url地址,权限验证采用的是Bearer Token
public static String sendGet(String urlParam, String token) {
// 1.创建httpClient实例对象
HttpClient httpClient = new HttpClient();
// 设置httpClient连接主机服务器超时时间:15000毫秒
httpClient.getHttpConnectionManager().getParams().setConnectionTimeout(15000);
// 2.创建GetMothod实例对象
GetMethod getMethod = new GetMethod(urlParam);
// 3.设置post请求超时时间、请求头
getMethod.getParams().setParameter(HttpMethodParams.SO_TIMEOUT, 60000);
getMethod.addRequestHeader("Content-Type", "application/json");
if(!StringUtils.isEmpty(token)) {
Header header = new Header("Authorization", "Bearer " + token);
getMethod.addRequestHeader(header);
}
try {
// 4.执行getMethod,调用http接口
httpClient.executeMethod(getMethod);
// 5.读取内容[流的形式读取]
InputStream is = getMethod.getResponseBodyAsStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8));
// 采用线程安全的StringBuffer
StringBuffer res = new StringBuffer();
String str= "";
while((str = br.readLine()) != null){
res.append(str);
}
return res.toString();
} catch (IOException e) {
e.printStackTrace();
} finally {
// 6.释放连接
getMethod.releaseConnection();
}
return null;
}
Post请求:
根据需求,这里我们需要三个参数,一个是token,一个是url参数 + url地址,还有一个是请求体,权限验证采用的是Bearer Token
public static String sendPost(String urlParam, Map<String, Object> jsonMap, String token) {
// 1.创建httpClient实例对象
HttpClient httpClient = new HttpClient();
// 设置httpClient连接主机服务器超时时间:15000毫秒
httpClient.getHttpConnectionManager().getParams().setConnectionTimeout(15000);
// 2.创建PostMethod实例对象
PostMethod postMethod = new PostMethod(urlParam);
// 设置post请求超时时间、请求头
postMethod.getParams().setParameter(HttpMethodParams.SO_TIMEOUT, 60000);
postMethod.addRequestHeader("Content-Type", "application/json;charset=utf-8");
if(!StringUtils.isEmpty(token)) {
Header header = new Header("Authorization", "Bearer " + token);
postMethod.addRequestHeader(header);
}
// 3.设置请求体
Gson gson = new Gson();
String jsonStr = gson.toJson(jsonMap);
postMethod.setRequestBody(jsonStr);
try {
// 4.执行postMethod,调用http接口
httpClient.executeMethod(postMethod);
// 5.读取内容[流的形式读取]
InputStream is = postMethod.getResponseBodyAsStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8));
// 采用线程安全的StringBuffer
StringBuffer res = new StringBuffer();
String str= "";
while((str = br.readLine()) != null){
res.append(str);
}
return res.toString();
} catch (IOException e) {
e.printStackTrace();
} finally {
// 7.释放连接
postMethod.releaseConnection();
}
return null;
}
Main方法测试:
// 1.调用获取token接口
String baseUrl = "http://*****/";
String url = baseUrl + "/****/token";
Map<String, Object> jsonMap = new HashMap<>();
jsonMap.put("username", "***");
jsonMap.put("password", "***");
String res = sendPost(url, jsonMap, null);
log.info("获得的请求结果:{}", res);
获得的请求结果:
{"message":"success","status":1,"data":{"token":"eyJh****"}}
我们请求得到JSON字符串后,使用GSON来解析JSON,提取有用的信息,如token
// 2.解析JSON,得到token
Gson gson = new Gson();
// 克服泛型类型擦除问题
// 具体查阅https://zditect.com/main-advanced/java/gson-json-to-map.html
Type mapType = new TypeToken<HashMap<String, Object>>(){}.getType();
HashMap<String, Object> resMap = gson.fromJson(res, mapType);
log.info("请求结果解析:{}", resMap);
LinkedTreeMap data = (LinkedTreeMap) resMap.get("data");
String token = (String) data.get("token");
log.info("token为:{}", token);
获取得到token后我们开始使用,模拟一次Get请求:
url = baseUrl + "******";
log.info("获得的请求结果:{}", sendGet(url, token));
获得的请求结果:
{"message":"success","status":1,"data":{****}
完整工具类,方便大家拿来直接使用:
HttpClientUtils.java
package com.example.demo;
import com.google.gson.Gson;
import com.google.gson.internal.LinkedTreeMap;
import com.google.gson.reflect.TypeToken;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.httpclient.Header;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.params.HttpMethodParams;
import org.springframework.util.StringUtils;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
/**
* @author xh
* @Date 2022/9/14
*/
@Slf4j
public class HttpClientUtils {
public static String sendPost(String urlParam, Map<String, Object> jsonMap, String token) {
// 1.创建httpClient实例对象
HttpClient httpClient = new HttpClient();
// 设置httpClient连接主机服务器超时时间:15000毫秒
httpClient.getHttpConnectionManager().getParams().setConnectionTimeout(15000);
// 2.创建PostMethod实例对象
PostMethod postMethod = new PostMethod(urlParam);
// 设置post请求超时时间、请求头
postMethod.getParams().setParameter(HttpMethodParams.SO_TIMEOUT, 60000);
postMethod.addRequestHeader("Content-Type", "application/json;charset=utf-8");
if(!StringUtils.isEmpty(token)) {
Header header = new Header("Authorization", "Bearer " + token);
postMethod.addRequestHeader(header);
}
// 3.设置请求体
Gson gson = new Gson();
String jsonStr = gson.toJson(jsonMap);
postMethod.setRequestBody(jsonStr);
try {
// 4.执行postMethod,调用http接口
httpClient.executeMethod(postMethod);
// 5.读取内容[流的形式读取]
InputStream is = postMethod.getResponseBodyAsStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8));
// 采用线程安全的StringBuffer
StringBuffer res = new StringBuffer();
String str= "";
while((str = br.readLine()) != null){
res.append(str);
}
return res.toString();
} catch (IOException e) {
e.printStackTrace();
} finally {
// 7.释放连接
postMethod.releaseConnection();
}
return null;
}
public static String sendGet(String urlParam, String token) {
// 1.创建httpClient实例对象
HttpClient httpClient = new HttpClient();
// 设置httpClient连接主机服务器超时时间:15000毫秒
httpClient.getHttpConnectionManager().getParams().setConnectionTimeout(15000);
// 2.创建GetMothod实例对象
GetMethod getMethod = new GetMethod(urlParam);
// 3.设置post请求超时时间、请求头
getMethod.getParams().setParameter(HttpMethodParams.SO_TIMEOUT, 60000);
getMethod.addRequestHeader("Content-Type", "application/json");
if(!StringUtils.isEmpty(token)) {
Header header = new Header("Authorization", "Bearer " + token);
getMethod.addRequestHeader(header);
}
try {
// 4.执行getMethod,调用http接口
httpClient.executeMethod(getMethod);
// 5.读取内容[流的形式读取]
InputStream is = getMethod.getResponseBodyAsStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8));
// 采用线程安全的StringBuffer
StringBuffer res = new StringBuffer();
String str= "";
while((str = br.readLine()) != null){
res.append(str);
}
return res.toString();
} catch (IOException e) {
e.printStackTrace();
} finally {
// 6.释放连接
getMethod.releaseConnection();
}
return null;
}
public static void main(String[] args) {
// 1.调用获取token接口
String baseUrl = "http://****";
String url = baseUrl + "/*****";
Map<String, Object> jsonMap = new HashMap<>();
jsonMap.put("username", "****");
jsonMap.put("password", "*****");
String res = sendPost(url, jsonMap, null);
log.info("获得的请求结果:{}", res);
// 2.解析JSON,得到token
Gson gson = new Gson();
// 克服泛型类型擦除问题
// 具体查阅https://zditect.com/main-advanced/java/gson-json-to-map.html
Type mapType = new TypeToken<HashMap<String, Object>>(){}.getType();
HashMap<String, Object> resMap = gson.fromJson(res, mapType);
log.info("请求结果解析:{}", resMap);
LinkedTreeMap data = (LinkedTreeMap) resMap.get("data");
String token = (String) data.get("token");
log.info("token为:{}", token);
// 3.模拟Get请求
// TODO 需要使用URL编码
url = baseUrl + "****";
log.info("获得的请求结果:{}", sendGet(url, token));
}
}
为前端提供接口并测试:
首先我们统一返回风格:
Result.java
package com.example.demo;
import lombok.Data;
import java.io.Serializable;
/**
* @author xh
* @Date 2022/9/14
*/
@Data
public class Result<T> implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 编码:0表示成功,其他值表示失败
*/
private int code = 0;
/**
* 消息内容
*/
private String msg = "success";
/**
* 响应数据
*/
private T data;
public Result<T> ok(T data) {
this.setData(data);
return this;
}
public Result<T> error(String msg) {
this.code = 500;
this.msg = msg;
return this;
}
}
新建ApiController:
首先我们将公共变量做一个提取:
public static String TOKEN = "";
public static final String BASE_URL = "http://****";
public static final String USERNAME = "****";
public static final String PASSWORD = "****";
// 静态代码块
static {
// 1.调用获取token接口
String url = BASE_URL + "/****";
Map<String, Object> jsonMap = new HashMap<>();
jsonMap.put("username", USERNAME);
jsonMap.put("password", PASSWORD);
String res = sendPost(url, jsonMap, null);
// 2.解析JSON,得到token
Gson gson = new Gson();
Type mapType = new TypeToken<HashMap<String, Object>>(){}.getType();
HashMap<String, Object> resMap = gson.fromJson(res, mapType);
LinkedTreeMap data = (LinkedTreeMap) resMap.get("data");
TOKEN = (String) data.get("token");
log.info("token获取成功:{}", TOKEN);
}
模拟Get请求:
/**
* Get请求
* 请求地址:http://localhost:8080/identity/getDetail_get?handle=xxx
* @return
*/
@GetMapping("/getDetail_get")
public Result<String> getDataGet(@RequestParam String handle) {
log.info("开始发起Get请求, token为:{}", TOKEN);
Assert.notNull(handle);
String url = BASE_URL + "/xxx=" + handle;
try {
String res = sendGet(url, TOKEN);
return new Result<String>().ok(res);
} catch (Exception e) {
e.printStackTrace();
return new Result<String>().error("请求失败!");
}
}
模拟Post请求:
/**
* 模拟POST请求
* 请求地址:http://localhost:8080/identity/getDetail_post
*/
@PostMapping("/getDetail_post")
public Result<String> getDataPost(@RequestBody HashMap<String, Object> requestBody) {
String url = BASE_URL + "/****";
try {
String res = sendPost(url, requestBody, TOKEN);;
return new Result<String>().ok(res);
} catch (Exception e) {
e.printStackTrace();
return new Result<String>().error("请求失败!");
}
}
整体代码:
package com.example.demo;
import com.google.gson.Gson;
import com.google.gson.internal.LinkedTreeMap;
import com.google.gson.reflect.TypeToken;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.*;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.Map;
import static com.example.demo.HttpClientUtils.sendGet;
import static com.example.demo.HttpClientUtils.sendPost;
/**
* @author xh
* @Date 2022/9/14
*/
@RestController
@RequestMapping("/identity/")
@Slf4j
public class ApiController {
public static String TOKEN = "";
public static final String BASE_URL = "http://*****";
public static final String USERNAME = "****";
public static final String PASSWORD = "****";
// 静态代码块
static {
// 1.调用获取token接口
String url = BASE_URL + "/identity/token";
Map<String, Object> jsonMap = new HashMap<>();
jsonMap.put("username", USERNAME);
jsonMap.put("password", PASSWORD);
String res = sendPost(url, jsonMap, null);
// 2.解析JSON,得到token
Gson gson = new Gson();
Type mapType = new TypeToken<HashMap<String, Object>>(){}.getType();
HashMap<String, Object> resMap = gson.fromJson(res, mapType);
LinkedTreeMap data = (LinkedTreeMap) resMap.get("data");
TOKEN = (String) data.get("token");
log.info("token获取成功:{}", TOKEN);
}
/**
* Get请求
* 请求地址:http://localhost:8080/identity/getDetail_get?handle=****
* @return
*/
@GetMapping("/getDetail_get")
public Result<String> getDataGet(@RequestParam String handle) {
log.info("开始发起Get请求, token为:{}", TOKEN);
Assert.notNull(handle);
String url = BASE_URL + "/****" + handle;
try {
String res = sendGet(url, TOKEN);
return new Result<String>().ok(res);
} catch (Exception e) {
e.printStackTrace();
return new Result<String>().error("请求失败!");
}
}
/**
* 模拟POST请求
* 请求地址:http://localhost:8080/identity/getDetail_post
*/
@PostMapping("/getDetail_post")
public Result<String> getDataPost(@RequestBody HashMap<String, Object> requestBody) {
String url = BASE_URL + "/****";
try {
String res = sendPost(url, requestBody, TOKEN);;
return new Result<String>().ok(res);
} catch (Exception e) {
e.printStackTrace();
return new Result<String>().error("请求失败!");
}
}
}
优化:
模拟场景:在尽可能的不破坏源代码的情况下,不喜勿喷
优化一:属性通过配置文件读取
新建application.yml文件
api:
baseUrl: http://*****
username: ****
password: ****
新建配置文件读取类:
ApiConfig.java
package com.example.demo;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* @author xh
* @Date 2022/9/14
*/
@Component
@ConfigurationProperties(prefix = "api")
@Data
public class ApiConfig {
/**
* API地址
*/
private String baseUrl;
/**
* 代理用户名
*/
private String username;
/**
* 代理密码
*/
private String password;
}
ApiController进行微调:
@Autowired
ApiConfig apiConfig;
public static String TOKEN = "";
public static String BASE_URL = "";
public static String USERNAME = "";
public static String PASSWORD = "";
@PostConstruct
private void getBaseInfo() {
BASE_URL = apiConfig.getBaseUrl();
USERNAME = apiConfig.getUsername();
PASSWORD = apiConfig.getPassword();
}
private String getToken() {
if(!StringUtils.isEmpty(TOKEN)) {
return TOKEN;
}
// 1.调用获取token接口
String url = BASE_URL + "/***/token";
Map<String, Object> jsonMap = new HashMap<>();
jsonMap.put("username", USERNAME);
jsonMap.put("password", PASSWORD);
String res = sendPost(url, jsonMap, null);
// 2.解析JSON,得到token
Gson gson = new Gson();
Type mapType = new TypeToken<HashMap<String, Object>>(){}.getType();
HashMap<String, Object> resMap = gson.fromJson(res, mapType);
LinkedTreeMap data = (LinkedTreeMap) resMap.get("data");
TOKEN = (String) data.get("token");
log.info("token获取成功:{}", TOKEN);
return TOKEN;
}
由于Token会存在过期时间,所以我们这里引用Redis
引入依赖:
<!-- 整合redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
在application.yml添加redis配置:
spring:
# redis 配置
redis:
# 地址
host: xxxxx
# 端口,默认为xxx
port: xxxx
# 数据库索引(db0,db1,db2...不同业务可以放在不同数据库中)
database: 0
# 密码
password: 'xxxx'
注入RedisTemplate,并优化
@Autowired
RedisTemplate<String, String> redisTemplate;
private String getToken() {
ValueOperations<String, String> operations = redisTemplate.opsForValue();
// 0.查询Redis
if(!StringUtils.isEmpty(operations.get("token"))) {
return operations.get("token");
}
// 1.调用获取token接口
String url = BASE_URL + "/***/token";
Map<String, Object> jsonMap = new HashMap<>();
jsonMap.put("username", USERNAME);
jsonMap.put("password", PASSWORD);
String res = sendPost(url, jsonMap, null);
// 2.解析JSON,得到token
Gson gson = new Gson();
Type mapType = new TypeToken<HashMap<String, Object>>(){}.getType();
HashMap<String, Object> resMap = gson.fromJson(res, mapType);
LinkedTreeMap data = (LinkedTreeMap) resMap.get("data");
String token = (String) data.get("token");
// 设置TOKEN 6小时过期
operations.set("token", token, 6, TimeUnit.HOURS);
log.info("token获取成功:{}", token);
return token;
}
进一步优化
场景:如果有大量请求同时访问一个正好过期的缓存数据,可能会出现缓存击穿,所以我们的解决方案是添加分布式锁
加入依赖:
<!-- 整合redis 开始 -->
<!--原生redisson-->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.11.0</version>
</dependency>
<!-- 操作redisTemplate -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- 整合redis 结束 -->
创建RedissionConfig.java 配置RedissionClient
package com.example.demo;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
/**
* @author xh
* @Date 2022/9/14
*/
@Component
public class RedissionConfig {
/**
* 所有对redisson的使用都是通过RedissonClient对象
*/
@Bean(destroyMethod="shutdown")
public RedissonClient redisson() {
//创建配置
Config config = new Config();
//可以用"rediss://"来启用SSL连接,useSingleServer表示单例模式
config.useSingleServer().setAddress("redis://xxxx:xxxx").setDatabase(0).setPassword("xxxx");
//根据config创建出RedissonClient实例
return Redisson.create(config);
}
}
注入并编写读锁、写锁:
@Autowired
RedisTemplate<String, String> redisTemplate;
@Autowired
RedissonClient redisson;
private String getToken() {
// 0.查询Redis
String token = readToken();
if(!StringUtils.isEmpty(token)) {
return token;
}
// 1.调用获取token接口
String url = BASE_URL + "/xxx/token";
Map<String, Object> jsonMap = new HashMap<>();
jsonMap.put("username", USERNAME);
jsonMap.put("password", PASSWORD);
String res = sendPost(url, jsonMap, null);
// 2.解析JSON,得到token
Gson gson = new Gson();
Type mapType = new TypeToken<HashMap<String, Object>>(){}.getType();
HashMap<String, Object> resMap = gson.fromJson(res, mapType);
LinkedTreeMap data = (LinkedTreeMap) resMap.get("data");
token = (String) data.get("token");
// 设置TOKEN
Assert.isTrue(setToken(token));
log.info("token获取成功:{}", token);
return token;
}
private String readToken() {
RReadWriteLock lock = redisson.getReadWriteLock("token-lock");
RLock rLock = lock.readLock();
String token = "";
try {
//加读锁
rLock.lock();
token = redisTemplate.opsForValue().get("token");
} catch (Exception e) {
e.printStackTrace();
} finally {
rLock.unlock();
}
return token;
}
private boolean setToken(String token) {
RReadWriteLock lock = redisson.getReadWriteLock("token-lock");
RLock rLock = lock.writeLock();
try {
// 改数据加写锁,读数据加读锁
rLock.lock();
redisTemplate.opsForValue().set("token", token, 6, TimeUnit.HOURS);
} catch (Exception e) {
e.printStackTrace();
} finally {
rLock.unlock();
}
return true;
}
再次优化:
互联网系统经常会遇到高并发大流量的请求,在突发情况下(如秒杀、抢购),瞬间大流量会直接把系统打垮,为了防止出现这种情况最常见的解决方案之一就是限流,当请求达到一定的并发数或速率,就进行等待、排队、降级、拒绝服务等。
基于Guava工具类【令牌桶算法】,借助自定义注解+AOP实现接口限流
令牌桶算法的原理也比较简单:系统会维护一个令牌(token)桶,以一个恒定的速度往桶里放入令牌(token),这时如果有请求进来想要被处理,则需要先从桶里获取一个令牌(token),当桶里没有令牌(token)可取时,则该请求将被拒绝服务。令牌桶算法通过控制桶的容量、发放令牌的速率,来达到对请求的限制。
单机模式模拟:
添加依赖:
<!-- Guava: 限流工具类RateLimiter -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>30.1-jre</version>
</dependency>
<!-- 加入AOP依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
自定义限流注解:
Limit.java
package com.example.demo;
import java.lang.annotation.*;
import java.util.concurrent.TimeUnit;
/**
* @author xh
* @Date 2022/9/15
*/
@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 "系统繁忙,请稍后再试.";
}
使用AOP切面拦截限流注解
package com.example.demo;
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.bind.annotation.ResponseBody;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
/**
* @author xh
* @Date 2022/9/15
*/
@Slf4j
@Aspect
@Component
public class LimitAop {
/**
* 不同的接口,不同的流量控制
* map的key为 Limiter.key
*/
private final Map<String, RateLimiter> limitMap = Maps.newConcurrentMap();
@Around("@annotation(com.example.demo.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 resp=((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
resp.setCharacterEncoding("utf-8");
resp.setContentType("application/json; charset=utf-8");
PrintWriter writer = null;
try {
writer = resp.getWriter();
} catch (IOException e) {
e.printStackTrace();
}
writer.write(new Result<String>().error(msg).toString());
}
}
给需要限流的接口加上注解
/**
* Get请求
* @return
*/
@GetMapping("/getDetail_get")
@Limit(key = "limit1", permitsPerSecond = 1, timeout = 1000, timeunit = TimeUnit.MILLISECONDS, msg = "当前排队人数较多,请稍后再试!")
public Result<String> getDataGet(@RequestParam String handle) {
log.info("开始发起Get请求, token为:{}", getToken());
Assert.notNull(handle);
String url = BASE_URL + "/****" + handle;
try {
String res = sendGet(url, getToken());
return new Result<String>().ok(res);
} catch (Exception e) {
e.printStackTrace();
return new Result<String>().error("请求失败!");
}
}
多次请求时:
基于 Spring Cloud 开源分布式物联网(IOT)平台,强啊!
回复【干货】获取精选干货视频教程
回复【加群】加入疑难问题攻坚交流群
回复【mat】获取内存溢出问题分析详细文档教程
回复【赚钱】获取用java写一个能赚钱的微信机器人
回复【副业】获取程序员副业攻略一份
好文请点赞+分享