Files
lehuo/docs/11_DECISIONS.md
T
2026-08-12 14:35:12 +08:00

8.4 KiB

11 架构与产品决策日志(ADR 简化版)

Codex 不得删除已批准决策。新决策追加。

D-001 产品架构

状态:ACCEPTED

决定: V1 使用“微信小程序 + Web 管理后台 + 单体 API + MySQL + Redis + 对象存储”。

原因: 低复杂度、易部署、易维护、适合当前组织规模。


D-002 后端架构

状态:ACCEPTED

决定: 模块化单体,不采用微服务。

触发重新评估条件:

  • 单体部署已形成明确性能瓶颈;
  • 团队规模显著扩大;
  • 某模块有独立扩容/安全隔离的客观需求。

D-003 主技术语言

状态:ACCEPTED

决定: 优先 TypeScript 全栈。

原因: 前后端共享类型、Codex 开发一致性高、维护人员切换成本较低。


D-004 数据库

状态:ACCEPTED

决定: MySQL 作为业务事实数据库。 Redis 不保存不可恢复的唯一业务事实。


D-005 文件

状态:ACCEPTED

决定: 生产文件进入私有对象存储,通过授权临时访问;不持久化在应用容器本地盘。


D-006 V1 范围

状态:ACCEPTED

决定: V1 首先实现服务闭环、志愿者、项目活动、内容和基础统计,不做复杂 AI、地图、公开募捐、自研 IM。


D-007 隐私

状态:ACCEPTED

决定: 个人敏感信息采取最小收集、最小权限、默认脱敏、操作审计;儿童相关信息更严格。


D-008 UI

状态:ACCEPTED

决定: 优先普通群众与适老使用,不追求炫技。


D-009 数据访问与迁移

状态:ACCEPTED

决定: Phase 1 使用 Prisma 7 与 MariaDB/MySQL driver adapter 访问 MySQL;所有 schema 变化通过 Prisma migration SQL 提交,运行时不使用自动同步。

原因: 保持类型安全、显式迁移和模块化单体的一致性,并确保 CI 与部署可重复执行 migration。


D-010 认证与会话

状态:ACCEPTED

决定: 微信登录通过可替换 Adapter 调用官方 code exchange;后台密码使用 Argon2id。会话采用数据库支持的随机 opaque token,数据库只保存带 pepper 的 HMAC。小程序使用短期 Bearer token,后台使用 HttpOnly、SameSite=Strict Cookie 并校验 Origin。

原因: V1 不引入额外身份供应商或复杂 token 基础设施,同时支持主动注销、轮换、停用和审计,且避免前端持久化后台凭据。


D-011 服务申请隐私与幂等

状态:ACCEPTED

决定: Phase 2 使用 PRD 已批准的六类服务分类和本人/代申请方式。申请敏感字段采用 AES-256-GCM,查询索引及规范化请求指纹采用域隔离 HMAC-SHA-256;以申请人和客户端请求 UUID 的数据库唯一约束实现幂等。隐私同意、申请、初始状态历史和审计必须事务一致。

原因: 在不提前引入 Phase 3 档案和工单的前提下,确保弱网重试不会重复建单,并降低数据库泄露后联系方式、需求内容或可枚举请求摘要被还原的风险。


D-012 Phase 3 接单、记录与文件一致性

状态:ACCEPTED

决定: 申请接单采用数据库事务和客户端 UUID 幂等:从评估状态建立或关联服务对象、写监护授权、生成工单、指派人员、追加申请/工单历史及审计必须原子完成。已有档案只允许在手机号 HMAC 精确匹配且工作人员明确确认后关联。服务记录创建幂等,修改使用 revision 乐观锁并保存加密修订快照。文件通过 S3 兼容 Adapter 进入私有对象存储,后端只签发短期 URL 并在确认时校验元数据。

Phase 3 只开放 ASSIGNED → IN_SERVICE;完成服务与自动回访保持为 Phase 4 的事务边界,禁止提前拆开实现。

原因: 避免弱网重试重复建档/建单、同名误合并、并发覆盖服务记录、对象存储永久公开,以及在回访能力尚未实现时产生无法闭环的“已完成”工单。


D-013 Phase 4 回访与继续帮扶一致性

状态:ACCEPTED

