You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
mail-yubao/docs/项目全貌与开发指南.md

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. 阅读路径与权威优先级

材料冲突时,优先级从高到低:

  1. 正在运行的代码:src/app/**、src/services/**、src/worker/index.ts、prisma/schema.prisma
  2. 行为契约:docx/需求规格-邮件自动预报-v0.2.md、docx/接口/carriercentral客户端通用接口V1.73.md
  3. 协作入口:docs/邮箱项目.md(红线、排障、上市清单)
  4. 指令规则:docx/产品规则-邮件指令识别与拆分.md、docs/邮件指令提取规则.md
  5. 速查: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:

  1. ImapPoller.tick()
  2. staleReaper.run()(PARSING/IMPORTING 超时打回)
  3. drainFetchedMails(10) 消化 FETCHED
  4. processDueAutoExec(10)
  5. 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 登录

  1. POST /api/auth/login {username, password}
  2. checkSlidingWindow → verifyCredentials 比对 APP_ADMIN_* / APP_OPS_*
  3. iron-session 写入 username+role
  4. 客户端进 (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 功能加到助手仓」):

  1. 先引用本文的模块路径与函数名,再给修改方案。
  2. 遵循现有分层、Zod 入参、状态机、pnpm、中文 ui-copy。
  3. 未经明确允许,不引入 Redis/新 ORM/第二套前端框架。
  4. Worker 与 ConfirmImport 职责拆分不破。
  5. 行为变更同步 docx 与本文对应章节;协作入口 docs/邮箱项目.md 红线保持。