Files
MeowOS-For-R3P2/patch_v2/ncm_app/app/logic/account.c
T
AskaEth 3d70a3dd2c feat: M6.10–M6.19 批量收编 + M6.19 推卡验收
- M7 launcher 重写 5 期(虚拟化宫格/meow_theme 单一权威/横幅动画/自定义 PIN 键盘)
- OOM 通用化(cachegov 上收 SDK)、自动切歌根治、meowpanel jv_free 泄漏修复、Hello 滚动卡顿修复
- ncm 歌词 UI / 媒体通知封面 / 封面分辨率 640
- 5 bug 修复:ncm 后台化 UI 叠层+点击穿透、再进 ncm 桌面叠层、播放页新旧 UI 叠层抢刷脏区(meow_ui_gfx 恢复边沿整帧重画)、PIN 亮屏亮度跳(keys.c meow_panel_on 按 settings_brightness_clamped 恢复)、通知中心封面显歌单封面(cover.c save_cover_raw 加 is_song + 命中块守卫)
- 四 App 统一重链新 meow_ui.a 51caee4a;M6.19=0.8.19 固件 2d2331f2 推卡自刷验收全绿
2026-09-03 12:16:01 +08:00

236 lines
11 KiB
C
Raw 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.
/* logic/account.c — 账号会员态缓存与刷新策略(§20,NCM_CACHE_DESIGN §2.1 定案)。
*
* 落盘:<data_dir>/account.json = {uid, nickname, vip, cvip_expire_ms, svip_expire_ms,
* checked_at}。开 App 先用缓存渲染(account_load 进 g_st);后台重查时机(风控:不轮询):
* ① 登录成功后(login worker,force=1);
* ② checked_at 距今 > 1 天;
* ③ 会员到期时间已过(可能自动续费,再查一次)。
* 每条件每次启动只查一次(单飞标志);查询在调用方 worker 线程上下文执行(UI 零网络)。
* account_vip() 是纯本地视图:到期时间已过即按降档后的有效态返回(不联网)。
*/
#include "../ncm_app_internal.h"
#include <stdio.h>
#include <string.h>
#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);
}
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);
int len = ncm_store_read_file(path, buf, sizeof buf);
if (len <= 0) return;
/* 小 JSON 直接 jsmn 解析(栈上 token 数组,≤48 token 足够) */
jsmntok_t toks[48];
int parent[48];
ncm_json_t j;
if (ncm_json_parse(&j, buf, (size_t)len, toks, 48, parent) < 0) return;
long vip = 0, checked = 0;
ncm_json_path_int(&j, "vip", &vip);
ncm_json_path_int(&j, "checked_at", &checked);
int rc = 0;
int64_t cvip_exp = ncm_json_get_i64_at(&j, ncm_json_path(&j, "cvip_expire_ms"), &rc);
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_SUPER) 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;
/* 有效到期时间 = 当前档对应的到期字段(SUPER 无独立到期字段,沿黑胶 svip 场)。 */
g_st.vip_expire_ms = (vip == NCM_VIP_SVIP || vip == NCM_VIP_SUPER) ? svip_exp
: (vip == NCM_VIP_CVIP) ? cvip_exp : 0;
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_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,"
"\"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)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");
}
/* 当前有效会员档(本地视图,不联网):到期时间已过则降档。 */
int account_vip(void)
{
int vip = g_st.vip;
if (vip > NCM_VIP_NONE && g_st.vip_expire_ms > 0 &&
g_st.vip_expire_ms <= ncm_now_ms())
vip = NCM_VIP_NONE; /* 已到期:按非会员对待(后台会再查一次确认续费) */
return vip;
}
int account_level_cap(void)
{
switch (account_vip()) {
case NCM_VIP_SVIP:
case NCM_VIP_SUPER: return NCM_LEVEL_SURROUND;
case NCM_VIP_CVIP: return NCM_LEVEL_HIRES;
default: return NCM_LEVEL_EXHIGH;
}
}
void account_vip_maybe_refresh(int force)
{
if (!g_st.logged_in || !g_st.ctx.cookie[0]) return;
int64_t now_s = ncm_now_ms() / 1000;
int expired = (g_st.vip > NCM_VIP_NONE && g_st.vip_expire_ms > 0 &&
g_st.vip_expire_ms <= ncm_now_ms());
const char *why = NULL;
if (force && !s_refreshed_login) why = "login";
else if ((g_st.vip_checked_at == 0 || now_s - g_st.vip_checked_at > VIP_TTL_SEC)
&& !s_refreshed_stale) why = "stale>1d";
else if (expired && !s_refreshed_expired) why = "expired";
if (!why) return;
ncm_vip_info_t info;
int rc = ncm_api_vip_info(g_st.ctx.device_id, g_st.ctx.cookie, g_st.uid, &info);
/* 无论成败都置单飞标志:本条件本启动只查一次(失败等下次启动/下个条件,防风控) */
if (force) s_refreshed_login = 1;
if (strcmp(why, "stale>1d") == 0) s_refreshed_stale = 1;
if (strcmp(why, "expired") == 0) s_refreshed_expired = 1;
if (rc != NCM_OK) {
meow_log(MEOW_LOG_WARN, "account: vip refresh (%s) failed rc=%d, keep cached vip=%d",
why, rc, g_st.vip);
return;
}
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.vip == NCM_VIP_SUPER) ? info.svip_expire_ms
: (info.vip == NCM_VIP_CVIP) ? info.cvip_expire_ms : 0;
g_st.vip_checked_at = 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);
}