Files
FootPrint/DESIGN.md
T
root 42ca57abfd feat: 足迹 Footprints 个人留痕系统
桌面应用(Tauri+React+SQLite):时间线/记录/学年学科成绩梯度/统计/类型与字段模板/附件内容寻址/详情视图/PWA

同步服务端(sync-server):多用户+邀请码/LWW推拉/seq游标/附件去重/Docker部署

多端同步地基:uuid v7/HLC混合逻辑时钟/软删除墓碑/sync_oplog,Node与Rust双端一致并通过自动化测试
2026-10-16 13:53:15 +08:00

14 KiB
Raw Blame History

个人留痕系统(Footprints)设计方案

版本 v0.5 · 足迹本体是核心;分类、表单、统计、导出、评分全部用户可自定义 形态:单人使用 · Windows 桌面应用(MVP 已实现)· 预留多端同步

0. 设计原则

  1. 足迹本体覆盖一个学生全部值得留痕的事,不局限于任何评分公式
  2. 配置即数据:所有枚举、分类轴、表单字段、统计口径、导出格式都是数据库里的配置, 程序只提供渲染与计算引擎;换学校、换习惯、换关注点 = 改配置,不改代码
  3. 时间线完整:正面/负面记录、进行中、草稿都留痕
  4. 单机桌面、数据全本地、附件与描述分离存储

1. 产品定位

个人成长档案馆:平时随手留痕,需要时按任意维度检索、聚合、量化、导出。

  • 检索盘点:"大二上学期做过什么""拿过哪些省级奖""在宣传部做过哪些事" —— 秒级检索
  • 材料输出:评奖评优/综测/简历/入党材料 → 选条目、汇总表、佐证包
  • 成长量化:自定义维度与指标统计
  • 时间线复盘:完整回顾

2. 足迹本体模型

footprint (
  id            INTEGER PRIMARY KEY,
  type_id       INTEGER NOT NULL,   -- → footprint_type(含自定义类型)
  title         TEXT NOT NULL,
  role          TEXT,               -- 来自 taxonomy(kind='role'),可自定义
  level         TEXT,               -- 来自 taxonomy(kind='level'),可自定义
  polarity      TEXT,               -- positive/negative
  award_grade   TEXT, rank INTEGER, -- 可选
  organizer     TEXT,
  start_date    DATE, end_date DATE,
  status        TEXT,               -- draft/ongoing/submitted/finished
  description   TEXT, result TEXT,
  metrics       TEXT,               -- JSON {"志愿时长":8} 任意键 → metric_def 注册后可统计
  extras        TEXT,               -- JSON 按类型表单模板的动态字段
  group_id      TEXT,               -- 事件归组
  parent_id     INTEGER,            -- 晋升链/隶属
  tags          TEXT,               -- JSON 自由标签
  weight        INTEGER,
  created_at    DATETIME, updated_at DATETIME
);

footprint_type (
  id, name, icon, color, sort,
  form_template JSON,             -- 动态字段模板(见 §3.2)
  is_builtin BOOL, closed BOOL    -- 内置类型可改名/停用,不可删除
);

dimension (id, name, color, sort);          -- 成长维度(自定义分类轴)
footprint_dimension (footprint_id, dimension_id);

attachment (id, owner_type, owner_id, kind, file_path, file_name, mime, size, taken_at);
semester (id, term UNIQUE, gpa, rank, total_students);
course_grade (id, semester_id, course_name, credit, score, grade_point);
footprint_fts: FTS5(title, description, result, tags, extras_text);

3. 用户自定义体系(本版重点)

3.1 十个可自定义层(从记录到输出)

