接口文檔地址: https://www.coderutil.com/apiopen?tab=hotlist&pk=2000?

兩級緩存API性能保證

其實做前幾天的文章中介紹過程序員盒子使用的多集緩存方案,沒有看多的兄弟可以先了解下這篇文章(因為這里我們就不展開講了,直接show me code):

性能優化|前端LocalStorage + Google Cache + Redis三級緩存性能優化

其實服務端接口這里還是用的Google cache+ redis實現兩級緩存,這里并沒有入庫,完全依賴redis:

接口定義

/**
* 微博熱搜
* @return
*/
@GetMapping("/api/resou/v1/weibo")
public APIResponseBean weiboHotSearch(@RequestParam(value = "size", defaultValue = "10") Integer size) {
List<WeiboHotSearchResultBaseVO> result = hotSearchService.getWeiboHotSearchListFromCache(size);
return APIResponseBeanUtil.success(result);
}

針對跨域請求問題,我們還提供了jsonp跨域接口

/**
* 百度熱搜
* @return
*/
@RequestMapping(value = "/api/resou/v1/weibo.jsonp", produces = "text/script;charset=UTF-8", method= RequestMethod.GET)
public String weiboHotSearchJsonp(HttpServletRequest request, String callback) {
String sizeParam = request.getParameter("size");
int size = StringUtils.isBlank(sizeParam) ? DEFAULT_SIZE : Integer.valueOf(sizeParam);
List<WeiboHotSearchResultBaseVO> result = hotSearchService.getWeiboHotSearchListFromCache(size);
return callback + "(" + JsonUtil.toJsonString(result) + ")";
}

查詢方法

 @Autowired
private RedisService redisService;
@Autowired
private ApiUrlConfig apiUrlConfig;

private static final Cache<String, String> RESOU_LOCAL_CACHE;

static {
RESOU_LOCAL_CACHE = CacheBuilder.newBuilder()
.softValues()
.maximumSize(10L)
.expireAfterWrite(300L, TimeUnit.SECONDS)
.build();
}

/**
* 微博熱搜
* @return
*/
public List<WeiboHotSearchResultBaseVO> getWeiboHotSearchListFromCache(int size) {
String localCacheKey = LocalCacheKey.WEIBO.name();
String localCacheVal = RESOU_LOCAL_CACHE.getIfPresent(localCacheKey);
List<WeiboHotSearchResultBaseVO> list;
if (StringUtils.isNotBlank(localCacheVal)) {
list = JsonUtil.fromJson(localCacheVal, new TypeReference<List<WeiboHotSearchResultBaseVO>>() {});
} else {
String key = RedisKeyEnum.WEIBO_HOT_SEARCH_LIST_CACHE.getKey();
String val = redisService.get(key);
if (StringUtils.isNotBlank(val)) {
list = JsonUtil.fromJson(val, new TypeReference<List<WeiboHotSearchResultBaseVO>>() {});
} else {
// 上篇文章中有這個刷新方法的具體實現邏輯
list = refreshAndGetWeiboHotSearchListCache();
}
// 本地緩存如果沒有則刷新到本地緩存
RESOU_LOCAL_CACHE.put(localCacheKey, JsonUtil.toJsonString(list));
}
if (CollectionUtils.isNotEmpty(list)) {
list = list.subList(0, size > list.size() ? list.size() : size);
}
return list;
}

ok,兩集緩存實現接口響應性能到底怎么樣,我們看服務端日志穩定在10ms之內:

接口AKSK鑒權

開放z z鑒權Aksk是做常見的做法之一,這里我們其實實現的也比較簡單,具體方案如下圖所示:

(1)用戶請求接口,header頭邀請攜帶自己的app_key、secret_key

(2)APIFilter攔截API請求,做ak、sk做驗證

(3)aksk驗證通過請求api,請求緩存獲取數據

(4)返回數據

APIFilter定義

