鍵.png)
Python + BaiduTransAPI :快速檢索千篇英文文獻(xiàn)(附源碼)
接口文檔地址: https://www.coderutil.com/apiopen?tab=hotlist&pk=2000?
其實(shí)做前幾天的文章中介紹過程序員盒子使用的多集緩存方案,沒有看多的兄弟可以先了解下這篇文章(因?yàn)檫@里我們就不展開講了,直接show me code):
性能優(yōu)化|前端LocalStorage + Google Cache + Redis三級(jí)緩存性能優(yōu)化
其實(shí)服務(wù)端接口這里還是用的Google cache+ redis實(shí)現(xiàn)兩級(jí)緩存,這里并沒有入庫,完全依賴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);
}
針對(duì)跨域請(qǐng)求問題,我們還提供了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 {
// 上篇文章中有這個(gè)刷新方法的具體實(shí)現(xiàn)邏輯
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,兩集緩存實(shí)現(xiàn)接口響應(yīng)性能到底怎么樣,我們看服務(wù)端日志穩(wěn)定在10ms之內(nèi):
開放z z鑒權(quán)Aksk是做常見的做法之一,這里我們其實(shí)實(shí)現(xiàn)的也比較簡(jiǎn)單,具體方案如下圖所示:
(1)用戶請(qǐng)求接口,header頭邀請(qǐng)攜帶自己的app_key、secret_key
(2)APIFilter攔截API請(qǐng)求,做ak、sk做驗(yàn)證
(3)aksk驗(yàn)證通過請(qǐng)求api,請(qǐng)求緩存獲取數(shù)據(jù)
(4)返回?cái)?shù)據(jù)
@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鑒權(quán)的請(qǐng)求
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)) {
// 跳過鑒權(quán)
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, "無權(quán)限!");
backErrorMessage(response, apiResponse);
return;
}
}
if (isApiUri(uri)) {
// 獲取API請(qǐng)求的認(rèn)證參數(shù):accessKey secretKey
String accessKey = request.getHeader("access-key");
String secretKey = request.getHeader("secret-key");
// AK、SK 鑒權(quán)(走緩存查詢)
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調(diào)用埋點(diǎn)
apiInvokeRecordService.point(uri, openApiMap.get(uri), accessKey);
}
}
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void destroy() {
}
/***
* 是否需要鑒權(quán)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;
}
/**
* 是否需要鑒權(quán)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中我們維護(hù)了需要鑒權(quán)和跳過鑒權(quán)的白名單API、當(dāng)前請(qǐng)求如果需要鑒權(quán),獲取header頭中的ak、sk參數(shù),查詢緩存進(jìn)行aksk認(rèn)證,認(rèn)證通過請(qǐng)求接口。
這里的AK、SK是什么時(shí)候生成的、生成規(guī)則又是什么?
答:ak、sk盒子實(shí)在用戶注冊(cè)的時(shí)候就為每個(gè)用戶分配了(這了設(shè)計(jì)的不好,造成aksk的浪費(fèi),因?yàn)榇蟛糠钟脩羝鋵?shí)沒有自己的應(yīng)用、也沒有api調(diào)用的需求,最好是用戶需要的時(shí)候自己出發(fā)生成按鈕,在生成!)
AK、SK在哪里查看?
答:目前在個(gè)人中心和開放api服務(wù)平臺(tái)都可以看到自己的專屬ak、sk:
AKSK生成規(guī)則?
答:跟ID生成器一樣,只要保證唯一就ok,我這里用的簡(jiǎn)單的uuid
既然ak、sk都是唯一的,為啥需要兩個(gè),只生成一個(gè)token不行嗎?
答:也是可以的,能夠做的鑒權(quán)就可以。
為什么業(yè)界一般的做法都有兩個(gè)配對(duì)使用的,有的叫ak、有的叫appId其實(shí)是一樣的,用來標(biāo)識(shí)一個(gè)接入方,而secretKey其實(shí)可以有多個(gè)的,即一個(gè)用戶有多個(gè)sk,方便統(tǒng)計(jì)、計(jì)費(fèi)等,同時(shí)雙重保障API調(diào)用更安全。
當(dāng)前分享的方案是一種靜態(tài)sk方案,有同學(xué)在接入云或者其他第三方api的時(shí)候用到了動(dòng)態(tài)sk(token)的方式,看字面意思,靜態(tài)sk指生成一次不會(huì)在變化,動(dòng)態(tài)token及沒次調(diào)用前需要重新請(qǐng)求獲取一次(一般會(huì)有一定的實(shí)效性,避免沒次都要獲取造成的資源浪費(fèi),或者永久有效失去了動(dòng)態(tài)的特點(diǎn))
本文章轉(zhuǎn)載微信公眾號(hào)@coderutil技術(shù)
Python + BaiduTransAPI :快速檢索千篇英文文獻(xiàn)(附源碼)
掌握ChatGPT API集成的方便指南
node.js + express + docker + mysql + jwt 實(shí)現(xiàn)用戶管理restful api
nodejs + mongodb 編寫 restful 風(fēng)格博客 api
表格插件wpDataTables-將 WordPress 表與 Google Sheets API 連接
手把手教你用Python和Flask創(chuàng)建REST API
使用 Django 和 Django REST 框架構(gòu)建 RESTful API:實(shí)現(xiàn) CRUD 操作
ASP.NET Web API快速入門介紹
2024年在線市場(chǎng)平臺(tái)的11大最佳支付解決方案
對(duì)比大模型API的內(nèi)容創(chuàng)意新穎性、情感共鳴力、商業(yè)轉(zhuǎn)化潛力
一鍵對(duì)比試用API 限時(shí)免費(fèi)