决定: 完成服务要求至少一条服务记录,并由工单负责人以客户端 UUID 幂等提交。工单完成、申请进入待回访、状态历史、自动回访和审计必须原子提交。回访结果为“需要继续帮扶”时,不直接生成工单;必须先取得新的显式授权,创建保留原申请和来源回访关系的 SUBMITTED 新申请,再沿用既有评估/受理流程。新授权、新申请、回访完成、原申请关闭、历史和审计必须在同一事务提交。

人工和重点关爱回访使用同一任务模型;重点关爱回访仅适用于已标记对象。回访正文加密,列表最小化,志愿者不获得回访权限;普通工作人员只访问本人负责任务,具有全量资源权限的管理角色可查看工作台全量范围。

原因: 防止服务完成但回访丢失、弱网重试产生重复任务、继续帮扶覆盖原历史或绕过评估受理,同时降低回访结论和脆弱人群信息泄露风险。


D-014 Phase 5 志愿参与状态机与服务事实

状态:ACCEPTED

决定: 项目采用 V1 最小状态 DRAFT → PUBLISHED → COMPLETED;活动采用已批准的 DRAFT/PUBLISHED/REGISTRATION_CLOSED/IN_PROGRESS/COMPLETED/CANCELLED;报名采用 PENDING/APPROVED/REJECTED/CANCELLED,签到独立采用 NOT_CHECKED_IN/CHECKED_IN/ABSENT。每个业务状态变化均以客户端 UUID 幂等,在同一事务写条件更新、不可变历史和审计。

只有 APPROVED 志愿者可报名。报名事务锁定活动事实后校验发布状态、截止时间、容量和 (activity,user) 唯一约束。服务分钟只从已通过、已签到且活动已完成的报名记录产生,保留修改前后历史;志愿者修改姓名或手机号后必须重新审核并暂时撤销志愿者角色。

项目类别复用既有服务分类;项目可归集既有服务工单,但无全量权限的负责人不能转移其他项目工单。项目/活动封面复用 S3 兼容对象存储 Adapter 和短时 URL;外部存储未配置时允许无封面业务继续运行,但不得伪造上传或公开地址。Phase 5 不创建通知、证书、培训、团队、复杂排班或公开募捐能力。

原因: 保持 V1 范围最小且与既有服务事实一致,防止并发超额、重复报名、资格绕过、不可追溯服务时长和跨项目资源转移,同时把外部对象存储配置与核心志愿参与链路解耦。


D-015 Phase 6 内容审核与可靠通知

状态:ACCEPTED

决定: V1 五类内容共用 DRAFT → PENDING_REVIEW → PUBLISHED → ARCHIVED 状态机;退回进入草稿,已发布内容只能归档,不允许编辑后直接公开。所有变更使用 UUID 幂等、revision 乐观锁、不可变历史和审计。服务故事发布必须显式确认隐私脱敏,正文仅按纯文本/Markdown 保存,不执行用户 HTML。

站内消息与外部投递分离。业务状态变化在原数据库事务创建幂等站内消息;微信订阅通知仅在用户明确开启偏好后创建数据库投递任务,由 Adapter 异步发送并指数退避,最多五次后进入死信。微信失败不得回滚主业务,未配置平台参数时明确不可用。

原因: 避免草稿或未经隐私审核的个案内容泄露,保证通知事实不因外部平台故障丢失,并维持外部供应商与业务服务解耦。


D-016 Phase 7 统计、导出与发布工程

状态:ACCEPTED

决定: V1 统计直接从业务事实表计算,不建立可人工覆盖的统计表。统计查询最多 366 天;导出仅提供汇总且默认不包含个人字段,使用独立权限并记录审计。运行面提供 liveness、数据库 readiness 和受保护的请求量/5xx/p95/资源指标。

生产发布使用不可变镜像标签,固定执行 staging、migration/备份预检、冒烟、UAT、生产备份、部署、健康检查、观察与按上一镜像回滚。MySQL 每日备份、SHA-256 校验、异地留存和季度独立库恢复演练。真实 staging、UAT、微信联调和生产发布必须等待对应环境、资质与人工审批,不以本地检查冒充。

原因: 确保公益服务数据口径可追溯,降低导出泄露面,并把可自动验证的生产工程与必须由真实环境/责任人完成的发布审批清晰分开。


待确认建议

Codex 若有会改变已批准架构的建议,只能写在这里,不得直接实施:

[PENDING]
日期:
建议:
原因:
收益:
风险:
迁移成本:
需要用户确认: