# 邮箱项目(邮件自动预报) > 给接手同事看的入口文档。细则以 `docx/` 下 PRD / 技术设计 / 韧性为准;冲突时以 PRD 为准。 > **更新日期**:2026-08-04(补充开发注意点 + 上市/生产安全清单) --- ## 1. 项目信息 | 项 | 说明 | |---|---| | 名称 | 邮件自动预报系统(repo: `email-forecast`) | | 目标 | 从绑定邮箱 IMAP(多箱/IDLE/OAuth|授权码)拉取 → 解析/分类 → **NEW 人工确认 SaveContainer**;DO/转仓/工单等走对应确认或指令写能力 | | **不做**(一期) | AI 客服、SMTP 自动回信客户、与 CC 双向全量同步、真实大模型识别(指令拆分为词表/规则) | | 栈 | Next.js 15 App Router · Prisma 5 · MySQL 8 · Ant Design 5 · TypeScript · pnpm | | 进程 | `web`(UI+API+ConfirmImport)· `worker`(IMAP+解析+reaper+补偿+retention)· `mysql` | | 本地入口 | http://localhost:3100 | | 默认账号(**仅本地开发**) | `admin` / `admin123`(Admin);`ops` / `ops123`(运营) | | MySQL 映射 | 宿主机 **7023** → 容器 3306(避让本机 3306) | | 包管理 | **仅 pnpm**(不要 npm/yarn) | > ⚠️ 生产环境 **禁止** 使用默认口令与默认 `SESSION_SECRET`;`NODE_ENV=production` 时弱口令会 **拒绝启动**。详见 [§10 上市注意点](#10-上市生产注意点)。 ### 1.1 路由 | 路径 | 说明 | |---|---| | `/login` | 登录 | | `/mails` | 邮件列表 | | `/mails/[id]` | 详情(重新解析 / Admin 改类型) | | `/mails/[id]/confirm` | 确认导入(预报类:有效货件 + 可确认状态;含部分 DO 路径) | | `/logs` | 导入日志;拉取记录 | | `/settings` | Admin:自动拉取间隔/过滤、邮箱绑定、CC、OAuth | ### 1.2 设计文档索引 | 文档 | 路径 | |---|---| | 需求规格(PRD) | `docx/需求规格-邮件自动预报-v0.2.md` | | 技术设计 | `docx/技术设计方案-邮件自动预报-v1.0.md` | | UI/UX | `docx/产品UI-UX设计方案-邮件自动预报-v1.0.md` | | 异常与韧性 | `docx/异常场景与系统韧性设计.md` | | 运营 SOP | `docx/运营SOP-邮件自动预报.md` | | 产品规则(指令拆分) | `docx/产品规则-邮件指令识别与拆分.md` | | CC 接口 V1.72 | `docx/接口/carriercentral客户端通用接口V1.72.md` | | 实施计划(Cursor) | `.cursor/rules/implementation-plan.mdc` | | 快速 README | `README.md` | --- ## 2. 常用命令 ### 2.1 日常开发 ```bash pnpm install cp .env.example .env # 首次;DATABASE_URL 指向 localhost:7023;无 CC 账号时 CC_MOCK=true docker compose up -d mysql pnpm exec prisma db push pnpm db:seed # 仅账号 pnpm dev # Web :3100 + Worker(IMAP 自动拉取)一起启 pnpm dev:web # 仅 Web(不拉信) pnpm worker # 仅 Worker(IMAP / 解析 / 回收 / 补偿) ``` ### 2.2 一键三件套 ```bash pnpm compose:up # mysql + web + worker pnpm compose:ps pnpm compose:logs ``` **Windows 一键启停(先杀旧进程/端口再启动,防地址占用):** ```powershell # 默认 local:只起 mysql 容器 + 本机 pnpm dev/worker(不拉 node 镜像,避开 Docker Hub) .\docs\start-system.ps1 .\docs\start-system.cmd # 全量 compose(需能访问 registry-1.docker.io) .\docs\start-system.ps1 -Mode compose .\docs\start-system.ps1 -Mode compose -NoBuild # compose 失败默认会自动 fallback 到 local;禁止回退加 -NoFallback ``` Docker Hub 超时(`registry-1.docker.io` / `node:*-slim`)时用默认 local 即可;Dockerfile 基底已改为本地常见的 `node:20-bookworm`。 ### 2.3 数据库 ```bash pnpm db:generate pnpm db:push pnpm db:migrate:dev # 开发迁移 pnpm db:migrate # 部署 migrate deploy pnpm db:seed ``` ### 2.4 IMAP / CC / 运维 ```bash pnpm imap:smoke # 连通性(.env 回退凭证) pnpm imap:poll # 拉一轮(优先设置页 mailbox_account) pnpm exec tsx scripts/clear-imap-lock.ts # 清残留 imap_poll 锁(lock_busy) pnpm cc:smoke # 跟随当前配置(默认 mock ≠ TEST 验收) pnpm cc:smoke -- --mode=mock pnpm cc:smoke -- --mode=live # 真烟雾:关 Mock + CC 账号 pnpm retention:cleanup pnpm retention:cleanup -- --dry-run ``` ### 2.5 测试与构建 ```bash pnpm test # vitest(含安全路径 tests/unit/security-hardening.test.ts) pnpm test:watch pnpm test:e2e # playwright(需先起服务) pnpm lint pnpm build ``` ### 2.6 默认环境要点 ```env DATABASE_URL=mysql://app:app@localhost:7023/email_forecast CC_MOCK=true POLL_INTERVAL_MS=1800000 IMAP_LOOKBACK_DAYS=3 ENABLE_TYPE_OVERRIDE=true ENABLE_FORCE_IMPORT=false SESSION_SECRET=change-me-to-a-long-random-string-at-least-32-chars # 生产必须换强随机 ≥32 ``` IMAP / CC 凭证:**优先设置页 DB 配置**,无则回退 `.env`。 拉取间隔:**设置 → 自动拉取**(`imap_settings`,默认 30min,3min~7d)优先于 `POLL_INTERVAL_MS`。 --- ## 3. 技术方案(摘要) ### 3.1 职责拆分 ``` imap.qq.com ──► worker(ImapPoller → Snapshot → ParsePipeline) ──► MySQL ▲ 运营浏览器 ──► web(/api + ConfirmImport → CC SaveContainer) ────────┘ ``` | 进程 | 允许 | **禁止** | |---|---|---| | `web` | UI、API、确认导入、调 CC、设置 | 长期 IMAP 轮询主循环(可 Admin 手动拉一轮) | | `worker` | IMAP 拉取、解析、stale reaper、补偿 due、retention、受限自动写(见能力表) | **业务 SaveContainer(新增预报)必须在 web 人工确认后调用** | ### 3.2 邮件主路径 1. Worker 按 **可配间隔**(默认 30min,设置页 3min~7d)轮询;Admin「立即拉取」可手动 2. Lookback:`SINCE IMAP_LOOKBACK_DAYS`(默认 3 天,已读+未读) 3. **过滤**:黑名单拒绝 → 发件人白名单必拉 → 关键词命中才拉;三列表皆空则回退业务相关兜底 4. 幂等:`message_id` / `folder+uid` / `raw_hash`;入库后标 `\Seen`;每封写 `imap_pull_log`(最多 1000) 5. `ParsePipeline`:分类 → 解 xlsx/csv/zip → 柜头/货件 6. 预报类有效货件 ≥1 → `PENDING_CONFIRM` → 确认页 → CC `SaveContainer` ### 3.3 状态机(邮件) 常用:`FETCHED` → `PARSING` → `PARSED` | `PENDING_CONFIRM` | `PARSE_FAILED` | `REJECTED_VALIDATION` 导入:`PENDING_CONFIRM` → `IMPORTING` → `SUCCESS` | `PARTIAL_SUCCESS` | `FAILED` 重新解析禁止:`IMPORTING` | `SUCCESS` | `PARTIAL_SUCCESS` 非法迁移抛 `IllegalTransition`;改状态必须走 `assertTransition`。 ### 3.4 命名锁 - `imap_poll` / `cc_login`:MySQL `GET_LOCK` - **必须**经 `src/services/db-lock.ts` 独立连接 acquire/release(Prisma 连接池会导致锁泄漏 → 永久 `lock_busy`) - Worker 启动会清残留 `imap_poll` 锁 ### 3.5 关键目录 ``` src/app/ # Next App Router(页面 + API) src/components/ # UI src/services/imap/ # 拉取 / lookback / snapshot src/services/parse/ # 分类 / 卡派 / pipeline src/services/import/ # 确认导入 / 补偿 src/services/cc/ # CC HTTP / auth / mock / 写能力 src/lib/ # env / session / safe-path / rate-limit / cc-api-base src/worker/ # 常驻 worker 入口 prisma/ # schema + seed docx/ # 需求与设计(权威) docs/ # 协作入口文档(本文) ``` ### 3.6 CC 写能力(易踩坑) 表 `cc_write_capability`(启动 `ensureCcWriteCapabilities`): | id | 默认 mode | 说明 | |---|---|---| | `save_container` | live | V1.72 有正式接口;确认导入路径 | | `do_upload` | live | `SaveFieldValue` + `annexes/upload`;list 验真接口文档未单列,为客户端同源 | | `transfer` / `batch_transfer` / `hold_split` / `label` / `customer_message` | **mock** | 多数 **不在 V1.72 正式表**;切 live 前必须对方确认 endpoint + 联调 | `CC_MOCK=true` 或设置页 Mock:**所有写视为 mock,不能当 TEST/上线验收**。 --- ## 4. 编码规范 1. **语言**:TypeScript strict;API 入参用 `zod`;日志用 `pino`。 2. **改动范围**:只改任务相关文件;禁止顺手大重构、无关格式化。 3. **兼容**:保持现有状态机、API 契约、Prisma 字段语义;破坏性变更先改 `docx` 再改代码。 4. **UI**:运营后台沿用 Antd 5;确认页大表用 TanStack Virtual;中文文案集中 `src/constants/ui-copy.ts`。 5. **Diff 优先**:补丁级修改;删除代码要确认无引用。 6. **测试**:状态机 / 分类 / 解析 / 锁 / 安全路径变更需补或更新 `tests/unit`;关键路径跑 `pnpm test`。 7. **密钥**:不提交真实 IMAP 授权码、CC 密码;用设置页或本地 `.env`(已 gitignore)。 8. **包管理**:只用 `pnpm`;compose worker 用 `tsx` 跑源码(避免 `dist` 里 `@/` 别名未重写)。 9. **提交**:不擅自 `git commit` / `push`;用户明确要求再建提交。 10. **文档**:改行为后同步本文件或对应 `docx`;Cursor 进度记在 `implementation-plan.mdc`(短摘要 + Done)。 11. **读附件/快照路径**:统一 `src/lib/safe-path.ts`(`resolveDataFile` / `assertUnderRoot`),禁止 `path.startsWith(dataRoot)` 自行拼装。 12. **导入柜头覆盖**:客户端字段须进 `pickContainerHeaderPatch` 白名单(`confirm.ts`),禁止 `z.record` 原样 merge 进 CC。 --- ## 5. 红线(绝对不能违反) | # | 红线 | |---|---| | 1 | **Worker 禁止承担新增预报 SaveContainer**(导入只在 web ConfirmImport) | | 2 | **禁止绕过状态机**非法迁移(含直接改库「修好」状态) | | 3 | **禁止用 Prisma 连接池直接 GET_LOCK/RELEASE_LOCK**(必须用 `db-lock` 同源连接) | | 4 | **mock ≠ TEST 验收**:`CC_MOCK=true` / 设置页 Mock 开着时的「成功」不能当上线门禁 | | 5 | **预报导入走确认页门禁**;指令类/转仓等不可冒充「新增柜」乱走 SaveContainer | | 6 | **幂等不可丢**:同一邮件不得因重拉产生重复业务柜(依赖 message_id / uid / raw_hash) | | 7 | **zip 只解一层**;路径穿越条目丢弃;解压后体积受 `ATTACHMENT_MAX_BYTES` 约束 | | 8 | **冲突柜默认阻断**;强制跳过仅 Admin + `ENABLE_FORCE_IMPORT`,且必须写审计 | | 9 | **先落库再 `\Seen`**;解析失败仍可「重新解析」,但不得在 `IMPORTING/SUCCESS/PARTIAL_SUCCESS` 重解析 | | 10 | **不擅自改端口约定**:本地 MySQL 对外 **7023**;Web **3100** | | 11 | **不把密钥写进仓库**;不在日志打印完整密码/授权码/Session/CC token | | 12 | SMTP/AI 客服仍不做;OCR/IDLE/OAuth/多邮箱等变更须书面确认后改 PRD | | 13 | **生产禁止默认口令与默认 SESSION_SECRET**(启动校验) | | 14 | **附件落盘路径必须在 `data/` 下**;API 下载禁止任意绝对路径读盘 | 违反以上任一条视为事故级改动,须回滚或补审计与文档后再合入。 --- ## 6. 排障速查 | 现象 | 处理 | |---|---| | 跳过拉取 `lock_busy` | `pnpm exec tsx scripts/clear-imap-lock.ts`;重启 worker;确认无多实例互抢 | | 拉取成功列表无信 | 看 Toast「新入库 / 候选」;候选 0 = lookback 内无新信或已入库;点刷新;确认 `pnpm worker` 在跑 | | IMAP 未配置 | Admin → `/settings` 绑定邮箱 + 测试连接 | | 解析失败 | 详情「重新解析」;看 `last_error`;对照韧性文档错误码 | | 不能确认导入 | 状态/类型门禁不满足(见门禁逻辑 `forecast-confirm-gate`);有效货件 ≥1 | | CC 导入失败 | `/logs`;补偿 ≤3;审计 `/logs?tab=audit` | | compose worker 起不来 | 看日志是否 `@/` MODULE_NOT_FOUND;应用 `tsx src/worker/index.ts` | | production 起不来 Invalid environment (production security) | 换强 `SESSION_SECRET` + 非弱 `APP_*_PASS`,admin/ops 口令不可相同 | | 登录 429 | 同 IP+用户名 1 分钟过多失败;稍后再试或换源 IP | | 设置 CC 提示 HOST_NOT_ALLOWED | `api_base` 仅允许 `*.carriercentral.vip` / localhost / 与 env `CC_API_BASE` 同主机(防 SSRF) | | OAuth 回调 `bad_state` | state 15 分钟有效,须从本站 Admin 点「绑定」发起;勿复用旧链接 | --- ## 7. 角色与验收提示 | 角色 | 能力 | |---|---| | ops | 列表/详情/确认导入/重新解析/附件下载 | | admin | 上述 + 改类型、设置、立即拉取、审计、FORCE 导入(开关开启时)、忽略邮件 | **一期验收要点(摘要)** - 列表有 IMAP 真信;详情可确认导入 - 新增预报 + 清单可进确认导入;勾选行与请求体一致 - CC live 烟雾(非 mock)`customerLogin` → SaveContainer - 冲突阻断;失败可重试/补偿;审计可查 - 生产环境可启动(密钥已换)、登录与导入有限流、附件只能在 data 根下 --- ## 8. 开发注意点(日常易踩) ### 8.1 起环境 | 注意 | 说明 | |---|---| | **必须起 worker** | `pnpm dev` 已默认连带 worker;若只用 `pnpm dev:web` 则不拉信、不解析 | | **MySQL 端口 7023** | `DATABASE_URL` 写错成 3306 会连到别的本机实例 | | **CC 无账号先 Mock** | `CC_MOCK=true` 可联调 UI;有 demovip/test 账号再关 Mock | | **多开 worker** | 同库多实例会抢 `imap_poll`(一得锁、一 `lock_busy`);一般只保留一个 worker | | **Windows 路径** | 附件 path 入库用 `/`;读盘一律走 `resolveDataFile`,不要自己 `join` absolute 逃逸 | ### 8.2 业务逻辑 | 注意 | 说明 | |---|---| | **确认门禁** | 以 `canEnterForecastConfirm` / 状态机为准,不是「邮件存在即可点导入」 | | **version / 幂等** | 导入带 `version` + `idempotency_key`;并发第二请求可能 `VERSION_CONFLICT` / 直接返回已成功 | | **渠道未映射** | 有 `CHANNEL_UNMAPPED` 须 `ack_unmapped_channels`,否则拒绝 | | **强制跳冲突** | 须 Admin + `ENABLE_FORCE_IMPORT=true`,默认关 | | **指令识别无 LLM** | `instruction-lexicon` / `split-instructions`;改规则先对齐 `docx/产品规则-*.md` | | **OCR** | `OCR_PROVIDER=local` 为启发式兜底,**不是**商用识别;`aliyun` 现为 pending SDK,不能写「已接阿里云 OCR」上线文案 | | **自动执行** | 新增预报 / DO / 转仓主路径 **人工确认** 优先;`ENABLE_AUTO_EXEC_*` 与 `cc_write_capability` 双重约束;默认 mock endpoint 不等于 CC 真有该接口 | ### 8.3 安全相关(开发也要守) | 注意 | 说明 | |---|---| | 登录限流 | 10 次/分钟/IP+用户名(`/api/auth/login`) | | 导入限流 | 10 次/分钟/登录用户(`/api/mails/:id/import`) | | 限流实现 | 进程内 Map,**多副本不共享**;要严格限流需前置网关 | | 健康检查 | `/api/health` **无鉴权**(docker healthcheck 依赖);勿往 response 里塞密钥 | | 会话 | `iron-session` + Cookie HttpOnly;改 `SESSION_SECRET` 会使所有旧会话失效 | | 邮箱密码 | DB 中 AES-GCM,密钥派生自 `SESSION_SECRET`——**换 SESSION_SECRET 后旧邮箱密文无法解密,需重新保存密码** | | OAuth | Admin 发起;state 含 actor + exp;回调无登录 Cookie 也可完成绑定(依赖 state 签名) | ### 8.4 改 CC / 接口 | 注意 | 说明 | |---|---| | **以 V1.72 为契约底** | `customerLogin` / `GetShippingLineList`(GET) / `SaveContainer` / `SaveFieldValue` / `annexes/upload` | | **文档外 endpoint** | 转仓/工单等默认 mock;live 前写清路径与验收入库 | | **token 可出现在 query** | GET 船司列表等;日志/反向代理勿明文长期存 query log | | **CC 密码** | 协议要求 MD5;设置页传明文则服务端 md5 后存用 | | **改 api_base** | 受主机白名单约束;自定义域名须 `.env` `CC_API_BASE` 同主机才能保存 | ### 8.5 测试建议(改完要跑) ```bash pnpm test # 全量单测 pnpm exec vitest run tests/unit/security-hardening.test.ts pnpm cc:smoke -- --mode=mock # UI 联调 pnpm cc:smoke -- --mode=live # 有真账号时 ``` --- ## 9. 上市 / 生产注意点 上线前把下表当 **检查清单** 勾选;未勾完不算可对客。 ### 9.1 必改环境变量 | 变量 | 要求 | |---|---| | `NODE_ENV` | `production` | | `SESSION_SECRET` | **≥32 位高强度随机**;禁止 example 占位串 | | `APP_ADMIN_PASS` / `APP_OPS_PASS` | 强密码;互不相同;禁止 `admin123`/`ops123`/`password`/`123456` | | `DATABASE_URL` | 生产 MySQL;账号最小权限;勿把开发 7023 写进生产 | | `CC_MOCK` | 正式业务 **false**;且设置页 Mock 关闭 | | `CC_API_BASE` / `CC_SAAS_HEADER` / `CC_LOGIN_MARK` | 生产租户正式地址与 Saas 头(非随意 test 残留) | | `CC_*` 账号 | 生产只读/业务约定账号;轮换策略由运维定 | | `OAUTH_PUBLIC_BASE_URL` | **公网 HTTPS 根**,与 OAuth 控制台回调一致 | | `OAUTH_*_CLIENT_*` | 仅在开启 Gmail/MS 绑定时配置;密钥不入库明文日志 | | `ENABLE_FORCE_IMPORT` | 生产默认 **false**;仅紧急支持场景临时开 | | `ENABLE_AUTO_EXEC_*` | 生产按业务决定;无 live endpoint 前保持关或 capability mock | | `ATTACHMENT_MAX_BYTES` / shipment 上下限 | 按磁盘与 CC 限制校准 | ### 9.2 基础设施 | 项 | 建议 | |---|---| | 进程 | `web` + `worker` 都要存活;worker 挂了只读历史、不进新信 | | 实例数 | IMAP 锁全局一把:**worker 建议单副本**(或明确主备切换) | | 数据盘 | `./data`(或挂载卷)持久化:eml、附件、`imap-runtime.json`;**勿用无状态容器丢盘** | | 备份 | MySQL + `data/mails` 同步备份策略 | | HTTPS | 生产强制 HTTPS;Cookie `secure` 在 production 已开启 | | 反向代理 | 若记 access log,注意 CC token 可能在 query;脱敏或关闭详细 query 日志 | | 健康检查 | 用 `/api/health` 即可;监控 mysql / imap_sync_alert | | 对外暴露 | 运营后台勿裸奔公网无 VPN;登录限流不替代 WAF/零信任 | ### 9.3 安全与合规 | 项 | 说明 | |---|---| | 启动硬校验 | 生产弱密钥 **直接拒绝启动**(见 `src/lib/env.ts`) | | 路径安全 | 下载/解析只读 `data/` 下相对路径;防 `../` 与 `data` 前缀绕过 | | 登录/导入限流 | 单机有效;多副本需 LB 层限流 | | 角色 | 仅 admin/ops 两账号模型;**不是多租户**;泄露一账号即全站业务数据 | | 审计 | 导入成功/失败、FORCE、邮箱绑定、CC 设置、OAuth 绑定须可查 | | 保留期 | `DATA_RETENTION_DAYS` + worker retention;合规要求则加大或关闭清理并外备 | | 第三方邮件 | 授权码/OAuth 属客户邮箱权限;SOP 告知运营勿绑个人箱当生产总线 | ### 9.4 业务上线闸门(建议顺序) 1. **配置**:生产 env + 强密钥 + `CC_MOCK=false` 2. **库**:`pnpm db:migrate`(或等价 migrate deploy);**不要**用生产库跑 seed 默认弱口令覆盖 3. **IMAP**:设置页绑定业务邮箱并「测试连接」;确认 lookback/filter 符合运营预期 4. **CC live**:`pnpm cc:smoke -- --mode=live` 或设置页「测试连接」 5. **抽样邮件**:真拉一封预报类 → 确认导入一柜(低风险测试数据)→ 查 CC 柜与审计 6. **补偿与冲突**:人为失败/冲突场景各看一次 `/logs` 7. **监控**:worker 连续失败告警、`imap_sync_alert`、磁盘水位、MySQL 连接 8. **回滚预案**:保留上一镜像 + DB 备份;知悉换 `SESSION_SECRET` 会登出全员并可能需重配邮箱密码 ### 9.5 明确不要当作上市完成的事项 - 仅 Mock 绿色通过 - 仅 `customerLogin` 成功、未做过 SaveContainer - OCR local 启发式「识别准确」宣传 - 文档外写接口未 live 联调就打开自动执行 - 开发默认口令仍在生产 `.env` - 只部署 web 未部署 worker --- ## 10. 安全加固摘要(实现侧,便于 Code Review) 下列已在代码侧落地,改相关模块时请保持不退化: | 能力 | 位置(参考) | |---|---| | 数据区路径约束 | `src/lib/safe-path.ts` | | CC api_base 主机约束 | `src/lib/cc-api-base.ts` + `api/settings/cc` | | 生产弱密钥拒绝启动 | `src/lib/env.ts` | | 登录/滑动窗口限流 | `src/lib/rate-limit.ts` + `api/auth/login` | | OAuth state HMAC+TTL+actor | `src/services/oauth/providers.ts` | | DO 上传 410 最多 1 次重试 | `src/services/cc/do-upload.ts` | | 柜头字段白名单 merge | `src/services/import/confirm.ts` `pickContainerHeaderPatch` | | Zip 解压体积上限 | `src/services/parse/zip-extract.ts` | | 单测 | `tests/unit/security-hardening.test.ts` | --- ## 11. 联系与变更 - 需求/行为变更:先改 `docx/需求规格-*.md` 与韧性文档,再改代码。 - 协作入口文档:即本文件 `docs/邮箱项目.md`。 - 开发进度:`.cursor/rules/implementation-plan.mdc`。 - CC 路径/字段:以 `docx/接口/carriercentral客户端通用接口V1.72.md` 为准;缺接口时先 mock + 书面确认。