# 开发决策记录

本文档记录开发过程中由 Codex 先行拍板的实现决定。后续如果业务方有不同意见，可以按条目调整。

## 已确认决策

### D001 PDF 生成策略

- 决定：PDF 由服务端生成，不在用户浏览器生成。
- 策略：按需生成 + 30 天缓存。
- 原因：证书属于正式凭证，服务端生成更利于统一版式、审计、作废状态控制和后续模板替换。

### D002 证书编号格式

- 决定：`EPX{YYYY}-{项目代码}-{7位短码}-{2位校验码}`。
- 示例：`EPX2026-DBY-K7M9Q2R-83`。
- 说明：内部数据库继续使用 `BIGINT` 主键，对外编号使用短码和校验码，降低枚举和手输错误风险。

### D003 公开查询方式

- 决定：一期公开查询支持两种方式：证书编号 + 姓名 + 图形验证码，或手机号 + 短信验证码。
- 说明：短信验证码查询无需姓名验证，通过手机号匹配可能返回多张证书，查询结果按列表展示。
- 风控：短信验证码有效期5分钟，同一手机号5分钟内最多发送3次，同一IP每分钟最多发送5次，查询接口保留基础频率限制。

### D004 作废证书 PDF

- 决定：一期作废证书不允许下载正常 PDF。
- 展示：查询页、直达页、二维码核验页统一显示作废状态。

### D005 证书模板

- 决定：一期先使用占位模板。
- 要求：模板文件与业务逻辑分离，后续替换 Logo、公章、排版、正式文案时不改核心业务代码。

### D006 更正和补发

- 决定：一期不实现复杂更正/补发流程。
- 一期保留证书编辑、作废、重置链接等主链路操作。

## Codex 先行决定

### C001 项目代码维护方式

- 决定：后台提供项目/课程代码配置，导入 Excel 使用项目代码匹配，不允许随意生成编号中的项目代码。
- 原因：避免同一项目出现多个拼写，确保证书编号稳定。

### C002 一期部署方式

- 决定：单机 Docker Compose 部署。
- 组件：Nginx、前端静态资源、FastAPI 后端、MySQL。
- 原因：符合低并发和快速上线目标，减少运维复杂度。

### C003 文件目录

- 决定：使用 `/data/certificate-system/uploads`、`error-reports`、`exports`、`pdf-cache`。
- 原因：便于备份策略区分；PDF 缓存不进入长期备份。

### C004 初始化管理员

- 决定：通过初始化命令创建第一个系统管理员。
- 原因：避免在公开页面暴露注册入口。

### C005 访问 token 一期存储

- 决定：一期在 `certificate_access_tokens` 中同时保存 token 哈希和 token 原值。
- 原因：后台导出必须能生成证书对外直达链接；纯哈希无法反推出链接。
- 风险：数据库泄露时 token 原值可被直接使用。
- 后续优化：将 token 原值改为应用层加密存储，或仅在生成时写入导出文件并提供链接重置机制。

### C006 验证码和限流

- 决定：一期使用后端进程内存保存图形验证码和简单 IP 频率计数。
- 原因：一期单机部署，不引入 Redis，满足快速上线和低并发目标。
- 后续优化：访问量上来后迁移到 Redis，并把限流前移到 Nginx 或网关层。

### C007 后台 UI 风格

- 决定：后台采用浅色、清爽、偏办公工具的视觉风格。
- 原因：这个系统是日常管理工具，用户需要快速查找和处理数据，不适合厚重的深色或营销化页面。

### C008 后台 PDF 下载方式

- 决定：后台证书 PDF 下载使用带 Authorization token 的 blob 请求。
- 原因：受保护的后台接口不能依赖普通链接跳转，否则浏览器不会带前端保存的登录 token，容易导致下载失败。

### C009 Excel 导入字段

- 决定：导入模板保留姓名、手机号、项目代码、课程开始日期、课程结束日期、发证日期。
- 原因：证书名称、课程名称、阶段名称、发证单位属于项目配置，不应该在每一行 Excel 里重复填写。
- 备注：导入备注字段一期移除；批量导入后的备注缺少明确展示和业务使用场景。

### C010 导入批次文件管理

- 决定：后台保留导入批次列表，并支持下载原始 Excel、下载错误报告、删除批次记录。
- 删除范围：只删除上传文件、错误报告和批次记录，不删除已经确认导入后生成的学员和证书。
- 原因：导入文件会占用服务器存储，但导入后的业务数据应作为正式数据继续保留。
### C011 Certificate Rendering Template

- Decision: keep `certificate-template-lab` as a standalone visual tuning page.
- Decision: production PDF rendering now uses the approved certificate image as the base layer, then renders learner/course/date/certificate number dynamically with Pillow.
- Current behavior: the rendered course date range uses the separate course start/end dates; legacy certificates without them fall back to `issue_date`.
- Current limitation: mentor, issuer names, and QR area remain from the approved base image for visual consistency.

### C012 PDF 批量预生成

- 决定：后台提供批量预生成 PDF 能力，可按导入批次生成，也可在证书管理中筛选和勾选后生成。
- 策略：预生成继续使用系统设置里的 `PDF 生成并发限制`，不单独开无限制后台任务。
- 原因：大批量学员毕业前先生成缓存，可以降低公开查询高峰的等待时间和 CPU 压力。
- 当前限制：任务进度保存在后端进程内，服务重启后只保留已生成的 PDF 缓存，不恢复历史进度。

### C013 课程起止日期独立建模

- 决定：证书增加“课程开始日期”和“课程结束日期”两个独立字段，不再用发证日期代替课程时间。
- 原因：课程学习周期与证书签发日期含义不同，拆分后可校验时间顺序，也便于后续按开班、结业周期统计。
- 导入约定：两个课程日期与发证日期均必填，模板统一推荐 `YYYY-MM-DD`，例如 `2026-06-01`；课程结束日期不得早于开始日期。
- 兼容策略：历史证书没有课程日期时，展示和 PDF 继续回退到发证日期，避免升级后旧证书无法查看或下载。
