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

需求分析:

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

实战:

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

1.引入依赖

这里我们使用 commons-httpclient 3



    commons-httpclient
    commons-httpclient
    3.1



    com.google.code.gson
    gson
    2.8.6


    org.projectlombok
    lombok

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 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 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>(){}.getType();
HashMap 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 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 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>(){}.getType();
        HashMap 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));
    }
}

为前端提供接口并测试:

首先我们统一返回风格:

package com.example.demo;

import lombok.Data;

import java.io.Serializable;

/**
 * @author xh
 * @Date 2022/9/14
 */
@Data
public class Result implements Serializable {
    private static final long serialVersionUID = 1L;
    /**
     * 编码:0表示成功,其他值表示失败
     */
    private int code = 0;
    /**
     * 消息内容
     */
    private String msg = "success";
    /**
     * 响应数据
     */
    private T data;

    public Result ok(T data) {
        this.setData(data);
        return this;
    }

    public Result 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 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>(){}.getType();
        HashMap 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 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().ok(res);
    } catch (Exception e) {
        e.printStackTrace();
        return new Result().error("请求失败!");
    }
}

模拟Post请求:

/**
 * 模拟POST请求
 * 请求地址:http://localhost:8080/identity/getDetail_post
 */
@PostMapping("/getDetail_post")
public Result getDataPost(@RequestBody HashMap requestBody) {
    String url = BASE_URL + "/****";
    try {
        String res = sendPost(url, requestBody, TOKEN);;
        return new Result().ok(res);
    } catch (Exception e) {
        e.printStackTrace();
        return new Result().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 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>(){}.getType();
        HashMap 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 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().ok(res);
        } catch (Exception e) {
            e.printStackTrace();
            return new Result().error("请求失败!");
        }
    }

    /**
     * 模拟POST请求
     * 请求地址:http://localhost:8080/identity/getDetail_post
     */
    @PostMapping("/getDetail_post")
    public Result getDataPost(@RequestBody HashMap requestBody) {
        String url = BASE_URL + "/****";
        try {
            String res = sendPost(url, requestBody, TOKEN);;
            return new Result().ok(res);
        } catch (Exception e) {
            e.printStackTrace();
            return new Result().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 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>(){}.getType();
    HashMap 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. 引入依赖:


    org.springframework.boot
    spring-boot-starter-data-redis

  1. 在application.yml添加redis配置:
spring:
  # redis 配置
  redis:
    # 地址
    host: xxxxx
    # 端口,默认为xxx
    port: xxxx
    # 数据库索引(db0,db1,db2...不同业务可以放在不同数据库中)
    database: 0
    # 密码
    password: 'xxxx'
  1. 注入RedisTemplate,并优化
@Autowired
RedisTemplate redisTemplate;

private String getToken() {
    ValueOperations operations = redisTemplate.opsForValue();
    // 0.查询Redis
    if(!StringUtils.isEmpty(operations.get("token"))) {
        return operations.get("token");
    }
    // 1.调用获取token接口
    String url = BASE_URL + "/***/token";
    Map 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>(){}.getType();
    HashMap 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. 加入依赖:



    org.redisson
    redisson
    3.11.0



    org.springframework.boot
    spring-boot-starter-data-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 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 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>(){}.getType();
    HashMap 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)可取时,则该请求将被拒绝服务。令牌桶算法通过控制桶的容量、发放令牌的速率,来达到对请求的限制。

单机模式模拟:

  1. 添加依赖:


    com.google.guava
    guava
    30.1-jre



    org.springframework.boot
    spring-boot-starter-aop

  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 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().error(msg).toString());
    }
}
  1. 给需要限流的接口加上注解
/**
 * Get请求
 * @return
 */
@GetMapping("/getDetail_get")
@Limit(key = "limit1", permitsPerSecond = 1, timeout = 1000, timeunit = TimeUnit.MILLISECONDS, msg = "当前排队人数较多,请稍后再试!")
public Result 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().ok(res);
    } catch (Exception e) {
        e.printStackTrace();
        return new Result().error("请求失败!");
    }
}

多次请求时:

来源:blog.csdn.net/m0_51517236/article/details/126855104
展开阅读全文

页面更新:2024-05-01

标签:接口   令牌   注解   初始化   线程   实例   对象   性能   地址   时间   内容

1 2 3 4 5

上滑加载更多 ↓
推荐阅读:
友情链接:
更多:

本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828  

© CopyRight 2008-2024 All Rights Reserved. Powered By bs178.com 闽ICP备11008920号-3
闽公网安备35020302034844号

Top