Files
lehuo/docs/11_DECISIONS.md

18 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、微信联调和生产发布必须等待对应环境、资质与人工审批,不以本地检查冒充。

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


D-017 参考界面还原与延期能力扩展边界

状态:ACCEPTED

决定: 小程序端和管理端以 2026-08-10 已确认参考图作为当前视觉基准,在不改变 V1 业务状态机、权限、数据口径和隐私边界的前提下完整还原其信息层级、色彩、卡片、导航、表格、图表和详情结构。界面统计只使用现有事实接口,不展示虚构人数、金额、满意度或成果。

爱心捐赠及微信/支付宝/银行卡支付、每月自动捐赠、捐赠记录与金额统计、物资订单、志愿者培训、证书和团队不进入当前版本,不注册页面或可操作入口,不新增数据库/API;通过集中能力清单、稳定模块边界和可扩展导航结构预留后续接入点,后续启用仍须独立产品、权限、审计、支付合规和迁移评审。

原因: 满足已批准视觉目标,同时避免用不可用入口或模拟数据冒充已交付功能,并防止支付、订单和志愿者扩展模块未经合规及架构评审进入 V1。


D-018 财务公开采用受限富文本文档发布

状态:ACCEPTED

决定: 财务公开作为独立文档发布模块,由管理人员在本地完成文档后导入管理后台、调整基础富文本排版、预览并直接发布,小程序只读查看。状态采用 DRAFT → PUBLISHED → ARCHIVED;已发布正文禁止覆盖编辑,创建、发布和归档必须幂等并写不可变状态历史及审计。

DOCX 仅在管理端浏览器转换,API 保存前对富文本执行服务端白名单清洗。首版不保存原 Word 文件,不建设结构化财务核算、复杂审核、在线募捐、支付、捐赠订单、银行流水同步或金额统计。

原因: 满足机构以现有本地文档进行公开的真实工作方式,降低录入成本,并通过双层富文本清洗、后端权限和发布状态隔离保证公众展示安全;同时避免把轻量公开需求扩大成财务或支付系统。


D-019 旧物善用采用独立域并分三阶段交付

状态:ACCEPTED

决定:用户于 2026-08-15 明确批准在 D-017 原延期边界之外新增“旧物善用”独立模块,但继续排除现金捐赠、微信/支付宝/银行卡支付和自动扣款。模块沿用现有账号、登录态和安全基线,新增独立路由、后台菜单、权限、API 和数据表,不修改既有服务申请与公益项目状态机。

交付分三阶段:第一阶段实现真实旧物捐赠提交、最多六张私有图片、上门取件或定点投递、后台人工审核与人员分配边界、状态溯源、物资需求发布与定向捐赠、真实事实统计;第二阶段实现入库/分拣/消毒/出库/帮扶落地台账及 Excel 导入导出打印;第三阶段实现电子证书和公益积分。第一阶段不接第三方物流,取件人员由后台人工安排;未实施能力只保留模块边界,不显示可操作伪入口。

捐赠状态为 SUBMITTED → WAITING_PICKUP/WAITING_DROP_OFF → RECEIVED → PROCESSING → COMPLETED,并允许审核驳回与接收前取消。需求状态为 DRAFT → PUBLISHED → FULFILLED/CLOSED。所有创建和状态变更使用客户端 UUID 幂等、revision 乐观锁、不可变历史和事务审计。联系人、手机号、取件地址、物品描述、驳回原因加密保存;手机号另存域隔离 HMAC;公开资源使用随机 UUID 和非连续公益编号。统计只从已接收/已完成事实计算,不允许人工覆盖。

原因:满足用户新增的实物公益闭环,同时把物流、仓储台账、证书积分与支付等风险不同的能力拆开交付,避免以空页面、模拟数据或未审计状态冒充完成。


D-020 旧物台账采用原子批量入库与只追加流转事件

状态:ACCEPTED

决定:旧物善用 Phase 2 使用一条物资批次主记录配合只追加流转事件,状态固定为 IN_STOCK → SORTED → DISINFECTED → OUTBOUND → DELIVERED,禁止跳级、回退和覆盖历史。来源捐赠最多绑定一个物资批次;关联台账在分拣和落地时同步捐赠处理/完成事实,定向需求满足数量只在落地事务中增加。

Excel 在管理端浏览器本地读取和生成,API 只接收固定结构、最多 500 行的已解析数据,不保存或执行原始工作簿。导入批次、全部物资行、入库事件和审计全有或全无;导出使用独立权限、最多 5000 条且排除个人字段和内部备注。小程序只公开状态、时间及出库/落地的粗粒度地区。

原因:防止部分导入、重复入库、越级完成、覆盖审计事实和表格携带宏/公式进入服务端,同时使物资去向可追溯但不扩大受助对象隐私暴露面。


