本文定义 V1 的目标架构、模块边界、关键数据流和安全边界。Stage 0 不实现这里描述的模块;各阶段按 TASKS.md 逐步落地。具体产品范围以 PRD.md 为准,数据字段以 DATABASE.md 为准。
- 微信小程序兼容性优先,使用原生框架和 TypeScript。
- 游客可本地优先使用,开启身份后平滑同步到 CloudBase。
- 领域模型、校验与规则不依赖页面,为未来 App 保留复用能力。
- 共享数据的身份、权限、并发和敏感操作位于可信服务端。
- 可观测、可重试、可降级,不让网络失败静默造成数据丢失。
- V1 保持简单,不引入与范围不匹配的微服务或复杂基础设施。
微信用户
│
├─ 微信原生小程序(游客本地数据、页面与交互)
│ │
│ ├─ 微信平台能力(身份上下文、分享、订阅消息授权)
│ │
│ └─ CloudBase 云函数(可信写入口、同步、共享、提醒)
│ │
│ ├─ CloudBase 数据库
│ └─ 平台日志/定时触发能力
│
管理员 ── 轻量 Web 管理后台 ── 管理端云函数/接口
逻辑边界不代表 Stage 0 已创建具体运行时或环境。
apps/miniprogram/ 微信小程序 UI、页面状态、平台适配、本地存储适配
apps/admin/ 独立轻量管理端(Stage 14 再选定具体框架)
packages/shared/ 纯 TypeScript 领域类型、校验、日期/重复/同步协议
cloudfunctions/ CloudBase 可信服务端入口和服务端领域编排
config/ 无敏感信息的模板与说明
tests/ 跨模块、契约、安全和同步测试
scripts/ 可重复的检查、生成、迁移辅助工具
docs/ 产品、技术、数据、UI、任务和决策记录
依赖方向:页面/云函数可依赖共享领域层;共享领域层不得依赖微信页面、云函数运行时或 Web UI。小程序不能直接复用含 Node.js 专属 API 的服务端模块。
Stage 1 落地时保持以下逻辑层次,具体目录名可在不破坏边界的前提下细化:
- 页面与组件层:渲染、手势、表单交互、导航和无障碍文本。
- 应用服务层:创建事项、完成事项、改期、开始专注等用例编排。
- 领域层:实体类型、状态转换、校验、排序、重复规则等纯逻辑。
- 数据访问层:本地仓库、云端仓库、同步队列及映射。
- 平台适配层:微信存储、登录上下文、分享、订阅消息、音频等 API。
页面不得直接拼装云数据库写入;业务写操作通过应用服务和仓库接口完成。
Stage 4 建立、Sprint 1B 与 Sprint 2 扩展后的本地访问链为:
页面/组件(只消费结果和处理错误)
→ 各实体 Repository(事项、重复、子任务、提醒、专注)
→ 纯领域函数(类型推导、重复生成、提醒时间、倒数、计时)
→ LocalDataStore(schema 迁移、不可变快照、容量保护)
→ StorageAdapter(微信实现使用 wx.getStorageSync / wx.setStorageSync)
domain/** 与 types/** 不读取页面实例或 CloudBase;services/local/storage-adapter.ts 是唯一直接接触微信 Storage API 的业务数据适配层。页面只能调用仓库/应用服务,不能绕过该边界。订阅授权另由 services/subscription-message-adapter.ts 隔离微信 API,模板 ID 从无敏感信息的配置读取。
- 首次初始化生成
localUser.clientId并持久保存,重启不改变。 - 每个本地实体另生成稳定 UUID 作为实体
clientId;云端正式id在同步前为null,首次上云和重试以实体clientId幂等映射。 - 安装和实体两类
clientId都不是登录凭据,不能用作共享权限依据。 - 清除小程序数据会丢失未同步游客数据;产品需在适当位置说明并引导备份。
Stage 4 使用可替换 Storage Adapter 和版本化 LocalData 快照隔离微信 API。Sprint 3 的本地 schemaVersion=3 在同一个 key 中保存游客标识、个人与共享缓存、同步状态和设置;schemaVersion=1/2 均可自动迁移且不清空 Storage。写入前以 900KiB 软上限保护微信 1MB 单 key 边界;超过边界明确失败并保留旧数据。未来若容量实测需要分片,必须通过 migrateLocalData() 提升 schema,而不是由页面自行拆分或覆盖。
本地写入基本流程:校验 → 生成/保持稳定 ID → 构造不可变新快照 → 容量预检 → 单 key 写入 → 返回新实体。Storage 与迁移异常转为稳定错误码,由后续 UI 明确提示;Stage 4 不伪装写入成功,也不创建同步队列。
本地快照保存 local_only、pending、syncing、synced、failed 与上次成功时间。开启同步后的本地业务写入先成功落盘,再标记 pending 并触发 1.5 秒防抖同步;应用 onShow 和用户手动操作也可触发。云环境未配置或网络失败时保留本地数据和待同步状态,UI 不显示假成功。
小程序通过 CloudService/Adapter 调用单一 rixuApi 云函数入口;页面不直接散落 wx.cloud.database() 或 wx.cloud.callFunction()。客户端只有在本机私有配置中存在真实 envId 时才调用 wx.cloud.init(),否则保持游客本地模式。云函数使用 cloud.getWXContext().OPENID 创建或读取内部 userId;客户端传入的 userId、角色、openid 或 creatorId 均不作为鉴权依据。
- 使用实体 UUID 作为稳定业务键,服务端以“用户/作用域 + 实体 ID”唯一识别记录。
- 每个变更带
updatedAt、version、操作 ID 或等价幂等信息。 - 网络重试同一操作不得产生第二条事项。
- 服务端时间用于可信审计;客户端时间用于离线变更排序时需记录偏差风险。
- 个人数据:以
clientId幂等合并,同一记录按updatedAt较新者优先;时间相等时墓碑优先,其后比较version,最后使用确定性内容顺序,避免各设备反复摆动。 - 共享数据:要求客户端提交已知
version,云函数做版本校验;不匹配返回CONFLICT,客户端重新拉取最新记录并提示,禁止无条件覆盖。 - 本轮使用全量有界快照同步,每类实体最多 2000 条、云端查询每页 100 条且最多 20 页。增量游标、指数退避和墓碑自动清理不属于本 Sprint,达到容量前必须升级协议。
用户操作
→ 应用服务校验
→ 本地仓库立即保存
→ 本地同步状态标记 pending,并防抖触发
→ 云函数验证身份/输入/权限/版本
→ 数据库写入并返回服务端版本
→ 本地确认或标记冲突/失败
个人计时仍以本地 FocusSession 时间戳为事实来源。共同专注页只把有限的 focusing / paused / completed 状态、计划分钟数、开始时间和自愿公开目标发到云端;房间不能远程控制其他成员的本地计时。
共同专注页面
→ FocusRoomService
→ Cloud Adapter
→ rixuApi(可信 OPENID、membership、owner、容量与输入校验)
→ focus_rooms / focus_room_members / focus_room_messages
自愿使用微信昵称或头像时,页面只把当前用户的资料交给 FocusRoomService.updateProfile(),再通过 Cloud Adapter 调用 rixuApi.updateFocusProfile。服务端只采用可信 OPENID 映射出的用户 ID,不接受客户端指定他人身份;昵称经过 msgSecCheck,头像经过 imgSecCheck 后才上传 Cloud Storage,并将通过审核的展示快照同步到当前用户的有效房间成员记录。拒绝授权、审核失败或上传失败时保留原资料或几何身份,不能导致房间白屏。
房间页每 5 秒刷新成员、每 10 秒刷新最近消息,并以约 20 秒心跳维护在线状态;每秒倒计时只在本机刷新,禁止每秒调用云函数。页面隐藏或离开时停止轮询并将成员标记离线。网络失败保留本地计时,不把共同专注失败传播到个人会话仓库。
自由文字、公开目标和自愿昵称在云函数写入前调用微信文字内容安全能力,自愿头像使用图片内容安全能力;平台调用异常、返回非通过或无法判断时一律拒绝写入。公开房由服务端固定定义,邀请房才存在用户房主;邀请、禁言、移除、举报、资料更新和消息发送均不信任客户端角色或用户标识。
云函数按业务能力分组,不按页面复制接口。所有写入口都必须:
- 从可信运行时读取调用者身份。
- 验证输入类型、长度、枚举、日期和对象所有权。
- 对共享清单查询
list_members和创建者关系。 - 对共享更新校验
version。 - 使用服务端时间维护审计字段,不信任客户端提交的权限字段。
- 捕获并返回稳定错误码,内部日志不泄露敏感数据。
- 对关键管理/邀请/解散操作记录审计信息。
数据库安全规则作为纵深防御,不能替代云函数业务授权。客户端不拥有管理数据库的通用写权限。
| 模块 | 主要职责 | 关键边界 |
|---|---|---|
| 启动与身份 | 欢迎状态、clientId、同步开启状态 |
游客标识不是服务端身份 |
| 事项 | 创建、编辑、完成、改期、软删除、排序 | 内部类型由日期字段一致推导 |
| 日历 | 日期选择、周/月展示、按日查询 | 不做专业时间轴 |
| 清单 | 收集箱、自定义清单、共享清单 | 共享权限服务端校验 |
| 重复 | 规则校验、实例计算和编辑范围 | 语义在 Stage 8 定案 |
| 提醒 | 本地提醒中心、订阅授权、发送状态 | 不保证无限后台通知 |
| 专注 | 时间戳计时、沉浸界面、会话记录 | 不因计时结束自动完成事项 |
| 共同专注 | 固定公开房、邀请房、成员状态、克制聊天 | 云端失败不影响个人专注;消息和权限由服务端校验 |
| 统计 | 固定口径聚合和七日趋势 | 不做复杂分析 |
| 管理端 | V1 五项轻量运营能力 | 独立鉴权与审计 |
- 用户选择的“日期”存为
YYYY-MM-DD语义字段,避免纯日期因 UTC 转换偏移。 - 有时间事项本地把带
Z/明确偏移的输入规范化为 UTC ISO 8601 字符串,同时保存 IANA 时区(可取得时)与该时间点的utcOffsetMinutes;上云时再映射为 CloudBase 时间类型。 - “今天”“本周”“逾期”和重复实例按用户当前有效时区计算;默认使用设备时区,变更时区的规则需在 Stage 4/8 验证。
- 不把格式化显示字符串当作排序或同步依据。
- 服务端审计时间使用可信时间戳。
提醒记录与事项分离,以支持多个提醒、发送状态和小程序内通知。流程为:用户配置 → 明确动作请求订阅授权 → 保存授权结果/提醒计划 → 平台允许时由服务端调度发送 → 写入通知/发送结果 → 小程序内提醒中心展示。
风险与限制:微信模板、授权形态、触发和发送规则可能变化;Stage 9 必须查验当时官方能力并用真实测试账号验证。订阅拒绝、模板不可用或发送失败时,小程序内提醒中心是降级方案,但小程序关闭期间不承诺实时弹出。
V1 需要区分“重复规则模板”和“用户实际完成/修改的实例”。采用预生成实例、按需投影或混合方案会影响查询、编辑和提醒,Stage 8 前必须完成小规模原型和决策记录。无论方案如何:
- 规则可表达 PRD 规定的频率、间隔、星期和结束条件。
- 单次修改和完成不会不可逆破坏规则。
- 生成过程幂等,同一规则/日期没有重复实例。
- 软删除和规则修改不会复活旧实例。
管理后台独立于小程序发布,调用受保护的管理端接口。管理员身份不从普通客户端参数推断;最小权限、操作审计、二次确认和生产环境隔离在 Stage 14 落地。V1 不引入复杂运营工作流。
- UI 明确区分校验错误、离线、权限不足、版本冲突和服务异常。
- 云端返回稳定机器错误码与安全的人类可读信息;详细堆栈只进受控日志。
- 写入失败保留本地数据和重试入口;共享冲突不静默覆盖。
- 关键指标包括同步成功/失败、云函数错误、提醒发送结果和共享权限拒绝。
- 日志避免记录备注全文、身份令牌或其他不必要个人数据。
建议至少区分开发、测试、生产环境。Sprint 3 已提供空值模板和部署指南;当前开发电脑使用被 Git 忽略的本地配置连接开发环境,rixuApi、共同专注集合、最小复合索引和内容安全权限已于 2026-08-26 完成部署与单账号冒烟验证,双账号流程仍需真机复验。真实 AppID、AppSecret、CloudBase 环境 ID 和管理密钥继续使用开发者工具私有配置或平台环境变量,不写入可提交源码。
生产部署必须有显式目标确认,脚本不得默认指向生产环境。
- 领域单元测试:类型推导、日期边界、排序、重复、统计。
- 仓库测试:本地读写、迁移、软删除、同步队列。
- 云函数测试:身份、输入、权限矩阵、幂等、版本冲突。
- 契约测试:小程序/云函数的数据结构与错误码。
- 端到端测试:游客闭环、开启同步、共享邀请、提醒降级、专注恢复。
- 真机测试:手势、订阅消息、分享、音频、后台/前台切换和网络异常。
每阶段具体测试方式见 TASKS.md。
以下不是需求变更,而是必须在实现前验证的技术点:微信基础库最低版本、TypeScript 构建方式、本地存储方案、重复实例策略、订阅消息模板能力、共同专注的内容安全与轮询成本、管理后台框架。状态与决定统一记录在 DECISIONS.md。