34 KiB
项目全貌与开发指南
文档定位:
X:\work\yx(包名email-forecast)后续开发的唯一权威参考(系统核心知识库)。
产品名:邮件自动预报系统
形态:Next.js 运营 Web + IMAP Worker + MySQL,不是桌面助手、不是客服渠道。
代码根目录:X:\work\yx
文档版本:v1.0
编写日期:2026-08-18
对照材料:README.md、docs/邮箱项目.md、docx/PRD/技术设计、当前源码。
密钥纪律:只写配置键名与路径,不写 IMAP 授权码、CC 密码、OAuth secret 的具体值。
与 carriercentral-ai-assistant 的关系:yx 是运营后台(拉邮件 → 解析 → 人工确认 → 写 CarrierCentral SaaS);助手仓是桌面查询 Demo(读仓)。两边打同一套海外仓 HTTP,但进程、UI、状态机完全独立。把 yx 能力迁到助手仓时,以本文模块名为地图,禁止把 Worker 的 SaveContainer 禁令或 IMAP 轮询直接塞进 Tkinter。
0. 阅读路径与权威优先级
材料冲突时,优先级从高到低:
- 正在运行的代码:
src/app/**、src/services/**、src/worker/index.ts、prisma/schema.prisma - 行为契约:
docx/需求规格-邮件自动预报-v0.2.md、docx/接口/carriercentral客户端通用接口V1.73.md - 协作入口:
docs/邮箱项目.md(红线、排障、上市清单) - 指令规则:
docx/产品规则-邮件指令识别与拆分.md、docs/邮件指令提取规则.md - 速查:
README.md
改识别规则先改 docx/产品规则-* 再改 src/services/parse/。改 CC 路径以 V1.73 为准;文档外 endpoint 默认 mock。
1. 技术栈与全局依赖
1.1 系统形态
三进程,无独立消息队列、无 Redis、无 GraphQL。
| 组件 | 本仓库状态 |
|---|---|
| 前端 | Next.js 15 App Router + React 18 + Ant Design 5 |
| 后端 | 同进程:src/app/api/**/route.ts(Route Handlers) |
| Worker | 独立进程 src/worker/index.ts(tsx 跑源码) |
| ORM | Prisma 5 + mysql2 |
| 数据库 | MySQL 8(库名 email_forecast,宿主机端口 7023) |
| 缓存 | 无 Redis。CC token 在表 cc_token_cache;会话在 Cookie |
| 消息队列 | 无。补偿/自动执行靠 Worker 轮询 import_compensation.next_retry_at |
| 状态管理 | 无 Redux/Pinia。客户端 useState + hooks(useMails / useMailDetail)+ AuthSessionProvider |
| 包管理 | 仅 pnpm(不要 npm/yarn) |
1.2 关键三方库(package.json)
| 包 | 用途 |
|---|---|
next ^15.1.3 |
App Router、API、生产 next start -p 3100 |
react / react-dom 18 |
UI |
antd ^5.22 + @ant-design/icons + cssinjs + React19 patch |
运营后台组件库 |
@prisma/client / prisma |
schema → MySQL |
mysql2 |
Prisma 驱动;db-lock.ts 独立连接做 GET_LOCK |
iron-session |
Cookie 会话 email_forecast_session |
imapflow |
IMAP 拉取 / IDLE |
mailparser |
解析 eml |
exceljs |
装箱清单 / 转仓表 xlsx |
adm-zip |
zip 解一层 |
tesseract.js |
OCR_PROVIDER=local 启发式 OCR |
undici |
CC HTTP(src/services/cc/http.ts) |
zod |
env 与 API 入参校验 |
pino |
结构化日志 src/lib/logger.ts |
uuid |
幂等 key、补偿 retry_token、trace |
@tanstack/react-virtual |
确认页大表 ShipmentVirtualTable |
开发:vitest、@playwright/test、tsx、typescript。Lint:next lint(无独立 ESLint/Prettier 配置文件)。
1.3 本仓库明确没有的技术
- Vue / 桌面 Tkinter / LangChain Agent
- Redis / Kafka / RabbitMQ
- 独立 Tenant 表 / 一租户多登录用户(当前为一租户一
AppUser.role=customer账号) - SMTP 自动回信、真实大模型指令识别(词表/规则)
2. 目录结构详解
组织逻辑:按运行时分层 + 按业务域分子目录。
页面/API(src/app)
→ hooks / components(UI)
→ services(imap / parse / import / cc)
→ lib(env / session / prisma 辅助)
→ prisma + MySQL
Worker(src/worker)直接调同一套 services,禁止走 ConfirmImport 写新增预报。
2.1 仓库根
| 路径 | 职责 |
|---|---|
package.json |
脚本与依赖;name: email-forecast |
pnpm-lock.yaml |
锁文件 |
docker-compose.yml |
mysql + web + worker |
Dockerfile |
多阶段 Node 20 bookworm;web 入口 docker-entrypoint-web.sh |
next.config.ts |
Antd transpile、关 dev 指示标、client 禁 async_hooks |
tsconfig.json |
@/* → ./src/*,strict |
tsconfig.worker.json |
Worker 编译(compose 实际用 tsx 跑源码) |
vitest.config.ts |
单测 + coverage |
playwright.config.ts |
e2e,baseURL 3100 |
playwright.test-mode.config.ts |
TEST_MODE 端口 3110 |
.env.example |
环境变量模板 |
prisma/schema.prisma |
全表 |
prisma/seed.ts |
仅账号 + 部分 write capability |
scripts/ |
启动栈、IMAP/CC 烟雾、retention、样例导入 |
fixtures/emails/ |
01–16 金样例(spec + expected) |
docx/ |
PRD / 技术设计 / UI / 韧性 / 金样例 JSON / CC 接口 |
docs/ |
协作文档 + 本知识库 + Windows 一键脚本 |
data/ |
运行时 eml/附件/日志(gitignore);compose 挂载 ./data |
tests/unit tests/integration tests/e2e |
测试 |
2.2 src/app/ — 页面与 API
| 路径 | 职责 |
|---|---|
layout.tsx |
根:lang=zh-CN、AntdProvider、标题「邮件自动预报」 |
globals.css |
全局样式 |
error.tsx |
错误页 |
page.tsx |
/ → redirect("/mails") |
(auth)/login/page.tsx |
登录 |
(ops)/layout.tsx |
AuthGuard + TopNav + main.ops-main |
(ops)/mails/page.tsx |
邮件列表 |
(ops)/mails/[id]/page.tsx |
详情 |
(ops)/mails/[id]/confirm/page.tsx |
确认导入(按 instruction 分发视图) |
(ops)/logs/page.tsx |
导入日志 / 拉取记录 |
(ops)/settings/page.tsx |
Admin 设置 |
(ops)/test-workbench/page.tsx |
TEST_MODE=1 测试台 |
api/**/route.ts |
42 个 Route Handler(见第 6 节) |
无 middleware.ts。鉴权在各 route 的 requireSession / requireAdmin,页面靠客户端 AuthSessionProvider。
2.3 src/services/ — 业务核
| 目录 | 职责 |
|---|---|
imap/ |
poller.ts 轮询;idle-supervisor.ts IDLE;snapshot.ts 落 eml;pull-filter.ts 过滤;mailbox-config.ts 多箱;stale-reaper.ts 超时回收;runtime-status.ts 连续失败 |
parse/ |
pipeline.ts 主解析;classify.ts 类型;mail-intent.ts 意图文案;packing-list.ts 装箱表;split-instructions.ts 指令拆分;extract-container-header.ts 柜头 |
import/ |
confirm.ts ConfirmImport(唯一新增预报 SaveContainer);compensation.ts 补偿;auto-exec.ts 指令自动写;confirm-do-upload.ts / confirm-batch-transfer.ts / confirm-work-order.ts |
cc/ |
http.ts auth.ts save-container.ts do-upload.ts conflict.ts shipping-line.ts mock-* write-capability.ts settings-config.ts |
ocr/ |
index.ts local/aliyun/off;sanitize.ts |
oauth/ |
providers.ts Google/MS;state HMAC |
customer/ |
resolve-sender.ts 发件人→客户编码 |
alert/ |
webhook.ts 企微/飞书 JSON POST |
retention/ |
cleanup.ts |
test/ |
TEST_MODE 工作台、fixture 评测 |
db.ts |
Prisma 单例 |
db-lock.ts |
独立 mysql2 连接 GET_LOCK / RELEASE_LOCK |
state-machine.ts |
assertTransition / resolveAfterParse |
audit.ts |
审计日志 |
2.4 src/lib/ src/components/ src/hooks/ src/constants/ src/types/
| 路径 | 职责 |
|---|---|
lib/env.ts |
Zod 校验环境;生产弱密钥拒绝启动 |
lib/session.ts |
iron-session;verifyCredentials 读 .env 账号,不读 app_user 表 |
lib/api.ts |
ok/fail、requireSession、requireAdmin、BigInt JSON |
lib/safe-path.ts |
附件/快照必须在 data/ 下 |
lib/cc-api-base.ts |
CC api_base 主机白名单(防 SSRF) |
lib/rate-limit.ts |
进程内滑动窗口 |
lib/secret-crypto.ts |
AES-GCM,密钥派生自 SESSION_SECRET |
lib/logger.ts |
pino |
lib/mail-trace.ts |
单封 traceId |
lib/test-mode.ts / test-mode-guard.ts |
TEST_MODE;测试 API 非测试环境返回 404 |
components/TopNav.tsx |
顶栏:邮件 / 日志 / 设置(admin) / 测试台 |
components/MailTable.tsx |
列表 |
components/confirm/* |
确认页:预报 / DO / 转仓 / 工单 |
components/Cc*.tsx |
对照 CC 表单的运营控件 |
hooks/useMails.ts useMailDetail.ts useImport.ts useCcUiMode.ts |
客户端数据 |
constants/ui-copy.ts error-copy.ts |
中文文案 |
types/mail.ts |
MailType / MailStatus / 柜头货件字段 |
2.5 src/worker/index.ts
常驻循环 scheduleLoop:
ImapPoller.tick()staleReaper.run()(PARSING/IMPORTING超时打回)drainFetchedMails(10)消化FETCHEDprocessDueAutoExec(10)processDueCompensations(10)
启动:ImapPoller.clearStaleLock()、ensureCcWriteCapabilities()、IdleSupervisor.start()。
吞掉 IMAP ETIMEOUT 的 uncaughtException,避免整进程退出。
3. 入口与启动流程
3.1 入口一览
| 入口 | 实质 |
|---|---|
pnpm dev / dev:stack |
scripts/dev-stack.mjs:同时 next dev -p 3100 + tsx src/worker/index.ts |
pnpm dev:web |
仅 Web,不拉信 |
pnpm worker |
仅 Worker |
pnpm start |
生产 Next :3100 |
.\docs\start-system.ps1 |
Windows:mysql 容器 + 本机 web/worker(默认不拉 node 镜像) |
pnpm compose:up |
docker 三件套 |
prisma/seed.ts |
账号种子 |
无 FastAPI app.include_router。Next 按文件系统挂载路由。
3.2 Web 启动全过程
pnpm dev:web
→ Next 加载 next.config.ts
→ 首次请求触发 getEnv()(读 process.env,Zod)
→ prisma 单例连 DATABASE_URL
→ App Router:
/ → redirect /mails
(ops)/* → 客户端 AuthSessionProvider 调 GET /api/auth/me
未登录 → /login
→ API:各 route 内 requireSession / requireAdmin
没有全局中间件链。登录:POST /api/auth/login → verifyCredentials(env)→ session.save()。
Docker web:scripts/docker-entrypoint-web.sh(SEED_ON_START 时可 db push + seed)→ pnpm start。compose 把 DATABASE_URL 改成 mysql://app:app@mysql:3306/email_forecast。
注意:Dockerfile EXPOSE 3000,实际监听 3100(package.json start)。
3.3 Worker 启动全过程
tsx src/worker/index.ts
→ getEnv()
→ clearStaleLock(imap_poll)
→ ensureCcWriteCapabilities() upsert 写能力行
→ IdleSupervisor.start() 各箱 IDLE(IMAP_IDLE_ENABLED 且账号 idle_enabled)
→ scheduleLoop:
tick → 按 imap_settings.pollIntervalMs(否则 POLL_INTERVAL_MS)+ 连续失败退避
数据库:compose mysql healthcheck 通过后 web/worker 才起。本地需先 docker compose up -d mysql + pnpm exec prisma db push。
4. 核心模块与依赖关系
4.1 模块划分
| 模块 | 关键文件 | 职责 |
|---|---|---|
| 认证 | lib/session.ts api/auth/* |
admin/ops Cookie |
| 邮箱绑定 | imap/mailbox-config.ts api/settings/mailboxes* oauth/* |
多箱、授权码/OAuth |
| IMAP 拉取 | imap/poller.ts pull-filter.ts snapshot.ts |
入库 mail_message |
| 解析 | parse/pipeline.ts classify.ts |
类型 + 柜头 + 货件 + 指令 |
| 状态机 | state-machine.ts |
合法迁移 |
| 确认导入 | import/confirm.ts |
预报 SaveContainer(仅 web) |
| 指令写 CC | confirm-do-upload / confirm-batch-transfer / confirm-work-order / auto-exec |
DO/转仓/工单 |
| CC HTTP | cc/http.ts auth.ts |
customerLogin + 写接口 |
| 冲突锁 | cc/conflict.ts container_active_lock |
同柜互斥 |
| 补偿 | import/compensation.ts |
TIMEOUT_UNKNOWN 重试 ≤3 |
| 设置 | api/settings/* |
IMAP 间隔、过滤、CC、OAuth |
| 可观测 | api/health alert/webhook.ts imap-pull-log |
健康与告警 |
| TEST | api/test/* TestWorkbench |
仅 TEST_MODE |
调用方向(禁止反向):
页面 → hooks/client-api → Route Handler → services.* → prisma / CC / IMAP
Worker → services.imap|parse|import.compensation|auto-exec
Worker ──X── ConfirmImport.execute(新增预报)
4.2 公共层
| 能力 | 文件 |
|---|---|
API 信封 {ok, data} / {ok:false, error} |
lib/api.ts |
| 限流 | lib/rate-limit.ts |
| 命名锁 | db-lock.ts(禁止 Prisma 池直接 GET_LOCK) |
| 路径安全 | safe-path.ts |
| 审计 | audit.ts |
| 中文错误 | constants/error-copy.ts formatOperatorError |
4.3 页面层级与组件树
RootLayout (AntdProvider)
/login 无 TopNav
OpsLayout (AuthGuard + TopNav)
/mails MailTable + 筛选
/mails/[id] 详情:摘要、附件、重新解析、改类型(admin)
/mails/[id]/confirm 按 instruction_id:
ForecastConfirmView
DoUploadConfirmView
TransferConfirmView
WorkOrderConfirmView
InstructionConfirmPicker
/logs 导入 + ImapPullLogsPanel
/settings Admin:ImapPullSettingsCard、邮箱、CcSettingsCard、OauthSettingsCard
/test-workbench TestWorkbench(TEST_MODE)
顶栏 TopNav:ImapStatusBanner + 菜单 + 角色 Tag + 登出。
确认页 confirm/page.tsx:extractInstructionsFromMail → resolveConfirmInstruction;无 instruction_id 时 router.replace(confirmHref(...))。
5. 数据模型与数据流
5.1 表结构(prisma/schema.prisma → MySQL)
mailbox_account 1──* mail_message 1──* mail_attachment
│
├──1 parse_result(柜头/货件 JSON)
└──* container_import 1──* import_compensation
container_active_lock(柜号主键,跨邮件互斥)
cc_token_cache / cc_settings / cc_write_capability / oauth_settings
imap_settings / imap_filter_rule / imap_pull_log
sender_customer_map
app_user ← seed 写入;登录当前不查此表
MailboxAccount mailbox_account:IMAP 主机/端口/用户;password_enc AES-GCM;auth_type PASSWORD|OAUTH;OAuth 令牌密文与过期;idle_enabled;username 唯一。
MailMessage mail_message:幂等 message_id、(mailbox, folder, imap_uid)、raw_hash;mail_type;status;type_evidence JSON;snapshot_path;trace_id;version(乐观锁,导入用);正文/OCR/线程字段。
MailAttachment:sha256、path(相对 data/)、rejected、template_id。
ParseResult:container_header / shipments / lineage JSON,与 types/mail.ts 的 ContainerHeader、ShipmentRow 对齐。
ContainerImport:每次向 CC 提交一柜;shipments_hash 参与唯一键 uk_mail_container_shiphash;external_id 为 CC 返回 id。
ImportCompensation:reason、retry_count/max_retry、next_retry_at、retry_token UUID。
CcSettings id=1:mock、api_base、saas、login_mark、username、password_enc。优先于 .env。
CcWriteCapability:save_container / do_upload / transfer / batch_transfer / hold_split / label / customer_message,mode live|mock|off。
ImapSettings id=1:poll_interval_ms 默认 1800000(30min),合法 3min~7d。
ImapFilterRule:kind + value(关键词/白名单/黑名单等)。
ImapPullLog:每封拉取结果,最多约 1000 条。
AppUser:username + sha256 password_hash + role。现行登录走 env,此表不参与鉴权(隐式债务)。
5.2 邮件状态机(state-machine.ts)
| from | 允许 to |
|---|---|
| FETCHED | PARSING, IGNORED |
| PARSING | PENDING_CONFIRM, PARSED, PARSE_FAILED, REJECTED_VALIDATION, AUTO_EXECUTING, FETCHED |
| PARSED | PARSING, PENDING_CONFIRM, AUTO_EXECUTING, IGNORED |
| PENDING_CONFIRM | IMPORTING, PARSING, IGNORED, PENDING_CONFIRM |
| IMPORTING | SUCCESS, PARTIAL_SUCCESS, FAILED |
| AUTO_EXECUTING | SUCCESS, FAILED, PARSED |
| SUCCESS | (终态) |
| PARTIAL_SUCCESS | IMPORTING |
| FAILED | IMPORTING, PENDING_CONFIRM, PARSING, AUTO_EXECUTING, IGNORED |
| PARSE_FAILED | PARSING, IGNORED |
| REJECTED_VALIDATION | PARSING, IGNORED, PENDING_CONFIRM |
| IGNORED | (终态) |
非法迁移抛 IllegalTransition。IMPORTING/SUCCESS/PARTIAL_SUCCESS 禁止重新解析。
resolveAfterParse:NEW_CONTAINER 且有效货件≥1 → PENDING_CONFIRM;WORK_ORDER/DO_UPLOAD/TRANSFER → PENDING_CONFIRM;autoExecEligible → AUTO_EXECUTING;否则 PARSED。
5.3 邮件类型 MailType(types/mail.ts)
NEW_CONTAINER | TRANSFER | DO_UPLOAD | INSTRUCTION_HOLD_SPLIT | INSTRUCTION_LABEL | WORK_ORDER | UNKNOWN。
分类在 classify.ts(主题/正文/附件名打分),不是 LLM。
5.4 主数据流
IMAP UID
→ pull-filter(黑名单拒绝 → 白名单必拉 → 关键词;皆空则 isBusinessRelevantMail)
→ snapshot 落 data/ + Prisma upsert(幂等)
→ 成功后 IMAP \Seen(失败且未入库则不标已读)
→ status FETCHED
→ ParsePipeline(Worker drain 或拉取后立即 parse)
→ parse_result + PENDING_CONFIRM / PARSED / ...
→ 运营打开 /mails/[id]/confirm
→ POST /api/mails/:id/import
ConfirmImport.execute
version 校验 → 柜冲突检查 → 状态 IMPORTING
→ saveContainer(CC 或 mock)
→ SUCCESS / 补偿 TIMEOUT_UNKNOWN
→ 浏览器列表刷新
前端:useMails → GET /api/mails;详情 useMailDetail → GET /api/mails/:id。无全局 store。
导入状态 ImportStatus:PENDING|IMPORTING|SUCCESS|FAILED|CONFLICT|TIMEOUT_UNKNOWN。
6. API 接口与页面路由
统一响应:{ ok: true, data } 或 { ok: false, error: { code, message, details? } }。BigInt 转字符串。
权限:public / session(admin|ops)/ admin。/api/test/* 另需 TEST_MODE 否则 404。
6.1 认证与健康
| 方法 | 路径 | 权限 | 说明 |
|---|---|---|---|
| POST | /api/auth/login |
public | 限流 10/分/IP+用户名;写 Cookie |
| POST | /api/auth/logout |
Cookie | 销毁会话 |
| GET | /api/auth/me |
session | 当前用户 |
| GET | /api/health |
public | mysql、IMAP 告警、cc_mode/cc_live_ready、test_mode;勿塞密钥 |
6.2 邮件
| 方法 | 路径 | 权限 | 说明 |
|---|---|---|---|
| GET | /api/mails |
session | 分页筛选 mail_type/status/content_kind/q/sort |
| DELETE | /api/mails |
session | ids 或 oldest 批量删 |
| GET | /api/mails/[id] |
session | 详情+解析+附件 |
| POST | /api/mails/[id]/import |
session | ConfirmImport;10/分/用户;force_skip_conflict 仅 admin |
| POST | /api/mails/[id]/reparse |
session | 重新解析 |
| POST | /api/mails/[id]/type |
admin | ENABLE_TYPE_OVERRIDE;改 mail_type |
| POST | /api/mails/[id]/ignore |
admin | → IGNORED |
| GET | /api/mails/[id]/attachments/[attId]/download |
session | resolveDataFile 读盘 |
| POST | /api/mails/[id]/ocr-attachments |
session | 触发附件 OCR |
| POST | /api/mails/[id]/do/confirm |
session | DO 确认上传 |
| POST | /api/mails/[id]/transfer/confirm |
session | 转仓确认 |
| GET | /api/mails/[id]/transfer/gate |
session | 转仓门禁 |
| POST | /api/mails/[id]/work-order/confirm |
session | 工单确认 |
6.3 导入 / 补偿 / 船司 / 拉取日志
| 方法 | 路径 | 权限 | 说明 |
|---|---|---|---|
| GET | /api/imports |
session | 导入日志 |
| POST | /api/imports/[importId]/retry |
session;部分 admin | 失败重试 |
| POST | /api/compensations/[id]/retry |
session / admin 分支 | 补偿重试 |
| GET | /api/shipping-lines |
session | CC GetShippingLineList |
| GET | /api/imap-pull-logs |
session | 拉取记录 |
| POST | /api/admin/imap/poll |
admin | 手动拉一轮,锁等待 20s |
6.4 设置(均 admin)
| 方法 | 路径 | 说明 |
|---|---|---|
| GET/PUT | /api/settings/imap |
拉取间隔 |
| GET/POST | /api/settings/imap/filters |
过滤规则 |
| PATCH/DELETE | /api/settings/imap/filters/[id] |
单条规则 |
| GET/POST | /api/settings/mailboxes |
邮箱列表/新增 |
| PATCH/DELETE | /api/settings/mailboxes/[id] |
改/删 |
| POST | /api/settings/mailboxes/[id]/test |
测连通 |
| POST | /api/settings/mailboxes/probe |
探测 |
| GET/PUT | /api/settings/cc |
CC 连接(api_base 白名单) |
| POST | /api/settings/cc/test |
CC 测试 |
| GET/PUT | /api/settings/oauth |
OAuth 应用凭证 |
6.5 OAuth
| 方法 | 路径 | 权限 | 说明 |
|---|---|---|---|
| GET | /api/oauth/providers |
(见源码) | 提供商是否配置 |
| GET/POST | /api/oauth/start |
admin | 发起绑定,带 HMAC state |
| GET | /api/oauth/callback/[provider] |
无登录 Cookie | 靠 state.actor;写 mailbox 后重定向 /settings?oauth= |
6.6 TEST_MODE(requireTestMode)
/api/test/faults inject pipeline fixtures reset run-all target-requests:注入样例、切故障、跑流水线。生产必须 TEST_MODE=false。
6.7 页面路由
| 路径 | 文件 | 说明 |
|---|---|---|
/ |
app/page.tsx |
跳转 /mails |
/login |
(auth)/login/page.tsx |
登录 |
/mails |
(ops)/mails/page.tsx |
列表 |
/mails/[id] |
(ops)/mails/[id]/page.tsx |
详情 |
/mails/[id]/confirm |
confirm/page.tsx |
确认;query instruction_id |
/logs |
(ops)/logs/page.tsx |
日志 |
/settings |
(ops)/settings/page.tsx |
Admin |
/test-workbench |
(ops)/test-workbench/page.tsx |
测试台 |
7. 关键业务流程
7.1 登录
POST /api/auth/login{username, password}checkSlidingWindow→verifyCredentials比对APP_ADMIN_*/APP_OPS_*- iron-session 写入
username+role - 客户端进
(ops),AuthSessionProvider调/api/auth/me
状态:未登录只能 /login;Cookie HttpOnly,生产 secure。
7.2 IMAP 拉取 → 解析(Worker)
Idle 或 定时 tick
→ withMysqlNamedLock("imap_poll")
→ resolveEnabledMailboxes(DB 优先,否则 .env IMAP_*)
→ SEARCH SINCE lookback(默认 3 天,已读+未读)
→ 每 UID:decidePull → 跳过则 stub + pull_log
→ FETCH + parseMailSource + saveMailSnapshot
→ upsert mail_message(P2002 幂等)
→ ParsePipeline / 或 FETCHED 待 drain
→ markSeen
关键函数:ImapPoller.tick、pollMailbox、ParsePipeline(pipeline.ts)、drainFetchedMails。
7.3 新增预报确认导入(核心交易)
运营:有效货件 + PENDING_CONFIRM
→ /mails/[id]/confirm → ForecastConfirmView
→ POST /api/mails/:id/import
version + idempotency_key + F_TransMode + F_OperationType
+ container_header 白名单 + selected_row_indexes
→ ConfirmImport.execute(import/confirm.ts)
assertTransition → IMPORTING
checkContainerConflict / acquireContainerActiveLock
buildSaveContainerEntity → saveContainer
live: ccAuth + POST /Container/SaveContainer
mock: mockRegisterContainer
成功 SUCCESS + external_id
超时 TIMEOUT_UNKNOWN → import_compensation
Worker 禁止走这条 SaveContainer。门禁:forecast-confirm-gate.ts / canEnterForecastConfirm。
渠道未映射须 ack_unmapped_channels。强制跳冲突:Admin + ENABLE_FORCE_IMPORT + 审计。
7.4 DO / 转仓 / 工单
确认页按 mail_type 与 instruction_id 分发:
- DO →
confirm-do-upload.ts→cc/do-upload.ts(SaveFieldValue+/learun/adms/annexes/upload) - 转仓 →
confirm-batch-transfer.ts/batch-transfer.ts(默认 capability mock) - 工单 →
confirm-work-order.ts
自动执行:auto-exec.ts + ENABLE_AUTO_EXEC_*;新增预报仍以人工确认为准。
7.5 补偿与超时回收
IMPORTING超过IMPORTING_TIMEOUT_MS(默认 180s)→ reaper 打回PARSING超过PARSING_STALE_MS(默认 600s)→ 回 FETCHED- 补偿 due:
processDueCompensations最多COMPENSATION_MAX_RETRY=3 - IMAP 连续失败 ≥3 且配置了
ALERT_WEBHOOK_URL→ POST JSON(15 分钟去重)
8. 配置与环境管理
8.1 切换方式
无独立 config/dev.yml。环境 = .env + 设置页 DB(IMAP/CC/OAuth DB 优先于 env)。
| 场景 | 做法 |
|---|---|
| 本地 | .env 指向 localhost:7023;CC_MOCK=true |
| Docker | compose 覆盖 DATABASE_URL=@mysql:3306 |
| 生产 | NODE_ENV=production + 强密钥;弱口令 拒绝启动(env.ts enforceProductionSecrets) |
| TEST | TEST_MODE=1;与 CC_MOCK 正交 |
换 SESSION_SECRET:全员登出,且 邮箱密码密文无法解密,须重新保存。
8.2 环境变量(getEnv / .env.example)
| 键 | 含义 |
|---|---|
DATABASE_URL |
Prisma MySQL |
POLL_INTERVAL_MS |
Worker 间隔回退;设置页优先 |
DATA_RETENTION_DAYS |
保留天数;Worker 不再自动删,用 pnpm retention:cleanup |
ENABLE_TYPE_OVERRIDE |
Admin 改邮件类型 |
ENABLE_FORCE_IMPORT |
强制跳过柜冲突 |
ENABLE_ISO_CHECK |
柜号 ISO6346 |
IMAP_* |
主机/端口/用户/超时/lookback/每轮上限/IDLE/代理 |
CC_API_BASE CC_SAAS_HEADER CC_LOGIN_MARK |
SaaS |
CC_USERNAME CC_PASSWORD_PLAIN CC_PASSWORD_MD5 |
回退凭证 |
CC_MOCK |
全局写 mock |
CC_HTTP_TIMEOUT_MS CC_READ_TIMEOUT_MS |
写/读超时 |
APP_ADMIN_* APP_OPS_* |
实际登录账号 |
SESSION_SECRET |
≥32;会话 + AES 派生 |
TEST_MODE |
Mock IMAP/CC 故障注入 |
ALERT_WEBHOOK_URL |
告警 |
OCR_PROVIDER |
off|local|aliyun |
OAUTH_* |
Gmail/MS 应用 |
ENABLE_AUTO_EXEC_* AUTO_EXEC_REQUIRE_CONFIRM |
自动执行开关 |
SHIPMENT_ROW_SOFT/HARD_LIMIT ATTACHMENT_MAX_BYTES |
清单/附件上限 |
8.3 端口约定(红线)
Web 3100;MySQL 对外 7023。不要改成 3306/3000 当本地默认。
9. 构建、测试与部署
9.1 脚本(package.json)
| 脚本 | 作用 |
|---|---|
dev / dev:stack |
web+worker |
dev:web / dev:turbo |
仅 Next |
build |
next build + tsc -p tsconfig.worker.json |
start |
next start -p 3100 |
worker |
tsx src/worker/index.ts |
lint |
next lint |
test / test:coverage / test:all |
vitest |
test:e2e / test:e2e:test-mode |
Playwright |
db:* |
generate / migrate / seed / push |
cc:smoke imap:smoke imap:poll |
联调 |
compose:* |
docker |
retention:cleanup |
清旧数据 |
postinstall |
prisma generate |
9.2 日常命令
pnpm install
cp .env.example .env
docker compose up -d mysql
pnpm exec prisma db push
pnpm db:seed
pnpm dev # http://localhost:3100 admin/admin123
pnpm test
pnpm test:e2e # 需已起服务或交给 webServer 拉 pnpm dev
pnpm build && pnpm start
Windows:.\docs\start-system.ps1(-Mode compose 需 Docker Hub)。
9.3 部署要点
- 必须 web + worker 都活;worker 建议单副本(全局
imap_poll锁) - 持久化:MySQL 卷 +
./data(eml、附件、imap-runtime.json) - 生产:
pnpm db:migrate(migrate deploy),不要用弱口令 seed 覆盖生产 - 健康检查:
GET /api/health - compose worker 命令必须是
tsx src/worker/index.ts(dist 里@/别名会 MODULE_NOT_FOUND)
测试目录:src/**/*.test.ts、tests/unit、tests/integration/pipeline-fixtures.test.ts、tests/e2e。覆盖率排除 src/app 与 src/components。
10. 代码规范与约定
来源:docs/邮箱项目.md §4–5、源码实践。
| 项 | 约定 |
|---|---|
| 语言 | TypeScript strict;API 入参 Zod |
| 命名 | 文件 kebab-case;函数 camelCase;Prisma model PascalCase;表 snake_case |
| 路径别名 | @/ = src/ |
| API | ok/fail;错误码大写下划线(UNAUTHORIZED、VERSION_CONFLICT) |
| 日志 | pino 对象字段 + 短消息(worker.imap_tick);禁止打完整密码/token |
| UI 文案 | src/constants/ui-copy.ts;中文 |
| CC 柜头覆盖 | 仅 pickContainerHeaderPatch 白名单 |
| 读盘 | 只走 resolveDataFile / assertUnderRoot |
| 锁 | 只走 db-lock.ts |
| 状态 | 只走 assertTransition |
| 包管理 | 仅 pnpm |
| 提交 | 用户明确要求才 git commit |
| Lint | next lint;无 Prettier/Ruff 工程配置 |
| 破坏性变更 | 先改 docx 再改代码 |
角色:ops 列表/详情/确认/重解析/下载;admin 另含改类型、设置、立即拉取、忽略、FORCE 导入。
11. 现有问题与注意事项
源码几乎无 TODO/FIXME/HACK 标记。已知项来自 README、协作文档与代码对照。
| ID | 说明 |
|---|---|
| AUTH-TABLE | app_user 被 seed,登录却只认 env。改密码易误改库表无效 |
| MOCK≠TEST | CC_MOCK 或设置页 Mock 的成功 不算 上线验收 |
| CAP-MOCK | transfer/hold_split/label/customer_message 默认 mock,V1.73 无正式表 |
| OCR-LOCAL | OCR_PROVIDER=local 是启发式,不是商用 OCR;aliyun 路径未当正式 SDK 宣传 |
| RATE-LIMIT | 限流是进程内 Map,多副本不共享 |
| LOCK | Prisma 池 GET_LOCK 会泄漏 → 永久 lock_busy;用 scripts/clear-imap-lock.ts |
| UNIT-DRIFT | README:mail-intent / extract-header / four-business / work-order-dedupe 与 expected 不完全一致 |
| COMP-SCOPE | 工单/DO/转仓 5xx 未统一进预报补偿表,靠详情一键重试 |
| DOCKER-PORT | Dockerfile EXPOSE 3000 vs 应用 3100 |
| HEALTH-PUBLIC | /api/health 无鉴权(docker 依赖) |
| FILTER | 关键字未命中 IGNORED/SKIP_FILTER 不进列表;UNKNOWN 仍可能 PARSED 供人工处理 |
| PARTIAL-CC | CC 对象有 data 但仍可能 TIMEOUT_UNKNOWN 进补偿(缺 external id) |
高耦合:ParsePipeline 体量大(分类+xlsx+zip+OCR+船司匹配+指令拆分);ConfirmImport 与 CC 字段、状态机、锁强绑定。
风险区:SESSION_SECRET 兼会话与邮箱密文;worker 多开抢锁;附件路径穿越;CC api_base SSRF(已有白名单)。
红线(事故级,见 docs/邮箱项目.md §5):Worker 禁止新增预报 SaveContainer;不绕过状态机;不用 Prisma 池 GET_LOCK;mock 不当验收;zip 只解一层;冲突默认阻断;先落库再 \Seen;端口 7023/3100;密钥不入库。
12. 外部集成
12.1 CarrierCentral SaaS(核心)
| 项 | 位置 |
|---|---|
| 基址 | CC_API_BASE 或 cc_settings.api_base(默认 https://test.saas.carriercentral.vip/api) |
| 头 | Saas、登录 token(cc/auth.ts customerLogin) |
| 契约 | docx/接口/carriercentral客户端通用接口V1.73.md |
| 登录 | customerLogin;token 缓存 cc_token_cache |
| 预报写入 | POST /Container/SaveContainer(save-container.ts) |
| 船司 | GET GetShippingLineList(shipping-line.ts) |
| DO | SaveFieldValue + /learun/adms/annexes/upload |
| Mock | cc/mock-target-api.ts mock-state.ts;设置页或 CC_MOCK |
| 主机约束 | lib/cc-api-base.ts:*.carriercentral.vip / localhost / 与 env 同主机 |
CC 密码协议为 MD5;设置页明文则服务端哈希。Token 可能出现在 GET query,日志须脱敏。
12.2 IMAP 邮箱
| 项 | 位置 |
|---|---|
| 协议 | IMAPS 993(预设 lib/imap-presets.ts:QQ/163/Gmail/Outlook) |
| 客户端 | imapflow(imap/client.ts) |
| 凭证 | 设置页 AES 密文,或 .env IMAP_USER/PASS |
| OAuth | Google / Microsoft(oauth/providers.ts);回调写 mailbox_account |
| 代理 | IMAP_PROXY;默认仅 Gmail/Outlook 主机走代理 |
12.3 OCR
OCR_PROVIDER:off | local(tesseract.js)| aliyun(endpoint/ak 环境变量)。解析辅助,不是主识别。
12.4 告警 Webhook
ALERT_WEBHOOK_URL:IMAP 连续失败或 CC 鉴权失败 POST JSON。空则只打日志。实现 services/alert/webhook.ts。
12.5 未集成
支付、短信、邮件 SMTP 发送、对象存储 SDK、LangChain、微信客服。附件只落本地 data/。
附录 A. 限制常量
| 常量 | 默认 | 位置 |
|---|---|---|
| 拉取间隔 | 30min(设置页) | imap_settings / POLL_INTERVAL_MS |
| lookback | 3 天 | IMAP_LOOKBACK_DAYS |
| 每箱每轮最多新信 | 100 | IMAP_MAX_FETCH_PER_TICK |
| 解析过期 | 600s | PARSING_STALE_MS |
| 导入超时 | 180s | IMPORTING_TIMEOUT_MS |
| 补偿次数 | 3 | COMPENSATION_MAX_RETRY |
| 货件行软/硬上限 | 1000 / 5000 | env |
| 附件 | 20MiB | ATTACHMENT_MAX_BYTES |
| 登录/导入限流 | 10/分钟 | login / import route |
| pull_log | ~1000 | imap/pull-log.ts |
| 间隔合法范围 | 3min~7d | 设置页 IMAP |
附录 B. 与助手仓对照(迁移功能时用)
| 维度 | yx(本文) | carriercentral-ai-assistant |
|---|---|---|
| 形态 | Web 运营后台 | Windows Tkinter Demo |
| 主路径 | IMAP → 解析 → 确认 → SaveContainer | 自然语言 → ReAct → GET intelligent |
| CC 写 | 有(确认后) | 查询为主,附件下载 |
| 状态机 | 邮件 12 态 | 无邮件态 |
| 鉴权 | admin/ops Cookie | 无登录 |
| 禁止 | Worker 写新增预报 | 改 kf-ai、伪装客服 |
迁功能原则:复用 CC 契约与字段名(F_ContainerNo、SaveContainer),不要把 Next/Prisma/IMAP 整包塞进助手;UI 保持助手现有 ui_main.py 分层,除非用户确认换栈。
附录 C. 后续开发引用协议
之后任何需求(含「把 yx 功能加到助手仓」):
- 先引用本文的模块路径与函数名,再给修改方案。
- 遵循现有分层、Zod 入参、状态机、pnpm、中文
ui-copy。 - 未经明确允许,不引入 Redis/新 ORM/第二套前端框架。
- Worker 与 ConfirmImport 职责拆分不破。
- 行为变更同步
docx与本文对应章节;协作入口docs/邮箱项目.md红线保持。