/* 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 #include #include #include #include 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; }