feat(ncm): 风控加固——接口响应短 TTL 缓存 + 游客 MUSIC_A + track/all 死代码

三(按 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(留主会话上卡)。
This commit is contained in:
AskaEth committed 2026-09-02 12:48:34 +08:00
1 parent 9d5301f931
commit ea645ce2a6
17 files changed
+717 -74

No files matched your search

+132
View File
@@ -0,0 +1,132 @@
/* 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;
}