feat(ncm): MUSIC_U 临期主动续期 — 启动查龄超阈值后台静默刷新/eapi 会话锚点/cookie 合并+冷却
用户定案:失效前用当前 cookie 换新登录态(用户无感);仅刷新失败才退扫码重登。 - core/ncm_req.c|h:eapi Cookie 头补齐会话锚点 MUSIC_U/MUSIC_A(对齐 request.rs, 补 §36.5 遗留——登录刷新/song_url 会员档依赖);新增 ncm_req_cookie_merge (patch 覆盖 base 同名 cookie,MUSIC_A/__remember_me 等不丢)。 - core/ncm_login.c|h:ncm_login_refresh(eapi /api/login/token/refresh 空体, code200+set-cookie→merge;code!=200→NCM_ERR_AUTH 退扫码;网络/TLS→负码退避)。 - app/logic/account.c:account.json 增 last_refresh_ts + 拆分 cvip/svip; account_cookie_maybe_refresh(年龄阈值 12h 可配 ncm.cookie_refresh_h,单飞/boot, 失败冷却 30min 跨 boot 持久化,AUTH 置回登录页标志)+ mark_fresh/need_relogin。 - app/ui/home.c:home_worker 先续期再拉歌单;app/ui/login.c:803 落盘后 mark_fresh; app/app.c:UI tick 消费回登录标志 → logout+切登录页。 - 新增测试:test_ncm_core 的 cookie_merge + eapi_musicu_hdr;bench/cookie_refresh_sim (端点语义,0 网络);bench/account_cookie_sim(临期/未临期/退避/失效/冷却,0 网络)。 验证:host+qemu 核心测试 PASS;build_ncm_sims.sh 全家桶 PASS;build_ncm.sh 0 warning。 真机只读探针 2 次(设备 10.78.193.50,不写回):refresh 走 eapi 域 interface.music.163.com 在当前网络被 TLS reset(alert 80)→ rc=-5 瞬断退避, session.enc/deviceId md5 前后一致(登录态零损伤)。新 ncm md5 c960244b。
This commit is contained in:
1 parent
de18cef02c
commit
cd7329d141
17 files changed
+673
-13
No files matched your search
@@ -140,6 +140,14 @@ static void app_on_swipe_progress(int x_start, int dx)
|
||||
static void app_on_tick(void)
|
||||
{
|
||||
uint64_t now = meow_ui_time_ms();
|
||||
/* §NEW cookie 续期判会话失效 → 清登录态回登录页(worker 在后台续期发现 301/-460 时置标志;
|
||||
* 在此 UI 线程执行 logout+切页——meow_view_page_replace 非线程安全,只能 UI 线程做) */
|
||||
if (account_cookie_need_relogin()) {
|
||||
account_cookie_relogin_consume();
|
||||
meow_log(MEOW_LOG_WARN, "account: cookie invalid -> re-login");
|
||||
appstate_logout();
|
||||
meow_view_page_replace(ACT_LOGIN);
|
||||
}
|
||||
autoplay_maybe(); /* 真机联调 hook:/tmp/ncm_autoplay 存在则自动播已缓存曲目(一次性) */
|
||||
/* autoplay 已在后台线程起播缓存曲 → 在此(UI 线程)打开播放页,复现用户点曲播放路径 */
|
||||
{
|
||||
|
||||
@@ -15,11 +15,42 @@
|
||||
#define ACCOUNT_FILE "account.json"
|
||||
#define VIP_TTL_SEC (24 * 3600) /* 超 1 天重查 */
|
||||
|
||||
/* §NEW MUSIC_U 临期续期:cookie 年龄阈值默认 12h(可配 ncm.cookie_refresh_h 小时);失败冷却 30 分钟。 */
|
||||
#define COOKIE_REFRESH_DEFAULT_H 12
|
||||
#define COOKIE_REFRESH_COOLDOWN_SEC (30 * 60)
|
||||
#define COOKIE_FAIL_TS_KEY "ncm.cookie_refresh_fail_ts" /* 上次续期失败时刻(秒)持久化:跨 boot 冷却 */
|
||||
|
||||
/* 单飞标志(每次启动每条件最多一次请求) */
|
||||
static int s_refreshed_login; /* 登录后已查 */
|
||||
static int s_refreshed_stale; /* 超 1 天已查 */
|
||||
static int s_refreshed_expired; /* 到期后已查 */
|
||||
|
||||
/* §NEW cookie 续期状态(app 单实例;跨 boot 由 account.json last_refresh_ts + config 冷却键持久化) */
|
||||
static volatile int s_cookie_refreshed; /* 本 boot 已试(单飞:同一 boot 只刷一次) */
|
||||
static volatile int64_t s_cookie_fail_ts; /* 上次续期失败时刻(秒;冷却窗口用,跨 boot 持久化) */
|
||||
static volatile int s_cookie_need_relogin; /* 1=续期判会话失效,需回登录页(UI 消费) */
|
||||
|
||||
static int64_t cookie_refresh_ttl_sec(void)
|
||||
{
|
||||
int h = meow_config_int("ncm.cookie_refresh_h", COOKIE_REFRESH_DEFAULT_H);
|
||||
if (h < 1) h = 1;
|
||||
if (h > 72) h = 72;
|
||||
return (int64_t)h * 3600;
|
||||
}
|
||||
|
||||
/* 冷却时刻读写(持久化到 config,跨 boot:失败后 30 分钟内不重刷,不猛刷/防 -460 自锁)。 */
|
||||
static int64_t cookie_fail_ts_load(void)
|
||||
{
|
||||
int v = meow_config_int(COOKIE_FAIL_TS_KEY, 0);
|
||||
return (int64_t)(v > 0 ? v : 0);
|
||||
}
|
||||
static void cookie_fail_ts_store(int64_t now_s)
|
||||
{
|
||||
char b[24];
|
||||
snprintf(b, sizeof b, "%lld", (long long)(now_s > 0 ? now_s : 0));
|
||||
meow_config_set(COOKIE_FAIL_TS_KEY, b);
|
||||
}
|
||||
|
||||
static void account_path(char *out, size_t sz)
|
||||
{
|
||||
snprintf(out, sz, "%s/%s", g_st.data_dir, ACCOUNT_FILE);
|
||||
@@ -30,6 +61,9 @@ void account_load(void)
|
||||
g_st.vip = NCM_VIP_NONE;
|
||||
g_st.vip_expire_ms = 0;
|
||||
g_st.vip_checked_at = 0;
|
||||
g_st.vip_cvip_exp_ms = 0;
|
||||
g_st.vip_svip_exp_ms = 0;
|
||||
g_st.cookie_last_refresh_ts = 0;
|
||||
|
||||
char path[600], buf[512];
|
||||
account_path(path, sizeof path);
|
||||
@@ -50,26 +84,34 @@ void account_load(void)
|
||||
if (rc != NCM_OK) cvip_exp = 0;
|
||||
int64_t svip_exp = ncm_json_get_i64_at(&j, ncm_json_path(&j, "svip_expire_ms"), &rc);
|
||||
if (rc != NCM_OK) svip_exp = 0;
|
||||
/* §NEW:last_refresh_ts(0=旧会话/缺失 → 视为临期,开机续期一次) */
|
||||
int64_t last_refresh = ncm_json_get_i64_at(&j, ncm_json_path(&j, "last_refresh_ts"), &rc);
|
||||
if (rc != NCM_OK) last_refresh = 0;
|
||||
|
||||
if (vip < NCM_VIP_NONE || vip > NCM_VIP_SVIP) vip = NCM_VIP_NONE;
|
||||
g_st.vip = (int)vip;
|
||||
g_st.vip_cvip_exp_ms = cvip_exp;
|
||||
g_st.vip_svip_exp_ms = svip_exp;
|
||||
g_st.vip_checked_at = checked;
|
||||
g_st.cookie_last_refresh_ts = last_refresh;
|
||||
/* 有效到期时间 = 当前档对应的到期字段 */
|
||||
g_st.vip_expire_ms = (vip == NCM_VIP_SVIP) ? svip_exp
|
||||
: (vip == NCM_VIP_CVIP) ? cvip_exp : 0;
|
||||
meow_log(MEOW_LOG_INFO, "account: loaded vip=%d expire=%lld checked_at=%ld",
|
||||
g_st.vip, (long long)g_st.vip_expire_ms, (long)g_st.vip_checked_at);
|
||||
meow_log(MEOW_LOG_INFO, "account: loaded vip=%d expire=%lld last_refresh_ts=%lld",
|
||||
g_st.vip, (long long)g_st.vip_expire_ms, (long long)last_refresh);
|
||||
}
|
||||
|
||||
static void account_save(int vip, int64_t cvip_exp, int64_t svip_exp, int64_t checked_at)
|
||||
static void account_persist(int vip, int64_t cvip_exp, int64_t svip_exp, int64_t checked_at)
|
||||
{
|
||||
char path[600], buf[512];
|
||||
account_path(path, sizeof path);
|
||||
snprintf(buf, sizeof buf,
|
||||
"{\"uid\":\"%s\",\"nickname\":\"%s\",\"vip\":%d,"
|
||||
"\"cvip_expire_ms\":%lld,\"svip_expire_ms\":%lld,\"checked_at\":%lld}",
|
||||
"\"cvip_expire_ms\":%lld,\"svip_expire_ms\":%lld,\"checked_at\":%lld,"
|
||||
"\"last_refresh_ts\":%lld}",
|
||||
g_st.uid, g_st.nickname, vip,
|
||||
(long long)cvip_exp, (long long)svip_exp, (long long)checked_at);
|
||||
(long long)cvip_exp, (long long)svip_exp, (long long)checked_at,
|
||||
(long long)g_st.cookie_last_refresh_ts);
|
||||
if (ncm_store_write_file(path, buf, strlen(buf)) != NCM_OK)
|
||||
meow_log(MEOW_LOG_WARN, "account: save failed");
|
||||
}
|
||||
@@ -120,10 +162,73 @@ void account_vip_maybe_refresh(int force)
|
||||
}
|
||||
|
||||
g_st.vip = info.vip;
|
||||
g_st.vip_cvip_exp_ms = info.cvip_expire_ms;
|
||||
g_st.vip_svip_exp_ms = info.svip_expire_ms;
|
||||
g_st.vip_expire_ms = (info.vip == NCM_VIP_SVIP) ? info.svip_expire_ms
|
||||
: (info.vip == NCM_VIP_CVIP) ? info.cvip_expire_ms : 0;
|
||||
g_st.vip_checked_at = now_s;
|
||||
account_save(info.vip, info.cvip_expire_ms, info.svip_expire_ms, now_s);
|
||||
account_persist(info.vip, info.cvip_expire_ms, info.svip_expire_ms, now_s);
|
||||
meow_log(MEOW_LOG_INFO, "account: vip refresh (%s) -> vip=%d expire=%lld",
|
||||
why, info.vip, (long long)g_st.vip_expire_ms);
|
||||
}
|
||||
|
||||
/* ---- §NEW MUSIC_U 临期续期 ----
|
||||
* 用户定案语义:失效前用当前 cookie 换新登录态(用户无感);只有刷新失败(会话确失效)才退扫码。
|
||||
* 风控:同一 boot 只试一次;失败冷却 30 分钟(下次启动再试,不猛刷)。在 worker 线程上下文调用。
|
||||
* 返回:NCM_OK(已续期/未临期/冷却中/未登录);NCM_ERR_AUTH(会话失效,需回登录页);
|
||||
* 其它负错误码(网络/TLS 瞬断——保持旧 cookie,退避)。 */
|
||||
int account_cookie_maybe_refresh(void)
|
||||
{
|
||||
if (!g_st.logged_in || !g_st.ctx.cookie[0]) return NCM_OK;
|
||||
if (s_cookie_refreshed) return NCM_OK; /* 本 boot 已试(单飞) */
|
||||
|
||||
int64_t now_s = ncm_now_ms() / 1000;
|
||||
s_cookie_fail_ts = cookie_fail_ts_load(); /* 跨 boot 恢复上次失败时刻 */
|
||||
if (s_cookie_fail_ts > 0 && now_s - s_cookie_fail_ts < COOKIE_REFRESH_COOLDOWN_SEC) {
|
||||
s_cookie_refreshed = 1; /* 冷却窗口内:视为已处理,boot 内不再重复 */
|
||||
return NCM_OK;
|
||||
}
|
||||
|
||||
int64_t last = g_st.cookie_last_refresh_ts;
|
||||
int stale = (last == 0) || (now_s - last > cookie_refresh_ttl_sec());
|
||||
if (!stale) { s_cookie_refreshed = 1; return NCM_OK; } /* cookie 尚新(≤阈值),无需续期 */
|
||||
|
||||
s_cookie_refreshed = 1; /* 单飞:本 boot 最多一次刷 */
|
||||
char new_cookie[NCM_COOKIE_MAX];
|
||||
int rc = ncm_login_refresh(g_st.ctx.device_id, g_st.ctx.cookie,
|
||||
new_cookie, sizeof new_cookie);
|
||||
if (rc == NCM_OK) {
|
||||
ncm_ctx_set_cookie(&g_st.ctx, new_cookie); /* 内存 + 加密落盘(同一机制) */
|
||||
g_st.cookie_last_refresh_ts = now_s;
|
||||
s_cookie_fail_ts = 0; cookie_fail_ts_store(0); /* 成功:清冷却 */
|
||||
account_persist(g_st.vip, g_st.vip_cvip_exp_ms, g_st.vip_svip_exp_ms, g_st.vip_checked_at);
|
||||
meow_log(MEOW_LOG_INFO, "account: cookie refreshed (age %llds -> now), last_refresh_ts=%lld",
|
||||
(long long)(now_s - (last ? last : now_s)), (long long)now_s);
|
||||
return NCM_OK;
|
||||
}
|
||||
if (rc == NCM_ERR_AUTH) {
|
||||
s_cookie_fail_ts = now_s; cookie_fail_ts_store(now_s);
|
||||
s_cookie_need_relogin = 1; /* UI 线程消费 → logout + 进登录页 */
|
||||
meow_log(MEOW_LOG_WARN, "account: cookie refresh -> session invalid (rc=%d), re-login", rc);
|
||||
return NCM_ERR_AUTH;
|
||||
}
|
||||
/* 网络/TLS/HTTP 异常(瞬断):冷却 30 分钟(跨 boot 持久化),保持旧 cookie,下次启动再试 */
|
||||
s_cookie_fail_ts = now_s; cookie_fail_ts_store(now_s);
|
||||
meow_log(MEOW_LOG_WARN, "account: cookie refresh transient rc=%d, backoff 30min", rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
int account_cookie_need_relogin(void) { return s_cookie_need_relogin; }
|
||||
void account_cookie_relogin_consume(void) { s_cookie_need_relogin = 0; }
|
||||
|
||||
/* 登录成功(落盘新 cookie)后调用:把续期年龄标为现在(新 cookie 视为全新),并重置单飞/冷却,
|
||||
* 使同 boot 内后续如果年龄再次临期(不可能,但幂等)可再刷;同时清除上会的「需回登录页」标志。 */
|
||||
void account_cookie_mark_fresh(void)
|
||||
{
|
||||
g_st.cookie_last_refresh_ts = ncm_now_ms() / 1000;
|
||||
s_cookie_refreshed = 0;
|
||||
s_cookie_fail_ts = 0; cookie_fail_ts_store(0);
|
||||
s_cookie_need_relogin = 0;
|
||||
meow_log(MEOW_LOG_INFO, "account: cookie marked fresh (last_refresh_ts=%lld)",
|
||||
(long long)g_st.cookie_last_refresh_ts);
|
||||
}
|
||||
@@ -171,7 +171,12 @@ typedef struct ncm_app_state {
|
||||
/* 会员态(§20,account.json 落盘:data/account.json;刷新=登录后/超 1 天/到期后一次) */
|
||||
int vip; /* NCM_VIP_*(NONE/CVIP/SVIP) */
|
||||
int64_t vip_expire_ms; /* 当前档到期时间(ms;0=无/未知) */
|
||||
int64_t vip_cvip_exp_ms; /* 音乐包/畅听到期(ms;account.json cvip_expire_ms,落盘保真) */
|
||||
int64_t vip_svip_exp_ms; /* 黑胶到期(ms;account.json svip_expire_ms) */
|
||||
int64_t vip_checked_at; /* vip/info 最近获取时间(秒) */
|
||||
/* MUSIC_U 临期续期(§NEW,用户定案):cookie 最近一次成功续期时间(秒;account.json
|
||||
* last_refresh_ts;0=缺失/旧会话视为临期)。App 启动时后台静默刷新一次,不打断播/不卡 UI。 */
|
||||
int64_t cookie_last_refresh_ts;
|
||||
|
||||
/* 我的歌单 */
|
||||
ncm_playlist_t playlists[NCM_PLAYLIST_MAX];
|
||||
@@ -434,6 +439,13 @@ void account_load(void); /* appstate_init 时读 account.j
|
||||
int account_vip(void); /* 当前有效会员档(到期自动降档视图,不联网) */
|
||||
int account_level_cap(void); /* 会员态允许的最高音质档(NONE→EXHIGH/CVIP→HIRES/SVIP→SURROUND) */
|
||||
void account_vip_maybe_refresh(int force); /* worker 线程上下文调用(含网络,单次) */
|
||||
/* §NEW MUSIC_U 临期续期:App 启动(home_worker)调用——查 cookie 年龄,临期且登录且本 boot 未试
|
||||
* 则后台静默刷新一次(用当前 cookie 换新登录态落盘);返回 NCM_OK(已续期/未临期/冷却中)、
|
||||
* NCM_ERR_AUTH(会话失效,需回登录页,读 account_cookie_need_relogin)或负错误码(瞬断,退避)。 */
|
||||
int account_cookie_maybe_refresh(void);
|
||||
int account_cookie_need_relogin(void); /* UI 线程取「续期判失效需回登录页」标志 */
|
||||
void account_cookie_relogin_consume(void); /* UI 线程消费该标志 */
|
||||
void account_cookie_mark_fresh(void); /* 登录成功落盘新 cookie 后调用(重置年龄/单飞/冷却) */
|
||||
void appstate_apply_playlist_cover(int idx);
|
||||
void appstate_set_playing_song(int idx, const char *cover_url);
|
||||
int appstate_current_playing(void); /* 返回当前播放歌曲下标(UI) */
|
||||
|
||||
@@ -209,7 +209,13 @@ static void *home_worker(void *arg)
|
||||
(void)arg;
|
||||
int rc;
|
||||
if (!g_st.logged_in) rc = NCM_ERR_AUTH;
|
||||
else rc = appstate_load_playlists();
|
||||
else {
|
||||
/* §NEW MUSIC_U 临期续期:先于歌单网络请求(后续 eapi/weapi 用新 cookie)。
|
||||
* AUTH=会话确失效(置回登录页标志,UI 线程消费);瞬断=保持旧 cookie 走缓存/旧会话。 */
|
||||
int crc = account_cookie_maybe_refresh();
|
||||
if (crc == NCM_ERR_AUTH) rc = NCM_ERR_AUTH;
|
||||
else rc = appstate_load_playlists();
|
||||
}
|
||||
if (rc == NCM_OK)
|
||||
account_vip_maybe_refresh(0); /* §20:超 1 天/到期后各补查一次(worker 线程,不卡 UI) */
|
||||
pthread_mutex_lock(&g_home_mu);
|
||||
|
||||
@@ -145,6 +145,7 @@ static void *login_worker(void *arg)
|
||||
meow_log(MEOW_LOG_INFO, "login: 803 confirmed, saving cookie");
|
||||
ncm_strlcpy(cookie, out_cookie, sizeof cookie);
|
||||
ncm_ctx_set_cookie(&g_st.ctx, cookie);
|
||||
account_cookie_mark_fresh(); /* §NEW:新登录态 cookie 视为全新(年龄=now,重置单飞/冷却) */
|
||||
/* 803 后同步取 uid/nickname 并落盘(weapi 已修复:不再挂死、cookie 全量且去重发送,
|
||||
* 服务端认可会话)。此处在 worker 线程执行(不卡 UI);失败不阻断跳页——home 加载
|
||||
* 时 appstate_ensure_uid 会再验一次(也设了硬超时上限,不阻塞歌单页)。 */
|
||||
|
||||
Reference in new issue
Block a user