v0.7.0 接上真实服务器:登录 / 设备会话 / 全量同步与各模块真实数据
从「演示数据壳子」走到「真的能连家里的服务器用」。
登录(用户 2026-10-17「完整的登录功能」)
· 首次启动先填服务器地址 → /api/v1/ping 自报门户(认得出"这地址上真是家书")
· 手机号或用户名 + 密码 → 拿令牌;失败提示统一,不泄露账号是否存在
· FbDeviceStore:首次启动生成并持久化设备 UUID(三方应用拿不到 UDID),
设备名取机型(marketName),并上报 platform / app_version
设备会话(v57,用户 2026-10-17)
· 一把令牌 = 一台设备的一条会话;每次启动打一次 /me(有网就验)
· 两种锁分开:need_login(401/被踢 → 清令牌)与 offline_expired
(离线超 24h → 只锁界面、不清数据、联网自动恢复)——
混成一种就会违背用户要的"一直联网就不用每天输密码"
· 运行期任何请求 401 → 网络层全局回调立刻锁定跳登录页(FbHttp 的 authProbe
让登录/校验类请求跳过,避免"两个地方同时跳登录页")
· 退出登录:先清本地令牌(主防线),再尽力通知服务端
· 设置页显示版本号与「这台设备」
数据层
· FbDb / FbSchema / FbParse / 各 Repo(日程 / 名册 / 观影 / 模块)+ FbSyncAll
· 首页改为服务端 /home(农历/节气/纪念日/生日/天气/统计/往年的今天)
· 日程页用 FbDayEventPanel,与网页端显示方式/红线/并排三处一致
验证:ArkTS 静态守卫 231/0;hvigor 编译 BUILD SUCCESSFUL(entry-default-signed.hap)。
This commit is contained in:
@@ -0,0 +1,176 @@
|
||||
/**
|
||||
* 家书 FamilyBook · 全量同步(鸿蒙端)
|
||||
*
|
||||
* 一次把所有模块都同步一遍。
|
||||
*
|
||||
* 🔴 为什么需要它(用户 2026-10-12:"把同步做成全局的,接进来一个模块,
|
||||
* 就自动同步一个模块"):
|
||||
* 之前每个页面**自己同步自己**(进入观影页同步观影、进名册页同步名册),
|
||||
* 结果是"用户必须把每个页面都点一遍才拿到全量数据",
|
||||
* 而且设置页那个「保存并同步」只同步了观影 —— 一个入口只干一部分活,
|
||||
* 用户没法理解。
|
||||
* ⇒ 加这个"全量同步":**一处触发、全部模块都更新**。
|
||||
*
|
||||
* ⚠️ 它与"页面各自同步"**不冲突**,两者都要有:
|
||||
* · 页面各自同步:保证"刚进这一页就有最新数据";
|
||||
* · 全量同步:保证"不用点遍每一页"。
|
||||
* 两条路的终点是同一个缓存(`module_cache` / 实体表),所以谁先谁后都对。
|
||||
*/
|
||||
|
||||
import { hilog } from '@kit.PerformanceAnalysisKit';
|
||||
import { FbMovieRepo, FbSyncResult } from './FbMovieRepo';
|
||||
import { FbPersonRepo, FbRosterSyncResult } from './FbPersonRepo';
|
||||
import { FbModuleLoad, FbModuleRepo } from './FbModuleRepo';
|
||||
import { FB_MODULE_PATH } from './FbModuleLoad';
|
||||
import { FB_TOKEN_STORE } from './FbTokenStore';
|
||||
|
||||
const DOMAIN: number = 0x0000;
|
||||
const TAG: string = 'FbSyncAll';
|
||||
|
||||
/** 全量同步结果 */
|
||||
export interface FbSyncAllResult {
|
||||
ok: boolean;
|
||||
/** 成功同步的模块数 */
|
||||
done: number;
|
||||
/** 总模块数 */
|
||||
total: number;
|
||||
/** 失败模块名(用户看得到的人话) */
|
||||
failed: string[];
|
||||
/** 当前进度文案(同步中实时更新用) */
|
||||
note: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 全量同步的实时进度(供 UI 显示)。
|
||||
*
|
||||
* ⚠️ 为什么用一个**模块级变量**而不是回调:
|
||||
* 本机设备 `hilog` 读不出内容(已知限制),
|
||||
* 同步又可能跑十几秒 —— 页面必须能显示"现在到哪一步了",
|
||||
* 否则用户以为卡死。用轮询读这个变量的方式最省事(见设置页)。
|
||||
*/
|
||||
export let FB_SYNC_ALL_PROGRESS: string = '';
|
||||
|
||||
/** 模块的中文名(进度提示用) */
|
||||
const MODULE_CN: Record<string, string> = {
|
||||
'movies': '观影',
|
||||
'roster': '名册',
|
||||
'calendar': '日程',
|
||||
'tasks': '任务',
|
||||
'days': '纪念日',
|
||||
'trips': '旅程',
|
||||
'ledger': '账本',
|
||||
'resources': '资源',
|
||||
'books': '藏书',
|
||||
'notes': '笔记',
|
||||
'notifications': '通知',
|
||||
'home': '首页',
|
||||
};
|
||||
|
||||
/**
|
||||
* 进度回调(可选)。
|
||||
*
|
||||
* 🔴 为什么加这个参数,而不是让调用方去轮询 `FB_SYNC_ALL_PROGRESS`:
|
||||
* 模块级 `export let` 的"实时值"依赖打包器是否保留 live binding ——
|
||||
* 这在 esmodule / 混淆 / tree-shaking 下**不保证**,一旦被内联成常量,
|
||||
* 进度条就永远停在初始值(而调用方还以为在正常工作)。
|
||||
* ⇒ 登录页这种"用户正盯着屏幕等"的场景,进度必须**从函数里推出来**。
|
||||
*
|
||||
* @param label 正在同步的模块中文名(如「观影」)
|
||||
* @param done 已完成的模块数(**不含**当前这个)
|
||||
* @param total 模块总数
|
||||
*/
|
||||
export type FbSyncStepCb = (label: string, done: number, total: number) => void;
|
||||
|
||||
export async function syncAll(onStep?: FbSyncStepCb): Promise<FbSyncAllResult> {
|
||||
const token: string = FB_TOKEN_STORE.get();
|
||||
const failed: string[] = [];
|
||||
if (token === '') {
|
||||
const r: FbSyncAllResult = {
|
||||
ok: false, done: 0, total: 12, failed: [],
|
||||
note: '未登录,无法同步',
|
||||
};
|
||||
FB_SYNC_ALL_PROGRESS = r.note;
|
||||
return r;
|
||||
}
|
||||
|
||||
let done: number = 0;
|
||||
const total: number = 12;
|
||||
|
||||
/** 跑一个模块并记账 */
|
||||
const step = async (label: string, work: () => Promise<boolean>): Promise<void> => {
|
||||
FB_SYNC_ALL_PROGRESS = label;
|
||||
if (onStep !== undefined) {
|
||||
onStep(label, done, total);
|
||||
}
|
||||
try {
|
||||
const ok: boolean = await work();
|
||||
if (ok) {
|
||||
done += 1;
|
||||
} else {
|
||||
failed.push(label);
|
||||
}
|
||||
} catch (err) {
|
||||
hilog.warn(DOMAIN, TAG, '同步 %{public}s 失败:%{public}s', label, JSON.stringify(err));
|
||||
failed.push(label);
|
||||
}
|
||||
};
|
||||
|
||||
// ---- ① 有实体表的两个模块(先做:它们的数据量大、最该先落地)----
|
||||
await step(MODULE_CN['movies'], async (): Promise<boolean> => {
|
||||
const r: FbSyncResult = await FbMovieRepo.inst().sync(token);
|
||||
return r.ok;
|
||||
});
|
||||
|
||||
await step(MODULE_CN['roster'], async (): Promise<boolean> => {
|
||||
const r: FbRosterSyncResult = await FbPersonRepo.inst().sync(token);
|
||||
return r.ok;
|
||||
});
|
||||
|
||||
// ---- ② 其余模块(走通用缓存)----
|
||||
const cacheModules: string[] = [
|
||||
'calendar', 'tasks', 'days', 'trips', 'ledger', 'resources', 'books', 'notes',
|
||||
'notifications', 'home',
|
||||
];
|
||||
const repo: FbModuleRepo = FbModuleRepo.inst();
|
||||
for (const name of cacheModules) {
|
||||
const label: string = MODULE_CN[name] !== undefined ? MODULE_CN[name] : name;
|
||||
const path: string = FB_MODULE_PATH[name] !== undefined ? FB_MODULE_PATH[name] : '';
|
||||
if (path === '') {
|
||||
continue;
|
||||
}
|
||||
await step(label, async (): Promise<boolean> => {
|
||||
// 这里只关心"有没有成功写进缓存":`load` 的 sink 回调是同步的,
|
||||
// 用一个 Promise 把它包起来,等到网络那次回来(fromCache=false)才算成功。
|
||||
const ok: boolean = await new Promise<boolean>((resolve: (v: boolean) => void): void => {
|
||||
let settled: boolean = false;
|
||||
repo.load(name, path, (r: FbModuleLoad): void => {
|
||||
if (r.fromCache || settled) {
|
||||
return; // 缓存那次不算结果
|
||||
}
|
||||
settled = true;
|
||||
resolve(r.error === '');
|
||||
});
|
||||
// 兜底超时(网络卡住时不能让全量同步永远挂着)
|
||||
setTimeout((): void => {
|
||||
if (!settled) {
|
||||
settled = true;
|
||||
resolve(false);
|
||||
}
|
||||
}, 20000);
|
||||
});
|
||||
return ok;
|
||||
});
|
||||
}
|
||||
|
||||
const result: FbSyncAllResult = {
|
||||
ok: failed.length === 0,
|
||||
done: done,
|
||||
total: total,
|
||||
failed: failed,
|
||||
note: failed.length === 0
|
||||
? ('全部 ' + done.toString() + ' 个模块同步完成')
|
||||
: (done.toString() + '/' + total.toString() + ' 完成,失败:' + failed.join('、')),
|
||||
};
|
||||
FB_SYNC_ALL_PROGRESS = result.note;
|
||||
return result;
|
||||
}
|
||||
Reference in New Issue
Block a user