SpringBoot 对接外部接口,一步一步性能调实战篇

本文展示了如何使用SpringBoot构建一个HTTP请求工具类,包括GET和POST方法,处理BearerToken的授权,并提供了使用Gson解析JSON的示例。此外,还讨论了如何通过Guava的RateLimiter实现基于令牌桶算法的接口限流策略。
摘要由CSDN通过智能技术生成

本平台对接某某平台的接口,保证接口的稳定性和安全性

实战:

首先我们初始化一个Demo,SpringBoot初始化教程略,初始化后的效果如下:

37da984b0768c625dbddb41e3680efba.png
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

  1. 引入依赖:

<!--  整合redis -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
  1. 在application.yml添加redis配置:

spring:
  # redis 配置
  redis:
    # 地址
    host: xxxxx
    # 端口,默认为xxx
    port: xxxx
    # 数据库索引(db0,db1,db2...不同业务可以放在不同数据库中)
    database: 0
    # 密码
    password: 'xxxx'
  1. 注入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;
}

进一步优化

场景:如果有大量请求同时访问一个正好过期的缓存数据,可能会出现缓存击穿,所以我们的解决方案是添加分布式锁

  1. 加入依赖:

<!-- 整合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 结束 -->
  1. 创建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);
    }

}
  1. 注入并编写读锁、写锁:

@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实现接口限流

394c0172d5959ede17f066d023c911c4.png

令牌桶算法的原理也比较简单:系统会维护一个令牌(token)桶,以一个恒定的速度往桶里放入令牌(token),这时如果有请求进来想要被处理,则需要先从桶里获取一个令牌(token),当桶里没有令牌(token)可取时,则该请求将被拒绝服务。令牌桶算法通过控制桶的容量、发放令牌的速率,来达到对请求的限制。

单机模式模拟:
  1. 添加依赖:

<!-- 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>
  1. 自定义限流注解:

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 "系统繁忙,请稍后再试.";
}
  1. 使用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());
    }
}
  1. 给需要限流的接口加上注解

/**
 * 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("请求失败!");
    }
}

多次请求时:

d5381978a6a48ced67333bc96f5ff881.png

bc52d380b96ade9a489712b6bbe10fdf.jpeg

SpringBoot 最核心的27个注解,你了解多少?


15f813c2e284426bc71994ff9990cba0.jpeg

大白话DDD(DDD黑话终于都懂了)


4272eba5352c760097d9abeb73091e28.png

为什么魂斗罗只有128KB却可以实现那么长的剧情?


2f35d89915584bb6702353fbb2b12ca0.jpeg

面试官:为啥索引可以让查询变快?


outside_default.png

业务架构、数据架构、应用架构、技术架构对比


794d0ba95852e1b517b97e9f8d001c5f.jpeg

别再分库分表了,试试TiDB!


a598b1630531636eedcce30a9e512dae.jpeg

面试官:如何实现一个读写分离的中间件?


29a018fad00b4ac7afc565cc11828f94.jpeg

一次由热部署导致的 OOM 排查经历 !


86792fea50d8b97bcb963fec19aa57a5.jpeg

基于 Spring Cloud 开源分布式物联网(IOT)平台,强啊!


eb0b750eb140060c276debeb25116cce.gif

回复干货】获取精选干货视频教程

回复加群】加入疑难问题攻坚交流群

回复mat】获取内存溢出问题分析详细文档教程

回复赚钱】获取用java写一个能赚钱的微信机器人

回复副业】获取程序员副业攻略一份

1c8ba7286bdc9503f869e219d205f3f0.jpeg

好文请点赞+分享

f9cf707a6d9c0415f7391ec761b20eab.gif


Spring Boot开发中,可以使用RestTemplate来实现与外部接口对接。RestTemplate提供了对GET和POST请求的支持。 GET请求可以通过RestTemplate的getForObject或getForEntity方法发送。例如,使用getForObject方法可以直接获取返回的对象,而使用getForEntity方法可以获取返回的响应实体对象。对于POST请求,可以使用RestTemplate的postForObject或postForEntity方法发送。其中,postForObject方法可以直接获取返回的对象,而postForEntity方法可以获取返回的响应实体对象。在使用RestTemplate之前,需要在项目的依赖中添加spring-boot-starter-data-redis依赖来整合Redis。可以在pom.xml文件中添加以下依赖配置:`<!--整合redis--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>`除此之外,还需要在启动类上添加注解@EnableFeignClients和@SpringBootApplication,并指定需要扫描的包路径。例如:`@SpringBootApplication @EnableFeignClients @ComponentScan(basePackages={"com.definesys.mpaas","com.xdap.*","com.xdap.*"}) public class MobilecardApplication { public static void main(String[] args) { SpringApplication.run(MobilecardApplication.class, args); } }`通过以上的配置和使用RestTemplate,可以实现Spring Boot接口对接。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [SpringBoot 对接外部接口一步一步性能实战篇](https://blog.csdn.net/u012811805/article/details/130460526)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* *3* [SpringBoot 外部接口的三种方式](https://blog.csdn.net/weixin_46768610/article/details/129848985)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值