{ "schema_version": 1, "diagram_type": "architecture", "meta": { "title": "TallyNote 本地优先报销账本架构", "locale": "zh-CN", "output": "tallynote-architecture.html", "quality_profile": "showcase", "viewBox": [1360, 800], "views": [ { "id": "request-path", "label": "主请求路径", "focus": ["operator", "web", "api", "security", "expense", "db"], "note": "从管理员在浏览器快速记账,到安全会话和 SQLite 持久化。" }, { "id": "attachment-consistency", "label": "附件一致性", "focus": ["api", "expense", "attachment", "filesystem", "db", "audit"], "note": "查看 staging、内容校验、原子晋级与事务回滚如何保持记录和文件一致。" }, { "id": "export-recovery", "label": "导出与恢复", "focus": ["api", "export", "db", "filesystem", "janitor", "audit"], "note": "跟踪筛选快照、异步 ZIP 构建、会话绑定下载和重启恢复。" }, { "id": "admin-governance", "label": "管理员治理", "focus": ["operator", "web", "api", "admin", "security", "audit"], "note": "查看管理员初始化、停用、重置密码和全局审计边界。" } ] }, "components": [ { "id": "operator", "type": "external", "label": "使用者 / 管理员", "sublabel": "Chrome 浏览器", "pos": [30, 250], "size": [150, 80] }, { "id": "web", "type": "frontend", "label": "React 工作台", "sublabel": "React 19 + Vite", "tag": "桌面优先", "pos": [220, 250], "size": [190, 80] }, { "id": "api", "type": "backend", "label": "Fastify REST API", "sublabel": "/api/* + 静态托管", "tag": "单进程入口", "pos": [450, 250], "size": [190, 80] }, { "id": "security", "type": "security", "label": "认证与请求防护", "sublabel": "Argon2id · Cookie · CSRF", "tag": "HttpOnly / Origin", "pos": [450, 50], "size": [190, 80] }, { "id": "expense", "type": "backend", "label": "账目业务服务", "sublabel": "筛选 · 状态 · 乐观锁", "tag": "至少一张凭证", "pos": [680, 250], "size": [190, 80] }, { "id": "db", "type": "database", "label": "SQLite + Drizzle", "sublabel": "WAL · 外键 · 事务", "tag": "同机持久化", "pos": [910, 250], "size": [190, 80] }, { "id": "attachment", "type": "backend", "label": "附件流水线", "sublabel": "staging · 内容识别 · 晋级", "tag": "20 MB / 文件", "pos": [680, 430], "size": [190, 80] }, { "id": "filesystem", "type": "cloud", "label": "本地数据文件系统", "sublabel": "files / staging / exports", "tag": "同一卷 · UUID 路径", "pos": [910, 430], "size": [190, 80] }, { "id": "export", "type": "backend", "label": "异步导出构建器", "sublabel": "Excel · ZIP · manifest", "tag": "会话绑定任务", "pos": [910, 50], "size": [190, 80] }, { "id": "audit", "type": "backend", "label": "审计子系统", "sublabel": "追加写入 · 前后值 · 请求 ID", "tag": "只读查询", "pos": [680, 50], "size": [190, 80] }, { "id": "admin", "type": "backend", "label": "管理员管理", "sublabel": "初始化 · 停用 · 重置", "tag": "最后管理员保护", "pos": [450, 590], "size": [190, 80] }, { "id": "janitor", "type": "backend", "label": "启动清理与恢复", "sublabel": "孤儿文件 · 过期任务 · 会话", "tag": "每 60 秒 + 重启", "pos": [30, 590], "size": [150, 80] } ], "boundaries": [ { "kind": "region", "label": "TallyNote 单实例本地运行时(localhost / Docker)", "wraps": ["web", "api", "security", "expense", "db", "attachment", "filesystem", "export", "audit", "admin", "janitor"], "pad": 28 }, { "kind": "security-group", "label": "受保护 API 边界", "wraps": ["api", "security", "admin", "expense", "attachment", "export", "audit"], "pad": 18 } ], "connections": [ { "id": "operator-web", "from": "operator", "to": "web", "label": "浏览器交互", "variant": "emphasis", "fromSide": "right", "toSide": "left", "labelAt": [200, 210] }, { "id": "web-api", "from": "web", "to": "api", "label": "HTTP / REST", "variant": "emphasis", "fromSide": "right", "toSide": "left", "labelAt": [430, 210] }, { "id": "security-api", "from": "security", "to": "api", "label": "Cookie + CSRF / Origin", "variant": "security", "fromSide": "bottom", "toSide": "top", "labelAt": [900, 195] }, { "id": "api-expense", "from": "api", "to": "expense", "label": "账目路由", "fromSide": "right", "toSide": "left", "labelAt": [700, 210] }, { "id": "expense-db", "from": "expense", "to": "db", "label": "SQLite transaction", "variant": "emphasis", "fromSide": "right", "toSide": "left", "labelAt": [890, 210] }, { "id": "expense-attachment", "from": "expense", "to": "attachment", "label": "multipart 上传", "fromSide": "bottom", "toSide": "top", "labelAt": [880, 380] }, { "id": "attachment-files", "from": "attachment", "to": "filesystem", "label": "staging → atomic promote", "variant": "emphasis", "fromSide": "right", "toSide": "left", "labelAt": [890, 405] }, { "id": "api-export", "from": "api", "to": "export", "label": "snapshot → async job", "variant": "dashed", "fromSide": "right", "toSide": "left", "via": [[660, 290], [660, 25], [890, 25], [890, 90]], "labelAt": [775, 12] }, { "id": "export-db", "from": "export", "to": "db", "label": "快照读取", "fromSide": "bottom", "toSide": "top", "labelAt": [1070, 195] }, { "id": "export-files", "from": "export", "to": "filesystem", "label": "ZIP / Excel / manifest", "variant": "dashed", "fromSide": "right", "toSide": "bottom", "via": [[1280, 90], [1280, 700], [1005, 700]], "labelAt": [1170, 720] }, { "id": "expense-audit", "from": "expense", "to": "audit", "label": "操作审计", "variant": "dashed", "fromSide": "top", "toSide": "bottom", "labelAt": [620, 195] }, { "id": "admin-api", "from": "admin", "to": "api", "label": "管理员管理", "fromSide": "top", "toSide": "bottom", "labelAt": [700, 530] }, { "id": "janitor-files", "from": "janitor", "to": "filesystem", "label": "孤儿文件 / 过期任务", "variant": "dashed", "fromSide": "bottom", "toSide": "bottom", "via": [[105, 735], [1005, 735]], "labelAt": [555, 715] } ], "cards": [ { "dot": "cyan", "title": "请求与数据", "items": ["前端通过统一 API 客户端携带会话 Cookie 和 CSRF 令牌", "账目写入、附件元数据和审计在 SQLite 事务内保持一致"] }, { "dot": "emerald", "title": "文件可靠性", "items": ["附件先落 staging,完成格式、大小和内容校验后原子晋级", "失败路径删除已晋级字节,启动时继续清理孤儿文件"] }, { "dot": "amber", "title": "导出与治理", "items": ["导出冻结不可变快照,后台生成 Excel、ZIP 和可选 manifest", "管理员变更、记录操作、附件和导出都进入只读审计日志"] } ] }