邮箱项目(邮件自动预报)
给接手同事看的入口文档。细则以 docx/ 下 PRD / 技术设计 / 韧性为准;冲突时以 PRD 为准。
更新日期:2026-08-04(补充开发注意点 + 上市/生产安全清单)
1. 项目信息
| 项 |
说明 |
| 名称 |
邮件自动预报系统(repo: email-forecast) |
| 目标 |
从绑定邮箱 IMAP(多箱/IDLE/OAuth |
| 不做(一期) |
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 上市注意点。
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 日常开发
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 一键三件套
pnpm compose:up # mysql + web + worker
pnpm compose:ps
pnpm compose:logs
Windows 一键启停(先杀旧进程/端口再启动,防地址占用):
# 默认 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 数据库
pnpm db:generate
pnpm db:push
pnpm db:migrate:dev # 开发迁移
pnpm db:migrate # 部署 migrate deploy
pnpm db:seed
2.4 IMAP / CC / 运维
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 测试与构建
pnpm test # vitest(含安全路径 tests/unit/security-hardening.test.ts)
pnpm test:watch
pnpm test:e2e # playwright(需先起服务)
pnpm lint
pnpm build
2.6 默认环境要点
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 邮件主路径
- Worker 按 可配间隔(默认 30min,设置页 3min~7d)轮询;Admin「立即拉取」可手动
- Lookback:
SINCE IMAP_LOOKBACK_DAYS(默认 3 天,已读+未读)
- 过滤:黑名单拒绝 → 发件人白名单必拉 → 关键词命中才拉;三列表皆空则回退业务相关兜底
- 幂等:
message_id / folder+uid / raw_hash;入库后标 \Seen;每封写 imap_pull_log(最多 1000)
ParsePipeline:分类 → 解 xlsx/csv/zip → 柜头/货件
- 预报类有效货件 ≥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. 编码规范
- 语言:TypeScript strict;API 入参用
zod;日志用 pino。
- 改动范围:只改任务相关文件;禁止顺手大重构、无关格式化。
- 兼容:保持现有状态机、API 契约、Prisma 字段语义;破坏性变更先改
docx 再改代码。
- UI:运营后台沿用 Antd 5;确认页大表用 TanStack Virtual;中文文案集中
src/constants/ui-copy.ts。
- Diff 优先:补丁级修改;删除代码要确认无引用。
- 测试:状态机 / 分类 / 解析 / 锁 / 安全路径变更需补或更新
tests/unit;关键路径跑 pnpm test。
- 密钥:不提交真实 IMAP 授权码、CC 密码;用设置页或本地
.env(已 gitignore)。
- 包管理:只用
pnpm;compose worker 用 tsx 跑源码(避免 dist 里 @/ 别名未重写)。
- 提交:不擅自
git commit / push;用户明确要求再建提交。
- 文档:改行为后同步本文件或对应
docx;Cursor 进度记在 implementation-plan.mdc(短摘要 + Done)。
- 读附件/快照路径:统一
src/lib/safe-path.ts(resolveDataFile / assertUnderRoot),禁止 path.startsWith(dataRoot) 自行拼装。
- 导入柜头覆盖:客户端字段须进
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 测试建议(改完要跑)
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 业务上线闸门(建议顺序)
- 配置:生产 env + 强密钥 +
CC_MOCK=false
- 库:
pnpm db:migrate(或等价 migrate deploy);不要用生产库跑 seed 默认弱口令覆盖
- IMAP:设置页绑定业务邮箱并「测试连接」;确认 lookback/filter 符合运营预期
- CC live:
pnpm cc:smoke -- --mode=live 或设置页「测试连接」
- 抽样邮件:真拉一封预报类 → 确认导入一柜(低风险测试数据)→ 查 CC 柜与审计
- 补偿与冲突:人为失败/冲突场景各看一次
/logs
- 监控:worker 连续失败告警、
imap_sync_alert、磁盘水位、MySQL 连接
- 回滚预案:保留上一镜像 + 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 + 书面确认。