D-021 旧物善用积分仅作为公益荣誉记录

状态:ACCEPTED

决定:Phase 3 的积分只记录旧物捐赠参与,不具有现金价值,不支持兑换、购买、转让、提现、到期或排行。默认每件物资 1 分、单笔最多 500 分;管理端可激活不可变规则版本,按四类物资配置未来计分,但不得覆盖既有流水。自动计分只发生在捐赠真实进入 COMPLETED 时,并与证书、状态历史和审计在同一事务提交;证书补发不得重复计分。人工校正必须填写原因、校验 revision、写只追加流水且不得产生负余额。

证书使用纯文本模板和固定占位符,每次签发保存完整快照。新模板只影响未来证书;补发会撤销但不删除旧证书。证书收件人称呼加密保存,仅本人会话可查看,不提供匿名公开核验。法律声明固定明确证书仅记录公益参与,不是捐赠收据、税务凭证或财产权利证明。既有已完成捐赠不自动批量回填,必要时由授权人员依据真实记录补发并审计。

原因:在满足公益激励与纪念需求的同时,避免积分被误解为支付、资产或消费体系,防止规则变更重写历史,并保证完成事实、荣誉凭证和积分账目一致可追溯。


D-022 公众端使用“帮扶服务”名称并收敛首页入口

状态:ACCEPTED

决定:按用户 2026-08-15 的明确要求,小程序公众端将“服务申请”统一展示为“帮扶服务”,提交内容称为“帮扶需求”;首页移除关爱老人、困境儿童、妇女帮扶和残障救助四个重复分类快捷按钮,改为一个高权重帮扶服务主入口与志愿服务、公益项目、公益动态、旧物善用四个次级入口。六类具体服务仍在帮扶服务选择页完整保留。

本次仅调整公众信息架构、视觉资源和用户文案。为保持已上线契约兼容,内部 ServiceApplication 数据模型、API 路径、共享枚举、后台业务名称和状态机不改名。新增四张虚构 AI 场景图只用于分类识别,不构成真实公益服务或成果证明。

原因:首页分类入口与下级选择重复且同级入口过多;“帮扶服务”更直接表达群众可以获得的服务,同时避免因纯展示改名引入数据库/API 迁移风险。


D-023 首页主功能区补充财务公开与服务进度

状态:ACCEPTED

决定:按用户 2026-08-15 的明确要求,小程序首页“帮扶服务”下方主功能区由四项调整为 3×2 六宫格,保留志愿服务、公益项目、公益动态和旧物善用,并加入财务公开与服务进度。财务公开进入既有公开文档列表,服务进度进入本人帮扶服务列表,不新增占位页或新业务接口。

服务进度从下方便捷入口上移,避免同页重复;便捷入口取消旧物善用,仅保留公益活动,以及具备授权时显示的协作任务。旧物善用模块本身及首页主功能入口继续保留,不删除既有业务能力。

原因:提高财务透明内容和个人服务跟进的首页可达性,同时减少便捷入口重复和冗余;3×2 布局可保持适老化点击面积与清晰文字标签。


D-024 Ubuntu 26.04 初期生产采用可迁移单机 Compose 基线

状态:ACCEPTED

决定:为用户已准备的 Ubuntu 26.04 LTS 云服务器提供独立 Linux 部署包。初期拓扑在单机 Docker Compose 内运行 Nginx/管理后台、模块化单体 API、MySQL 8.4、Redis 7.4 和私有 MinIO,仅向宿主机发布 80/443;管理后台/API 与签名文件服务使用两个不同的合法 HTTPS 域名。数据、对象和缓存使用独立持久卷,生产环境文件以 600 权限保存在 /etc/lehuo,数据库每日备份并生成 SHA-256。

API 与管理后台镜像使用批准的 Git commit 作为不可变标签并可本机构建;固定执行预检、构建、迁移前备份、migration、部署、HTTPS/readiness 冒烟和观察。应用回滚只允许切换到本机存在的上一不可变镜像,不自动回滚数据库。自建 MinIO 继续通过既有 S3 Adapter 接入,未来可替换为腾讯 COS 或其他 S3 兼容私有存储,不改变业务服务。

该基线是可部署交付,不代表生产发布条件已满足。真实域名/备案/TLS、微信配置、staging、UAT、告警接收、异地加密备份、恢复演练和发布审批仍按 D-016 阻断;单机部署不扩展为 Kubernetes、微服务或多租户架构。

原因:在不改变批准技术栈和 V1 边界的前提下,为现有 Ubuntu 云服务器提供可复现、可备份、可回滚的最低复杂度部署路径,并保持未来迁移到托管 MySQL/Redis/COS 的能力。


待确认建议

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

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