日程改为本地库按需查询,修掉每次进页面解析 9.3MB 的架构错误
## 用户指出的问题
「这个数据量很大,不是应该每次同步完了之后解包放在一个数据库里吗?
然后点开日程,不就是把今天的日程从数据库里查出来吗?
这个应该速度非常快才对呀!怎么能每次打开都要去读所有的数据呢?」
用户说得完全对。这是**架构错误**,上一轮我只做了算法层的局部优化
(把 O(n²) 改成 O(n)),没动"数据怎么存、怎么取"这个根本问题。
## 改前 vs 改后
改前:
load() → module_cache 表读 **9.3MB 字符串**
→ JSON.parse **28628 个对象**
→ 全塞进 this.allEvents 内存数组
渲染 → 在这 28628 条上遍历
`module_cache` 的设计注释本来写着「解析一遍毫秒级,可接受」——
那是按小数据量假设写的,日程涨到 2.8 万条后这个假设就破了。
改后:
load() → ① `WHERE date BETWEEN ? AND ?`(走 idx_cal_date)→ 立刻渲染
② 后台拉全量 → 批量入库 → 重查当前范围
渲染 → 只在**当前范围**那几十到几百条上遍历
## 改动
### FbSchema / FbDb:新增 cal_events 实体表
字段对齐 FbCalEvent(12 个)+ uid + fetched_at,三个索引:
- `idx_cal_date` —— 按日期查(最高频)
- `idx_cal_date_kind` —— 农历/节日按 kind 过滤
- `idx_cal_uid` —— 同步去重
⚠️ 主键不用服务端 `ref_id`:衍生条目(农历/节气/纪念日)的 ref_id **恒为 0**,
几万条全撞主键。用自增 rowid + (cal_id, uid) 唯一索引。
uid 由 kind|calId|evId|date|time|title哈希 组成。
⚠️ **建表语句与 migrate 两处都改了**,列名逐一比对一致
(这个坑本项目已踩 5 次,加了静态守卫钉住)。
### FbCalStore(新建):本地库读写
- `replaceAll(rows, stamp)` —— 事务内先 DELETE 再分批写(20 行/批,照
FbMovieRepo 的实测结论,200 行会卡死)。整批替换而非逐条 upsert,
否则服务端已删的条目会永远留在本地。
- `between(from,to)` / `ofDay(iso)` —— 页面真正用的查询
- `lunarBetween` —— 月视图 42 个格子只要农历文字,单独查比取全量再过滤快
- `count()` / `fetchedAt()` —— 判空与"数据新不新"
⚠️ 命名用 `FbCalStore` 而不是 `FbCalRepo`:后者已存在,是**接口读写**层
(create/update/remove/fetchBooks)。我第一版误覆盖了它,编译报 8 处错误后
从 git 恢复,改名为 Store 区分「本地库」和「接口」。
### FbCalendarPage
- `allEvents` → **`rangeEvents`**(改名强制提醒:这里不再是全量)
- `rangeOfView()` —— 算当前视图需要的日期区间
- `loadFromLocal()` —— 先查本地,有数据立刻渲染
- `persistAndRefresh()` —— 入库完成后重查,界面跟上最新数据
- `reloadRange()` + `@Monitor('showYear','showMonth','mode')` —— 翻月/切视图按需重查
- `rangeSeq` —— 防抖,避免旧结果覆盖新结果
## 验证
### 编译
`build_hap debug` → BUILD SUCCESSFUL
### 静态校验(34 项全过)
`_tooling/check_cal_db.js`,含最关键的两项:
- 建表与迁移**列名逐一相同**(15 列全对上)
- `load()` 里本地查询确实在远程同步**之前**
### 未验证
**真机实测未做**(设备未连接)。需要确认:
- 老库升级(v7→v8)是否顺利建表
- 首次进入(库空)要等全量拉取,之后进入应该秒开
- 翻月是否立刻有数据
## 过程中的一个失误
第一版把新仓储命名为 `FbCalRepo`,**覆盖了已存在的同名文件**。
编译立刻报 8 处 "has no exported member / does not exist on type"。
从 git 恢复原文件后改名 `FbCalStore`,避免了两层职责混在一个类里。
教训:新建文件前先查同名是否存在(`git status` 没报是因为该文件本就在版控里,
我 `cp` 上去把它改成"已修改"状态,看着像正常改动)。
This commit is contained in:
@@ -37,7 +37,7 @@ export const FB_DB_NAME: string = 'familybook.db';
|
||||
* 本地库**不能像服务端那样"每次启动都全量 ALTER 一遍"** ——
|
||||
* 那是拿用户的手机当试验田。必须按版本逐步迁移。
|
||||
*/
|
||||
export const FB_DB_VERSION: number = 7;
|
||||
export const FB_DB_VERSION: number = 8;
|
||||
|
||||
/**
|
||||
* 建表语句。
|
||||
@@ -315,6 +315,46 @@ export const FB_SCHEMA_SQL: string[] = [
|
||||
payload TEXT NOT NULL DEFAULT '',
|
||||
fetched_at INTEGER NOT NULL DEFAULT 0
|
||||
)`,
|
||||
|
||||
// ---------- 日程实体表(v8 新增)----------
|
||||
//
|
||||
// 用户 2026-11-03 指出:「数据量这么大,不是应该同步完放进数据库,
|
||||
// 点开日程就把今天的查出来吗?怎么能每次都去读全部数据?」
|
||||
//
|
||||
// 他说得对。原来日程走 `module_cache`(存 9.3MB JSON 原样文本),
|
||||
// 每次进页面都要读 9.3MB + JSON.parse 28628 个对象 + 全塞内存。
|
||||
// 那张表的注释本来说「解析一遍毫秒级,可接受」—— 那是按小数据量假设写的,
|
||||
// 日程 2.8 万条之后这个假设就破了。
|
||||
//
|
||||
// ⇒ 日程单独建实体表(跟 events 表给观影用的思路一致),
|
||||
// 同步时批量入库,页面按日期范围查。
|
||||
//
|
||||
// ⚠️ 为什么主键不用 id:
|
||||
// 服务端 `ref_id` 对衍生条目(农历/节气/纪念日)恒为 0,
|
||||
// 几万条全是 0 会撞主键。用自增 rowid + (cal_id, uid) 唯一索引。
|
||||
`CREATE TABLE IF NOT EXISTS cal_events (
|
||||
row_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
uid TEXT NOT NULL DEFAULT '' COMMENT '服务端条目稳定标识',
|
||||
ev_id INTEGER NOT NULL DEFAULT 0 COMMENT '服务端 ref_id(自定义日程才是真 id)',
|
||||
cal_id INTEGER NOT NULL DEFAULT 0 COMMENT '所属日历本 id;0=衍生条目',
|
||||
title TEXT NOT NULL DEFAULT '',
|
||||
date TEXT NOT NULL DEFAULT '' COMMENT 'YYYY-MM-DD',
|
||||
time TEXT NOT NULL DEFAULT '' COMMENT 'HH:mm;全天为空',
|
||||
end_time TEXT NOT NULL DEFAULT '',
|
||||
cal_name TEXT NOT NULL DEFAULT '',
|
||||
color TEXT NOT NULL DEFAULT '',
|
||||
all_day INTEGER NOT NULL DEFAULT 0,
|
||||
place TEXT NOT NULL DEFAULT '',
|
||||
note TEXT NOT NULL DEFAULT '',
|
||||
kind TEXT NOT NULL DEFAULT '' COMMENT 'custom / lunar / holiday / anniv …',
|
||||
fetched_at INTEGER NOT NULL DEFAULT 0 COMMENT '这批数据的拉取时刻(全量替换时用来防并发)'
|
||||
)`,
|
||||
// 按日期查是最高频的(「今天的日程」「本月」),必须索引
|
||||
`CREATE INDEX IF NOT EXISTS idx_cal_date ON cal_events (date)`,
|
||||
// 农历/节日按 kind 过滤(渲染日历时区分「真日程」和「衍生条目」)
|
||||
`CREATE INDEX IF NOT EXISTS idx_cal_date_kind ON cal_events (date, kind)`,
|
||||
// 同步时按 uid 去重/更新
|
||||
`CREATE UNIQUE INDEX IF NOT EXISTS idx_cal_uid ON cal_events (cal_id, uid)`,
|
||||
];
|
||||
|
||||
/** 建表语句分组说明(供日志/排查用) */
|
||||
@@ -339,6 +379,10 @@ export const FB_TABLE_GROUPS: FbTableGroup[] = [
|
||||
group: '名册',
|
||||
tables: ['person_roster', 'person_tags']
|
||||
},
|
||||
{
|
||||
group: '日程',
|
||||
tables: ['cal_events']
|
||||
},
|
||||
{
|
||||
group: '同步元数据(本地专有)',
|
||||
tables: ['sync_state', 'pending_ops', 'module_sync', 'attach_cache']
|
||||
|
||||
Reference in New Issue
Block a user