# 对象 自定义内容 存储
1 足迹类型 新建类型(名称/图标/颜色);内置类型改名、停用 footprint_type
2 表单字段 每个类型的动态字段:增删改、必填、默认值、单位 form_template JSON
3 分类轴(维度) 自定义正交分类(如 学业深造/职业发展/兴趣生活),一条足迹可挂多轴 dimension + 多对多
4 标签 自由打标 + 标签管理(重命名/合并/用量统计) tags JSON
5 枚举字典 级别、角色等枚举可增改排序(如加"企业级""国际级") taxonomy
6 统计指标 把 metrics 的键注册为指标:求和/计数/去重计数 + 单位 metric_def
7 视图 保存筛选条件为一键视图(评优视图/学年视图/任一组合),可设默认 saved_view
8 导出模板 列选择、分组方式、文件命名规则 export_template
9 评分适配器 学校综测/加分类规则:预设 JSON 导入 + 界面微调分值 scoring_preset
10 外观 主题色/深浅模式/首页默认视图/时间线密度 app_config

3.2 动态表单模板(form_template)

每个类型一个字段数组,驱动录入表单、详情页、检索列三处渲染:

[
  { "key": "team_members", "label": "队伍成员", "widget": "multiselect", "options": "从联系人选" },
  { "key": "my_work", "label": "我的分工", "widget": "text", "required": true },
  { "key": "deliverable", "label": "最终产品", "widget": "text" },
  { "key": "cert_level", "label": "证书等级", "widget": "select",
    "options": ["从业资格", "初级", "中级", "高级"] },
  { "key": "hours", "label": "志愿时长", "widget": "number", "unit": "小时",
    "metric": "volunteer_hours" }
]
  • widget 类型:text / textarea / number / date / select / multiselect / link(关联其他足迹) / contact(联系人)
  • 数据存 footprint.extras JSON;schema 永不因新字段而改
  • widget=number 且声明 metric 时,录入即累计进统计

3.3 枚举字典(taxonomy)

taxonomy (id, kind TEXT, value TEXT, color TEXT, sort INT, is_builtin BOOL, UNIQUE(kind, value));
-- kind: level(级别) / role(角色) / attachment_kind / polarity ...
-- 预置: level = 国家级/省市级/区县级/校级/院级/none;
--       role  = 主导/核心/参加 —— 均可增改,例如加"企业实习级"

3.4 统计指标与视图

metric_def (id, key TEXT UNIQUE, label TEXT, unit TEXT,
            agg TEXT /*sum|count|distinct*/, section TEXT);
saved_view (id, name, filter JSON, sort JSON, is_default BOOL, updated_at);
export_template (id, name, columns JSON, group_by TEXT, filename_rule TEXT);
  • 指标卡:看板上的"自定义指标卡"由 metric_def 驱动(例:volunteer_hours 求和=志愿总时长; 证书类型 distinct 计数;省级以上获奖 count)
  • 视图:筛选器(类型×级别×角色×维度×学年×状态×标签×关键词)任意组合另存, 例:"综测佐证视图" = level∈{国家级,省市级} + polarity=positive + 有附件

3.5 评分适配器(第 9 层,V3)

预设 JSON 定义分类映射 + 查表/矩阵/公式 + 去重 + 封顶,对足迹投影综测得分单。 示例:rules/重庆城市科技学院-计网学院-2026-2027.json(参考实现,非系统依赖)。

4. 技术选型

Tauri 2 + React 18 + TypeScript + Vite + Tailwind + shadcn/ui + SQLite(rusqlite, FTS5) + ECharts

  • 配置驱动 UI:动态表单/动态筛选器/动态看板全部由上述配置表渲染
  • 备选:Electron + better-sqlite3(数据层一致可互换)

5. 输出

  • 任意视图/筛选结果 → xlsx 汇总表(列与分组来自 export_template)
  • 勾选条目 → 佐证材料包 zip(编号命名规则可配)
  • 适配器激活时 → 综测得分单 + 加分明细对账单
  • V3:评优事迹 Word、简历段生成

6. 迭代路线

  • MVP:类型管理 + 动态表单 + footprint CRUD + 时间线 + FTS 检索 + 附件 + taxonomy + 标签
  • V2:维度/指标/视图/导出模板、事件归组聚合、成绩导入、xlsx/佐证包
  • V3:评分适配器、AI 字段抽取、评优 Word、同步备份