@Slf4j
@Order(1)
@WebFilter(filterName = "apiRequestFilter", urlPatterns = {"/api/poster/*", "/api/resou/*", "/api/url/*",
"/api/upload/*", "/api/text/*", "/api/ip/*", "/api/music/*", "/api/yulu/*", "/api/openai/*", "/rmi/*"})
public class ApiRequestFilter implements Filter {

@Autowired
private ApiInvokeRecordService apiInvokeRecordService;

@Autowired
private AccessSecretKeyService accessSecretKeyService;

/***
* 開放API清單
*/
private static Map<String, String> openApiMap = new ConcurrentHashMap<>();

private static List<String> WHITE_API_LIST = new ArrayList<>();

static {

// 需要Ak、Sk鑒權的請求
openApiMap.put("/api/resou/v1/weibo", "微博熱搜");

/**
* 白名單
*/
WHITE_API_LIST.add("/api/poster/qr.temp");

}

@Override
public void init(FilterConfig filterConfig) throws ServletException {

}

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
String uri = request.getRequestURI();
if (isWhiteApiUri(uri)) {
// 跳過鑒權
filterChain.doFilter(servletRequest, servletResponse);
return;
}
if (uri != null && uri.startsWith("rmi")) {
String accessKey = request.getHeader("access-key");
String secretKey = request.getHeader("secret-key");
if (StringUtils.isBlank(accessKey) || StringUtils.isBlank(secretKey)) {
APIResponseBean apiResponse = APIResponseBeanUtil.error(401, "無權限!");
backErrorMessage(response, apiResponse);
return;
}
}
if (isApiUri(uri)) {
// 獲取API請求的認證參數:accessKey secretKey
String accessKey = request.getHeader("access-key");
String secretKey = request.getHeader("secret-key");
// AK、SK 鑒權(走緩存查詢)
APIResponseBean apiResponseBean = accessSecretKeyService.checkRequestAkSk(accessKey, secretKey);
if (!apiResponseBean.getSuccess()) {
log.error("ApiRequestFilter.checkRequestAkSk error, AK:{}, SK:{}, uri:{}", accessKey, secretKey, uri);
backErrorMessage(response, apiResponseBean);
return;
}
if (StringUtils.isNotBlank(openApiMap.get(uri))) {
// API調用埋點
apiInvokeRecordService.point(uri, openApiMap.get(uri), accessKey);
}
}
filterChain.doFilter(servletRequest, servletResponse);
}

@Override
public void destroy() {

}

/***
* 是否需要鑒權API
* @param uri
* @return
*/
private boolean isApiUri(String uri) {
if (openApiMap.keySet().contains(uri)) {
return true;
}
for (String api : openApiMap.keySet()) {
if (uri.startsWith(api)) {
return true;
}
}
return false;
}

/**
* 是否需要鑒權API
* @param uri
* @return
*/
private boolean isRmiApiUri(String uri) {
if (openApiMap.keySet().contains(uri)) {
return true;
}
for (String api : openApiMap.keySet()) {
if (uri.startsWith(api)) {
return true;
}
}
return false;
}

/**
* 是否白名單API
* @param uri
* @return
*/
private boolean isWhiteApiUri(String uri) {
return WHITE_API_LIST.contains(uri);
}

private void backErrorMessage(HttpServletResponse response, APIResponseBean apiResponseBean) {
response.setContentType("application/json; charset=UTF-8");
try {
response.getWriter().print(JsonUtil.toJsonString(apiResponseBean));
} catch (IOException e) {
e.printStackTrace();
}
}
}

Filter中我們維護了需要鑒權和跳過鑒權的白名單API、當前請求如果需要鑒權,獲取header頭中的ak、sk參數,查詢緩存進行aksk認證,認證通過請求接口。

擴展點1

這里的AK、SK是什么時候生成的、生成規則又是什么?

答:ak、sk盒子實在用戶注冊的時候就為每個用戶分配了(這了設計的不好,造成aksk的浪費,因為大部分用戶其實沒有自己的應用、也沒有api調用的需求,最好是用戶需要的時候自己出發生成按鈕,在生成?。?/strong>

AK、SK在哪里查看?

答:目前在個人中心和開放api服務平臺都可以看到自己的專屬ak、sk:

AKSK生成規則?

答:跟ID生成器一樣,只要保證唯一就ok,我這里用的簡單的uuid

既然ak、sk都是唯一的,為啥需要兩個,只生成一個token不行嗎?

答:也是可以的,能夠做的鑒權就可以。

為什么業界一般的做法都有兩個配對使用的,有的叫ak、有的叫appId其實是一樣的,用來標識一個接入方,而secretKey其實可以有多個的,即一個用戶有多個sk,方便統計、計費等,同時雙重保障API調用更安全。

擴展點2

當前分享的方案是一種靜態sk方案,有同學在接入云或者其他第三方api的時候用到了動態sk(token)的方式,看字面意思,靜態sk指生成一次不會在變化,動態token及沒次調用前需要重新請求獲取一次(一般會有一定的實效性,避免沒次都要獲取造成的資源浪費,或者永久有效失去了動態的特點)

本文章轉載微信公眾號@coderutil技術

#你可能也喜歡這些API文章!

我們有何不同?

API服務商零注冊

多API并行試用

數據驅動選型,提升決策效率

查看全部API→
??

熱門場景實測,選對API

#AI文本生成大模型API

對比大模型API的內容創意新穎性、情感共鳴力、商業轉化潛力

25個渠道
一鍵對比試用API 限時免費

#AI深度推理大模型API

對比大模型API的邏輯推理準確性、分析深度、可視化建議合理性

10個渠道
一鍵對比試用API 限時免費