Files
AskaEth ea645ce2a6 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(留主会话上卡)。
2026-09-02 12:48:34 +08:00

133 lines
6.5 KiB
C
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* 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;
}