9. 账号系统与多端互通(v0.5 新增)

9.1 定位

单人系统中账号 = 同步身份(多端识别 + 云备份 + 未来协作扩展点),不是数据前提。 未登录 = 纯本地模式(现状完全不变),登录后多端打通。

9.2 路线选型

A 整库文件同步 B 云优先 C 本地优先+同步引擎(选定)
离线 可用 差 完全可用
冲突 整库覆盖丢数据 服务端事务 行级合并可控
改造 极低 高 中(数据层+同步模块)
附件 易碎 对象存储 内容寻址+对象存储

9.3 数据层改造(P1)

-- 业务表统一增加同步元数据
uuid TEXT;          -- uuid v7(时间有序),替代自增 ID 的跨端身份
deleted_at TEXT;    -- 软删除墓碑,删除也要同步
hlc TEXT;           -- 混合逻辑时钟 "毫秒:计数:设备号",冲突仲裁依据
origin TEXT;        -- 创建设备

-- 本地变更日志(推送队列)
CREATE TABLE sync_oplog (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  table_name TEXT NOT NULL,
  row_uuid TEXT NOT NULL,
  op TEXT NOT NULL,            -- upsert / delete
  hlc TEXT NOT NULL,
  synced_at TEXT               -- NULL = 待推送
);
  • 自增 ID 换 UUID(v7):两台设备各自自增必然冲突
  • HLC 而非本地时间:时钟回拨免疫,跨设备可比较、同设备单调
  • 删除走墓碑:硬删除无法向其他设备传播

9.4 同步协议(P2)

  • push:本地 oplog 未同步变更 → 服务端按 hlc LWW 仲裁 upsert
  • pull:GET /sync?cursor=服务端hlc → 应用比自己新的行 → 推进游标
  • 附件:SHA-256 内容寻址;上传前查重;本地按需缓存(同一佐证多处引用只存一份)

9.4.1 附件与图片存储布局

本地(桌面端):

应用数据目录/
├─ footprints.db                  ← 元数据(rel_path/sha256/size/mime + 同步元数据)
└─ attachments/{记录id}/{uuid}_{原文件名}   ← 图片实体原样保存(EXIF 保留)

服务器(sync-server):

data/
├─ sync.db                        ← attachments 表 (user_id, sha256) 主键
└─ blobs/{user_id}/{sha256}       ← 内容寻址,跨记录/跨设备去重

生命周期:添加 → 本地落盘+元数据入 oplog → push 元数据 → 服务器缺 blob 则补传(哈希校验) → 其他端 pull 元数据 → 按需下载。删除 → 元数据墓碑同步,文件延迟 GC(全端确认后回收)。

演进方向:缩略图(256px WebP,网格用缩略图)、大文件分片、可选上传压缩、对象存储适配、E2E 加密。

  • 触发:启动 + 写操作后 debounce 3s + 手动同步
  • 冲突:按行 LWW(单人多端最优);不做 CRDT

9.5 身份层

  • 登录:邮箱魔法链接或手机验证码;本地设备 ID 注册
  • 凭据:Tauri keyring(Windows Credential Manager)
  • 设备管理:device_id ↔ 设备名,可远程下线
  • 未登录:同步模块整体旁路,界面显示"本地模式"

9.6 服务端选型

BaaS(火山引擎 Supabase 版) 自建轻量服务
Auth/DB/存储/Realtime 全部现成 + RLS 行级权限 自管 Postgres + MinIO/OSS
同步实现 客户端直连表 + hlc 游标 单 /sync 模块
运维 免运维,起步免费 一台服务器
适用 快速落地(推荐) 数据完全自持

9.7 安全分层

  1. 基线:TLS + 服务端账号隔离(RLS)
  2. 进阶:本地库 SQLCipher
  3. 可选端到端:客户端加密后再上云(代价是服务端不可检索)

9.8 分阶段落地

