三(按 NCMAPI_NOTES §2.1/§4/§6):
① 接口级响应缓存(对齐参考 apicache 2 分钟,防风控高频 ip 错误):
- 新增 core/ncm_apicache.{c,h}——内存 4 槽 LRU(单槽≤128KB,TTL 120s,
键=md5(path+\x01+json))+ 可选盘缓存 <data_dir>/ncm_api_cache/.json
(8B 墙钟头+body,超 TTL miss 并删,30s 节流清过期)。自包含模块
(仅 libc+pthread+clock_gettime),可独立链接无符号冲突。
- ncm_api.c:api_post 拆「请求+解析」两段,新增 api_post_cached;四个只读
接口接线:user_playlist/playlist_detail/song_detail/cloudsearch。
song_url/lyric/vip 不入缓存。
- 手动刷新绕过:ncm_api_cache_refresh_path(path) 一次性标记;home/歌单
topbar ↻ 接入;playlist_open/search 不标记(走缓存)。
- 盘层挂载 appstate_init;登出 appstate_logout 全清(防串账号)。
② 游客 MUSIC_A:ncm_req.c weapi+eapi 无 MUSIC_U 且非 login 路径补
MUSIC_A=anonymous_token(对齐参考 cookie 伪装)。
③ track/all 死代码清理:移除 ncm_api_playlist_track_all(真实服务器 8 形态全
404,无调用点);ncm_api_parse_track_all 保留(离线重放/单测),注释标注
「已验证不存在,勿用于真实请求」。
验证:新 bench/apicache_sim.c(0 网络)①TTL 内不重复请求/②过期重拉/③手动
刷新绕过/④cloudsearch 按键区分/⑤清空重拉/⑥盘缓存写入+clear 全 PASS;stream/
player_flow/window/search/cache_evict(1-9)/pagination/playlist_race sim 全
PASS;test_ncm_core host+mipsel(qemu) PASS;build_ncm.sh -Wall -Wextra 0
warning(text 1026914/data 1664/bss 1363460);0 高频真实请求。新 ncm md5
a69c93538920eba9d42a2ebb42f8fdca(留主会话上卡)。
133 lines
6.5 KiB
C
133 lines
6.5 KiB
C
/* bench/apicache_sim.c — 接口级响应短 TTL 缓存验证(风控加固 §1,0 真实 NCM 请求)。
|
||
*
|
||
* 用**真实** core(ncm_api.c / ncm_req.c / ncm_crypto.c / ncm_json.c / ncm_common.c /
|
||
* ncm_apicache.c),仅把网络层 ncm_http_request 换成计数桩(canned JSON 响应),验证:
|
||
* ① TTL 内重复调用不重复请求(缓存命中);
|
||
* ② TTL 过期后重拉(expired re-fetch);
|
||
* ③ 手动刷新(ncm_api_cache_refresh_path)绕过缓存;
|
||
* ④ cloudsearch 缓存按键(关键词/分页)区分,不同关键词各拉一次;
|
||
* ⑤ 清空(登出)后重拉;
|
||
* ⑥ 盘缓存写入/清除(文件存在性)。
|
||
* 风控纪律:0 次真实 NCM 请求(网络层计数桩);sleep 仅用于 TTL 过期(短 TTL=1s)。
|
||
*
|
||
* 编译(宿主),在 ncm_app 目录:见 build_ncm_sims.sh 新增 apicache_sim 段。
|
||
* 运行:./apicache_sim
|
||
*/
|
||
#include "ncm.h"
|
||
#include "ncm_http.h"
|
||
#include <stdio.h>
|
||
#include <string.h>
|
||
#include <stdlib.h>
|
||
#include <unistd.h>
|
||
#include <sys/stat.h>
|
||
|
||
static int g_http_calls = 0; /* ncm_http_request 桩计数 = 真实网络调用数 */
|
||
|
||
/* 桩:不联网,按 url 返回 canned JSON,并计数。 */
|
||
static const char *BODY_USER_PL =
|
||
"{\"code\":200,\"playlist\":[{\"id\":\"1\",\"name\":\"P1\",\"coverImgUrl\":\"http://x/c.jpg\",\"trackCount\":3,\"subscribed\":false}]}";
|
||
static const char *BODY_CLOUD_SEARCH =
|
||
"{\"code\":200,\"result\":{\"songCount\":1,\"songs\":[{\"id\":\"100\",\"name\":\"S\",\"dt\":200000,\"fee\":0,"
|
||
"\"ar\":[{\"name\":\"A1\"}],\"al\":{\"name\":\"AL1\",\"picUrl\":\"http://x/1.jpg\"}}]}}";
|
||
|
||
int ncm_http_request(const ncm_http_req_t *req, char *buf, size_t buf_sz, ncm_http_resp_t *resp) {
|
||
(void)req; (void)buf_sz;
|
||
g_http_calls++;
|
||
const char *body = (strstr(req->url, "cloudsearch") != NULL) ? BODY_CLOUD_SEARCH : BODY_USER_PL;
|
||
size_t n = strlen(body);
|
||
memcpy(buf, body, n);
|
||
memset(resp, 0, sizeof *resp);
|
||
resp->status = 200;
|
||
resp->body = buf;
|
||
resp->body_len = n;
|
||
resp->content_length = n;
|
||
return NCM_OK;
|
||
}
|
||
void ncm_http_set_verify_default(int verify, const char *ca_path) { (void)verify; (void)ca_path; }
|
||
|
||
static int g_fail = 0;
|
||
#define CHECK(cond, fmt, ...) do { \
|
||
if (cond) printf(" PASS " fmt "\n", ##__VA_ARGS__); \
|
||
else { printf(" FAIL " fmt "\n", ##__VA_ARGS__); g_fail = 1; } \
|
||
} while (0)
|
||
|
||
static const char *g_device = "AA11BB22CC33DD44EE55FF6600112233445566778899AABB00";
|
||
|
||
int main(void) {
|
||
ncm_apicache_init(NULL); /* 内存缓存;盘层本轮单独测(临时目录) */
|
||
ncm_apicache_ttl_set(NCM_APICACHE_TTL_S);
|
||
|
||
ncm_playlist_t pls[8]; int pn = 0;
|
||
|
||
/* ① TTL 内不重复请求(user/playlist) */
|
||
int c0 = g_http_calls;
|
||
int rc = ncm_api_user_playlist(g_device, "", "0", 30, 0, pls, 8, &pn);
|
||
CHECK(rc == NCM_OK && pn == 1, "user_playlist rc=%d n=%d (first = real)", rc, pn);
|
||
rc = ncm_api_user_playlist(g_device, "", "0", 30, 0, pls, 8, &pn);
|
||
CHECK(g_http_calls == c0 + 1, "TTL 内不重复请求: calls=%d (期望 %d)", g_http_calls, c0 + 1);
|
||
CHECK(rc == NCM_OK && pn == 1, "cached user_playlist rc=%d n=%d", rc, pn);
|
||
|
||
/* ② TTL 过期后重拉(短 TTL=1s;sleep 2s 确保严格超 1000ms 触发过期) */
|
||
ncm_apicache_ttl_set(1);
|
||
sleep(2); /* 等上一条(默认 120s 存的)过期 */
|
||
c0 = g_http_calls;
|
||
rc = ncm_api_user_playlist(g_device, "", "0", 30, 0, pls, 8, &pn);
|
||
CHECK(g_http_calls == c0 + 1, "过期重拉: calls=%d (期望 +1)", g_http_calls);
|
||
CHECK(rc == NCM_OK && pn == 1, "refetch after expiry rc=%d n=%d", rc, pn);
|
||
/* 刚存了 1s TTL 的新条目,立即再取应命中 */
|
||
int c1 = g_http_calls;
|
||
rc = ncm_api_user_playlist(g_device, "", "0", 30, 0, pls, 8, &pn);
|
||
CHECK(g_http_calls == c1, "重拉后 TTL 内命中: calls=%d (期望不增)", g_http_calls);
|
||
ncm_apicache_ttl_set(NCM_APICACHE_TTL_S); /* 还原默认 TTL */
|
||
|
||
/* ③ 手动刷新绕过缓存(一次性消费) */
|
||
c0 = g_http_calls;
|
||
ncm_api_cache_refresh_path("/api/user/playlist");
|
||
rc = ncm_api_user_playlist(g_device, "", "0", 30, 0, pls, 8, &pn);
|
||
CHECK(g_http_calls == c0 + 1, "手动刷新绕过: calls=%d (期望 +1)", g_http_calls);
|
||
CHECK(rc == NCM_OK && pn == 1, "refresh bypass rc=%d n=%d", rc, pn);
|
||
c1 = g_http_calls;
|
||
rc = ncm_api_user_playlist(g_device, "", "0", 30, 0, pls, 8, &pn);
|
||
CHECK(g_http_calls == c1, "刷新后(路径已消费)回缓存: calls=%d", g_http_calls);
|
||
|
||
/* ④ cloudsearch 缓存:同关键词命中、不同关键词各自请求 */
|
||
ncm_song_t songs[8]; int sn = 0; int64_t total = -1;
|
||
c0 = g_http_calls;
|
||
rc = ncm_api_cloudsearch_song(g_device, "", "abc", 0, 30, songs, 8, &sn, &total);
|
||
CHECK(rc == NCM_OK && sn == 1, "cloudsearch abc rc=%d n=%d", rc, sn);
|
||
int c2 = g_http_calls;
|
||
rc = ncm_api_cloudsearch_song(g_device, "", "abc", 0, 30, songs, 8, &sn, &total);
|
||
CHECK(g_http_calls == c2, "cloudsearch 同关键词命中: calls=%d", g_http_calls);
|
||
rc = ncm_api_cloudsearch_song(g_device, "", "def", 0, 30, songs, 8, &sn, &total);
|
||
CHECK(g_http_calls == c2 + 1, "cloudsearch 不同关键词各拉一次: calls=%d (期望 %d)", g_http_calls, c2 + 1);
|
||
CHECK(rc == NCM_OK && sn == 1, "cloudsearch def rc=%d n=%d", rc, sn);
|
||
|
||
/* ⑤ 清空(登出/换账号)后重拉 */
|
||
c0 = g_http_calls;
|
||
ncm_apicache_clear();
|
||
rc = ncm_api_user_playlist(g_device, "", "0", 30, 0, pls, 8, &pn);
|
||
CHECK(g_http_calls == c0 + 1, "清空后重拉: calls=%d (期望 +1)", g_http_calls);
|
||
|
||
/* ⑥ 盘缓存写入/清除(临时目录,默认 TTL) */
|
||
char tmpdir[128];
|
||
snprintf(tmpdir, sizeof tmpdir, "/tmp/apicache_sim_%ld", (long)getpid());
|
||
mkdir(tmpdir, 0777);
|
||
ncm_apicache_init(tmpdir);
|
||
ncm_apicache_ttl_set(NCM_APICACHE_TTL_S);
|
||
ncm_apicache_put("0123456789abcdef0123456789abcdef", "hello-disk", 10);
|
||
char dp[512];
|
||
snprintf(dp, sizeof dp, "%s/ncm_api_cache/0123456789abcdef0123456789abcdef.json", tmpdir);
|
||
struct stat st;
|
||
CHECK(stat(dp, &st) == 0, "盘缓存写入文件存在: %s", dp);
|
||
/* 命中盘缓存(内存若也存了会先命中内存;这里单独验证 clear 后文件被删) */
|
||
ncm_apicache_clear();
|
||
CHECK(stat(dp, &st) != 0, "盘缓存 clear 后文件删除");
|
||
/* 清掉临时目录(内容已由 clear 删除,rmdir) */
|
||
char cmd[160];
|
||
snprintf(cmd, sizeof cmd, "rm -rf \"%s\"", tmpdir);
|
||
if (system(cmd) != 0) { /* 忽略 */ }
|
||
|
||
printf("\n%s\n", g_fail ? "RESULT: FAIL" : "RESULT: PASS");
|
||
return g_fail ? 1 : 0;
|
||
}
|