阶段 内容 产出 状态
P1 UUID/软删除/HLC/oplog + 存量迁移(纯本地) 同步地基,功能不变 已实现(Node 与 Rust 双端一致)
P1.5 导出加密同步包手动导入合并 无后端的手动多端 待做(可选)
P2 自建同步服务:登录 + push/pull + 附件 hash 上传 真正多端互通 服务端已实现(sync-server/,Docker 部署);客户端引擎待接
P3 Web 端复用 React 前端;移动端 PWA 随时随地 前端响应式 + PWA 改造完成(manifest/SW/抽屉导航),待服务部署后直连
P4 端到端加密;多人协作(复用审核流设计) 班级/学院版 待做

9.9 自建同步服务实现(sync-server/,v0.2 新增)

  • 零 npm 依赖:node:http + node:sqlite,Docker 镜像 node:24-alpine,构建无供应链风险
  • 存储模型:current(每行最新态,user+table+row_uuid 唯一)+ changes(全量历史,seq 全局单调即拉取游标)
  • push:按 HLC 三元组(毫秒:计数:设备)逐级比较 LWW;旧推送计入 stale;服务端不解释业务字段(data 为不透明 JSON,业务表间引用由客户端以 uuid 表达)
  • pull:updated_seq > cursor 增量,游标由服务端 seq 保证单调
  • 附件:(user, sha256) 内容寻址去重,上传校验实际哈希,跨设备仅存一份
  • 认证:scrypt 口令散列 + Bearer token(365 天)+ 设备登记(同名设备复用 device_id);REGISTER_ENABLED 环境变量控制注册开关
  • 多用户:天然按 user_id 隔离(变更/附件/设备);INVITE_CODE 邀请码注册 + MAX_USERS 名额上限 + ADMIN_TOKEN 保护的 /admin/users 管理视图(仅元数据,档案内容管理员不可见)
  • 客户端多账号(P2):登录/切换账号,本地档案按账号建独立 SQLite profile
  • 部署:cd sync-server && docker compose up -d --build,数据落 ./data/;必须经 HTTPS 反代(Caddy 示例见 sync-server/README.md)

9.10 验证状态(诚实边界)

已验证(自动化测试):

  • 服务端协议:注册/登录/多用户/邀请码/名额/管理视图/push(LWW)/pull(游标)/附件去重/账号隔离 —— 全链路通过
  • Node 数据层:53 项回归(CRUD/守卫/FTS/墓碑/级联/oplog)—— 通过
  • Rust 后端:9 项运行时测试(uuid v7/HLC 单调/时钟回拨/老库迁移/命令层往返/墓碑/级联/重名守卫/加权计算)—— 通过

测试曾抓出并已修复的关键缺陷:

  • Rust exec() 返回"受影响行数"而非 lastInsertRowid,导致桌面端所有创建操作拿到 id=1(关键级)
  • Rust footprint_list 的 BASE_SELECT 与 build_where 双 WHERE 拼接导致检索 SQL 报错
  • 学期删除未级联墓碑课程,孤儿课程污染总览

尚未闭环(诚实声明):

  • 客户端同步引擎(P2)未实现:桌面/手机目前均为纯本地,尚无数据真正流向服务器
  • FK 的 uuid 映射约定已定稿(载荷中业务表间引用以 uuid 表达),代码随 P2 实现
  • 真实双设备并发、附件 GC、备份恢复等长尾场景待真机验证

P1 实现说明(v0.2):

  • schema v2:9 张业务表统一 uuid/deleted_at/origin/hlc 列 + sync_state + sync_oplog + uuid 唯一索引
  • 老库迁移幂等:pre_migrate(补列,先于 schema 执行)→ schema → backfill(uuid/hlc/origin)
  • 所有写路径接 oplog(Node 与 Rust 26 命令一致):变更先进本地队列,P2 推送直接消费
  • 删除统一软删除(墓碑);FTS 内容表在删除时同步清理
  • 附件新增 sha256 内容寻址列,为 P2 去重上传就绪
  • 新增 sync_status 命令与侧边栏同步状态显示(本地模式 · 待同步 N)