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.
34 KiB
34 KiB
异常场景与系统韧性设计(邮件自动预报系统专用)
本章为
docx/需求规格-邮件自动预报-v0.2.md第 14 章正文;与 §5–§8 状态机、CC 接口、数据模型、运营 SOP 一一对应。
常量约定:IMPORT_LOCK_TTL=120s;IMPORTING_TIMEOUT=180s;PARSING_STALE=10min;COMPENSATION_MAX_RETRY=3;IMAP_CONNECT_TIMEOUT=30s;IMAP_READ_TIMEOUT=60s;CC_HTTP_TIMEOUT=60s;CC_REPLAY_ON_410=1;SHIPMENT_ROW_SOFT_LIMIT=1000;SHIPMENT_ROW_HARD_LIMIT=5000。 ;IMAP_LOOKBACK_DAYS=3。
2026-07-23 回写:拉取=近 N 天已读+未读;一期无 audit_log;详见docx/现行口径-修订说明-v1.md。
1. 输入层异常处理
1.1 邮件缺失主题
- 异常描述:IMAP 入库的 Message
subject为空或仅空白。 - 系统行为:
mail_message.subject存"(无主题)";类型判定仅用正文+附件名;照常进入PARSING。 - 是否允许降级:是(继续解析)。
- 是否触发告警:
warn日志mail.subject_missing;不计监控告警。 - 用户看到的结果:列表主题显示「(无主题)」;详情正常。
1.2 邮件缺失正文
- 异常描述:纯附件邮件或正文 HTML/纯文本均为空。
- 系统行为:正文摘要空串;类型与柜头字段仅从附件/主题提取;无附件则
REJECTED_VALIDATION,last_error=NO_BODY_NO_ATTACHMENT。 - 是否允许降级:有附件则继续;无附件则阻断导入路径。
- 是否触发告警:无附件 →
error+ 指标mail.reject_validation。 - 用户看到的结果:状态「校验拒绝」;提示「无正文且无附件,无法解析」;可下载原始快照。
1.3 邮件无附件
- 异常描述:类型可能为 NEW/TRANSFER,但无 xlsx/csv/zip。
- 系统行为:若主题/正文能抽出柜号则写入
parse_result.container_header,shipments=[];mail_type仍按 §4 判定;因有效行=0,即使 NEW 也不进PENDING_CONFIRM,停留REJECTED_VALIDATION或PARSED(非 NEW)+last_error=NO_SHIPMENT_ROWS。 - 是否允许降级:是(仅展示,不可导入)。
- 是否触发告警:
warnparse.no_attachment。 - 用户看到的结果:详情提示「缺少卡派/装箱清单附件」;确认导入入口不可用。
1.4 附件格式非 xlsx/csv,且 zip 内无有效文件
- 异常描述:仅有 pdf/图片/docx,或 zip 解压后无 xlsx/csv/xls。
- 系统行为:附件仍写入
mail_attachment;解析器标记template_id=null;状态PARSE_FAILED,last_error=NO_SUPPORTED_SPREADSHEET。 - 是否允许降级:附件可下载;不调用 SaveContainer。
- 是否触发告警:
errorparse.unsupported_attachment。 - 用户看到的结果:「不支持的附件格式」+「重新解析」按钮;运营按 SOP 走客户端预报。
1.5 附件关键列缺失(仓库ID / 渠道 / 件数)
- 异常描述:卡派资料表头缺少映射到
F_FBACode/F_Transporter/F_CTNS的列。 - 系统行为:整表无法建有效行 →
PARSE_FAILED,lineage.missing_columns=[...];若仅部分行缺值见 1.7。 - 是否允许降级:否(不可进
PENDING_CONFIRM)。 - 是否触发告警:
errorparse.missing_columns。 - 用户看到的结果:展示缺失列名列表;可重新解析。
1.6 柜号格式非法(非 ISO 6346)
- 异常描述:抽出柜号未通过校验位(且未关闭 ISO 校验配置)。
- 系统行为:
container_header.F_ContainerNo保留原值并标container_no_valid=false;状态REJECTED_VALIDATION;禁止确认导入。 - 是否允许降级:Admin 可在详情手工改柜号后触发「重新校验」;不自动猜正确柜号。
- 是否触发告警:
warnparse.invalid_container_no。 - 用户看到的结果:「柜号未通过 ISO 校验:{no}」;编辑柜号后可再校验。
1.7 货件行半空(件数=0、仓库ID 空等)
- 异常描述:单行缺少
F_FBACode或F_Transporter或F_CTNS≤0/空。 - 系统行为:该行
row_status=INVALID,写入shipments[];不阻断其他 VALID 行;默认不勾选。 - 是否允许降级:是(柜级部分行导入,§5.7)。
- 是否触发告警:INVALID 行占比 >30% →
warnparse.high_invalid_ratio。 - 用户看到的结果:灰显行 + tooltip 列缺失原因;底栏「已选 n / 有效 m」。
1.8 超大附件(>20MB)
- 异常描述:单附件
size > 20MB。 - 系统行为:不落盘完整文件(或落盘后立即拒绝解析);
mail_attachment记 size +rejected=1;邮件REJECTED_VALIDATION,last_error=ATTACHMENT_TOO_LARGE;仍按 §6.2 标\Seen。 - 是否允许降级:否。
- 是否触发告警:
errormail.attachment_too_large。 - 用户看到的结果:「附件超过 20MB,请客户拆分后重发」;快照仅保留邮件头+正文。
1.9 渠道映射词不匹配
- 异常描述:渠道列值不在 §5.6 黄金表。
- 系统行为:
F_Transporter=原文截断 30 字符;行标CHANNEL_UNMAPPED;仍可为 VALID(若三必填齐全);确认页该行黄标警告。 - 是否允许降级:是(不阻断提交);CC 若 400 再走补偿。
- 是否触发告警:
warnmap.channel_unmapped(带原词)。 - 用户看到的结果:警告「渠道未映射:{raw},将原样提交 CC」;可手工改下拉为标准枚举。
1.10 体积/重量含非数字
- 异常描述:
总体积/毛重含单位文字或非法字符。 - 系统行为:剥离常见单位(m³/cbm/kg)后
parseFloat;失败则该字段null,行仍 VALID(体积重量非必填);lineage记FIELD_PARSE_FALLBACK。 - 是否允许降级:是。
- 是否触发告警:仅
debug/info。 - 用户看到的结果:对应单元格空;可手工补填。
1.11 地址/日期格式错误
- 异常描述:送仓日期无法解析,或地址超长。
- 系统行为:日期统一按
Asia/Shanghai解析;失败则F_Expected_DeliveryDateB/E=null;地址截断至 500 字符并warn。 - 是否允许降级:是。
- 是否触发告警:日期失败
warnparse.bad_date。 - 用户看到的结果:日期空或截断提示;确认页可改。
2. 用户行为与并发控制
2.1 确认页重复点击提交
- 异常描述:用户连点「确认导入」。
- 系统行为:前端按钮立即
disabled+ loading;请求带Idempotency-Key=mail_id:container_no:shipments_hash;后端对mail_message执行UPDATE ... SET status='IMPORTING' WHERE id=? AND status='PENDING_CONFIRM',影响行=0 则返回409 ALREADY_IMPORTING。 - 是否允许降级:第二次请求直接返回当前状态,不二次调 CC。
- 是否触发告警:
infoimport.duplicate_click。 - 用户看到的结果:首次进入「导入中」;再次点击 Toast「正在导入,请勿重复提交」。
2.2 快速连续提交(并发)
- 异常描述:两次请求几乎同时到达。
- 系统行为:DB 行锁/条件更新串行化;仅赢家进入 CC 调用;输家 409。
- 是否允许降级:否(严格单飞)。
- 是否触发告警:
warn若 1 分钟内同 mail 409>3 →import.contention。 - 用户看到的结果:同 2.1。
2.3 页面刷新导致状态丢失或重复请求
- 异常描述:提交后刷新,前端本地 state 清空。
- 系统行为:一切以 DB
status为准;刷新后拉取详情,若IMPORTING/SUCCESS/FAILED展示对应 UI;不自动重放未完成请求(除非用户点重试)。 - 是否允许降级:是(只读恢复)。
- 是否触发告警:否。
- 用户看到的结果:看到真实状态角标;IMPORTING 显示进度/等待。
2.4 浏览器回退/前进
- 异常描述:确认页回退到详情再前进。
- 系统行为:确认页
beforeunload不拦截只读浏览;若 status 已非PENDING_CONFIRM,确认页挂载时重定向详情并禁用提交。 - 是否允许降级:是。
- 是否触发告警:否。
- 用户看到的结果:「当前状态不可导入:{status}」。
2.5 多标签页同时操作同一邮件
- 异常描述:两标签同时改字段或提交。
- 系统行为:提交用乐观锁
mail_message.updated_at或version字段;冲突返回409 VERSION_CONFLICT;改类型接口同锁。 - 是否允许降级:后写失败,需刷新。
- 是否触发告警:
warnmail.version_conflict。 - 用户看到的结果:「数据已被其他页面更新,请刷新后重试」。
2.6 Admin 改类型误操作
- 异常描述:误将 LABEL 改为 NEW 并准备导入。
- 系统行为:改类型二次确认弹窗展示旧→新 + 证据分;写入
audit_log;支持「改回」若尚未IMPORTING;已 SUCCESS 禁止改类型。 - 是否允许降级:人工改回。
- 是否触发告警:每次改类型
info审计;1 小时同用户改类型>10 →warn。 - 用户看到的结果:弹窗确认;一期无审计页。
3. 网络与通信容错
3.1 IMAP 连接断开/超时
- 异常描述:connect>30s 或 read>60s,或 TLS 中断。
- 系统行为:本次轮询 abort;释放
GET_LOCK('imap_poll');下个POLL_INTERVAL重试;已FETCHED的邮件不受影响。 - 是否允许降级:retry(无限轮询,指数退避上限 5min 仅当连续失败≥5)。
- 是否触发告警:连续失败≥3 →
error监控imap.poll_fail;列表顶栏红条「邮件同步异常」。 - 用户看到的结果:已有邮件仍可操作;新邮件暂不出现。
3.2 前端调 CC 超时/断开
- 异常描述:Web→自有 API 或自有 API→CC 超过
CC_HTTP_TIMEOUT。 - 系统行为:若请求已发出且本地未收到响应:柜状态先标
FAILED或入import_compensation(reason=TIMEOUT_UNKNOWN);禁止立即自动重放 SaveContainer(防双写);需GetContainerList确认柜是否已存在后再决定重试或标 SUCCESS。 - 是否允许降级:补偿队列 + 人工确认重试。
- 是否触发告警:
errorcc.timeout。 - 用户看到的结果:「导入结果不确定,已加入待核查队列」;日志页显示「超时待核对」。
3.3 请求已发送但响应丢失(部分成功不确定)
- 异常描述:CC 可能已建柜,客户端读超时。
- 系统行为:同 3.2;核对流程:用柜号查
GetContainerList;命中近 10 分钟新建 → 回写external_id并SUCCESS;未命中 → 允许补偿重试。 - 是否允许降级:核查后自动收敛。
- 是否触发告警:
errorcc.ambiguous_success。 - 用户看到的结果:状态「待核查」直到收敛。
3.4 弱网重试导致重复请求
- 异常描述:浏览器/代理自动重试 POST。
- 系统行为:依赖
Idempotency-Key+ 条件更新PENDING_CONFIRM→IMPORTING;CC 侧再经 §5.8 冲突检测。 - 是否允许降级:幂等吞掉重复。
- 是否触发告警:
info。 - 用户看到的结果:单次成功结果。
3.5 CDN/静态资源加载失败
- 异常描述:JS/CSS chunk 404。
- 系统行为:Next error boundary 展示「资源加载失败,请强刷」;不影响已运行的 worker/IMAP。
- 是否允许降级:用户手动刷新。
- 是否触发告警:前端上报
warnui.chunk_fail(若有)。 - 用户看到的结果:整页错误壳,非静默白屏。
4. 后端 API(CarrierCentral)容错策略
4.1 customerLogin 返回 410 或 5xx
- 异常描述:登录失败或 token 接口异常。
- 系统行为:清
cc_token_cache+ 进程内存 token;5xx 退避重试登录最多 2 次;仍失败则导入整体FAILED,last_error=CC_LOGIN_FAILED。 - 是否允许降级:否(无法调用业务 API)。
- 是否触发告警:
errorcc.login_fail。 - 用户看到的结果:§5.10「登录失效/CC 异常」文案;运营检查
CC_USERNAME。
4.2 SaveContainer 业务调用中途 410
- 异常描述:token 过期发生在 SaveContainer。
- 系统行为:重登 1 次并重放同一 Idempotency 载荷 1 次(
CC_REPLAY_ON_410=1);仍 410 → FAILED。 - 是否允许降级:单次重放。
- 是否触发告警:重放成功
info;失败error。 - 用户看到的结果:成功则无感;失败见 §5.10。
4.3 SaveContainer 返回 400(含柜号已存在)
- 异常描述:CC 业务校验失败。
- 系统行为:该柜
container_import.status=FAILED;last_error=info;不自动重试(业务错误);邮件多柜则其他柜继续 → 可能PARTIAL_SUCCESS。 - 是否允许降级:人工改数据后重新确认(若邮件回到可编辑:从 FAILED 允许「重新打开确认」仅当无任何 SUCCESS 柜;已有 SUCCESS 柜则仅补偿失败柜)。
- 是否触发告警:
warncc.save_400。 - 用户看到的结果:「业务校验失败:{info}」。
4.4 GetContainerList 超时或数据不全
- 异常描述:冲突检测调用失败。
- 系统行为:默认阻断导入(
CONFLICT_CHECK_FAILED),不盲调 SaveContainer;运营可 Admin 强制跳过冲突检测(需ENABLE_FORCE_IMPORT=true)。 - 是否允许降级:仅 Admin 强制。
- 是否触发告警:
errorcc.list_timeout。 - 用户看到的结果:「无法校验柜号是否已存在,请稍后重试」。
4.5 响应结构变化(字段新增/删除)
- 异常描述:
data非预期类型或缺柜 id。 - 系统行为:
code=200但无法解析external_id→ 走 3.2 不确定流程(列表核对);解析层用宽松 schema(忽略未知字段)。 - 是否允许降级:核对收敛。
- 是否触发告警:
errorcc.schema_drift。 - 用户看到的结果:「返回异常,已待核查」。
4.6 相同 loginMark「重复提交」语义
- 异常描述:CC 侧会话与多次 SaveContainer。
- 系统行为:本系统不轮换 loginMark(进程级固定);重复保护完全靠本库幂等 + 冲突检测,不依赖 CC 对 loginMark 去重。
- 是否允许降级:本库幂等。
- 是否触发告警:否。
- 用户看到的结果:无重复柜(冲突则阻断)。
5. 邮件解析与附件处理容错(重点)
5.1 IMAP 拉取成功但无法解析原始内容
- 异常描述:损坏 MIME / 编码异常。
- 系统行为:原始字节仍写入
snapshot_path;PARSE_FAILED,last_error=MIME_PARSE_ERROR;已\Seen。 - 是否允许降级:人工下载快照;「重新解析」可重试。
- 是否触发告警:
errorparse.mime_error。 - 用户看到的结果:解析失败页 + 下载原始邮件。
5.2 zip 解压失败或嵌套多层
- 异常描述:损坏 zip,或有效 xlsx 在二层目录。
- 系统行为:仅解压一层;失败 →
PARSE_FAILEDZIP_EXTRACT_FAILED;一层内递归目录找 xlsx/csv;超过一层嵌套 zip 不进入,记warnzip.nested_ignored。 - 是否允许降级:否(需客户重发扁平附件)。
- 是否触发告警:失败
error;嵌套忽略warn。 - 用户看到的结果:对应错误文案。
5.3 多 sheet 找不到「卡派」
- 异常描述:无名称含「卡派」的 sheet。
- 系统行为:回退第一 sheet;若第一 sheet 表头映射失败再
PARSE_FAILED。 - 是否允许降级:fallback 第一 sheet。
- 是否触发告警:
infoparse.sheet_fallback。 - 用户看到的结果:详情显示「使用 Sheet:{name}」。
5.4 表头别名匹配失败
- 异常描述:列名与映射表无一命中必填语义。
- 系统行为:同 1.5 →
PARSE_FAILED+missing_columns。 - 是否允许降级:否。
- 是否触发告警:
error。 - 用户看到的结果:缺失列列表。
5.5 货件行数过大(>1000 / >5000)
- 异常描述:解析行数超过软/硬限制。
- 系统行为:
>SHIPMENT_ROW_HARD_LIMIT(5000)→REJECTED_VALIDATION不落全量 JSON;>SOFT(1000)且 ≤HARD → 落库,确认页强制虚拟滚动,默认不全选,需用户筛选后勾选,底栏警告。 - 是否允许降级:软限制降级;硬限制拒绝。
- 是否触发告警:软
warn;硬errorparse.row_limit。 - 用户看到的结果:硬限「行数过多请拆分」;软限「行数较多,请筛选后勾选导入」。
5.6 柜号提取错误
- 异常描述:正文柜号与附件柜号不一致,或正则误匹配。
- 系统行为:附件柜号优先(§ 结构化优先);冲突写入
lineageCONTAINER_NO_CONFLICT;确认页大红警告,默认阻断提交直至人工选定柜号(下拉:附件值/正文值/手输)。 - 是否允许降级:人工选定。
- 是否触发告警:
warnparse.container_conflict。 - 用户看到的结果:必须选择柜号后才能确认。
5.7 类型判定信号冲突
- 异常描述:多类型得分接近或同分。
- 系统行为:严格按 §4.2 同分优先级执行;
type_evidence完整落库;UI 展示各类型得分。 - 是否允许降级:Admin 改类型。
- 是否触发告警:得分差≤5 →
infotype.close_call。 - 用户看到的结果:证据面板;非 NEW 导入按钮禁用原因含「当前类型=…」。
5.8 重新解析
- 异常描述:用户/系统对
PARSE_FAILED/REJECTED_VALIDATION/PARSED点重新解析。 - 系统行为:仅当 status∉{IMPORTING,SUCCESS,PARTIAL_SUCCESS};置
PARSING;覆盖parse_result;写审计。 - 是否允许降级:是。
- 是否触发告警:
info。 - 用户看到的结果:解析中 → 新结果。
6. 状态机与生命周期保护
6.1 落库成功但标 \Seen 失败
- 异常描述:DB 已有
FETCHED,IMAP STORE 失败。 - 系统行为:邮件保持可解析;下次 lookback 再次命中 UID → 幂等键命中则跳过新建,仅重试
\Seen;不重复解析除非强制。 - 是否允许降级:retry Seen。
- 是否触发告警:连续 Seen 失败
warnimap.seen_fail。 - 用户看到的结果:列表仍一条;无重复卡片。
6.2 进程崩溃卡在 PARSING
- 异常描述:worker 死于解析中。
- 系统行为:定时任务(每 1min):
status=PARSING AND updated_at < now-PARSING_STALE→ 目标重置为FETCHED再入队;现行可先置PARSE_FAILED后「重新解析」(待收敛)。一期不写 audit_log。 - 是否允许降级:自动恢复。
- 是否触发告警:
errorstate.stale_parsing。 - 用户看到的结果:短暂后恢复为已解析或失败态。
6.3 IMPORTING 超时未更新
- 异常描述:超过
IMPORTING_TIMEOUT=180s。 - 系统行为:回收任务将状态改为
FAILED或TIMEOUT_UNKNOWN入补偿;触发 3.2 核对;禁止无限 IMPORTING。 - 是否允许降级:补偿+核对。
- 是否触发告警:
errorstate.importing_timeout。 - 用户看到的结果:「导入超时,请至日志核查/重试」。
6.4 解析与 Admin 改类型并发
- 异常描述:PARSING 中同时改类型。
- 系统行为:改类型 API 拒绝非终态解析中:
409 MAIL_BUSY;解析结束后以解析器写的mail_type为准,Admin 需再改。 - 是否允许降级:否。
- 是否触发告警:
info。 - 用户看到的结果:「邮件正在解析,请稍后再改类型」。
6.5 补偿队列丢记录或超最大重试
- 异常描述:
retry_count >= COMPENSATION_MAX_RETRY(3)或行被误删。 - 系统行为:超限标
COMPENSATION_EXHAUSTED,停止自动重试;监控告警;运营手工重试重置retry_count(Admin)。 - 是否允许降级:人工介入。
- 是否触发告警:
errorcompensation.exhausted。 - 用户看到的结果:日志「重试已耗尽,请联系值班」;SOP §12。
6.6 非法状态迁移
- 异常描述:客户端伪造 status 回写。
- 系统行为:所有迁移仅服务端按 §6.3 表执行;无通用 status PATCH。
- 是否允许降级:否。
- 是否触发告警:
warnstate.illegal_transition。 - 用户看到的结果:400。
7. 缓存一致性与幂等设计
7.1 DB token 过期但内存仍有效
- 异常描述:
cc_token_cache.expire_at已过,进程内存未清。 - 系统行为:每次用 token 前比较 DB
expire_at;以 DB 为权威;过期强制重登;内存跟随失效。 - 是否允许降级:重登。
- 是否触发告警:
infocc.token_refresh。 - 用户看到的结果:无感或一次短暂延迟。
7.2 多实例 token 刷新未同步
- 异常描述:实例 A 重登写入 DB,实例 B 仍持旧 token。
- 系统行为:B 收到 410 → 读 DB 最新 token;若仍 410 → 抢锁重登(
GET_LOCK('cc_login'))写 DB。 - 是否允许降级:410 重放路径。
- 是否触发告警:
warn频繁 410cc.token_churn。 - 用户看到的结果:同 §5.10。
7.3 raw_hash 碰撞
- 异常描述:不同邮件 hash 相同(极低概率)。
- 系统行为:幂等顺序:Message-ID → folder+uid → raw_hash;hash 命中时若 Message-ID 不同则不合并,改用 folder+uid 新建并
erroridempotency.hash_collision。 - 是否允许降级:分叉新建。
- 是否触发告警:
error(必告警)。 - 用户看到的结果:两条独立邮件记录。
7.4 同一 Message-ID 被多次处理
- 异常描述:重复投递/重复拉取。
- 系统行为:
UNIQUE(message_id)插入失败 → 吞掉;若需更新\Seen则只做 IMAP。 - 是否允许降级:幂等跳过。
- 是否触发告警:
infoidempotency.msgid_dup。 - 用户看到的结果:仍一条。
8. 并发一致性与锁策略
8.1 多 worker 锁竞争失败
- 异常描述:
GET_LOCK('imap_poll', 0)返回 0。 - 系统行为:本轮直接退出,等待下轮;不报错为故障。
- 是否允许降级:是(正常互斥)。
- 是否触发告警:连续 30 轮拿不到锁且无其他实例心跳 →
errorimap.lock_stuck(锁持有方崩溃未释放极罕见,MySQL 连接断即释放)。 - 用户看到的结果:无。
8.2 同一柜号多封邮件同时确认
- 异常描述:两封 PENDING 邮件含同柜,两运营同时提交。
- 系统行为:提交事务内:
SELECT ... FOR UPDATE锁container_import占位行UNIQUE(container_no) WHERE status IN ('IMPORTING','SUCCESS')或等价唯一活跃索引;第二人冲突检测失败阻断;第一人 CC 成功后第二人必拦。 - 是否允许降级:后提交失败。
- 是否触发告警:
warnconflict.cross_mail_container。 - 用户看到的结果:「柜号已被邮件#{id}占用/已存在」。
8.3 多 Admin 同时改类型并提交
- 异常描述:改类型+导入竞态。
- 系统行为:
version乐观锁;导入前再次校验mail_type=NEW_CONTAINER;类型已变则 409。 - 是否允许降级:刷新重来。
- 是否触发告警:
warn。 - 用户看到的结果:版本冲突提示。
8.4 补偿队列同一柜重复重试
- 异常描述:调度器重复投递补偿任务。
- 系统行为:补偿执行前锁
container_import;已 SUCCESS 跳过;IMPORTING跳过;用retry_token幂等。 - 是否允许降级:跳过。
- 是否触发告警:
info。 - 用户看到的结果:单次结果。
9. 数据计算安全
9.1 CHANNEL_UNMAPPED 未阻断
- 异常描述:未知渠道仍提交(见 1.9)。
- 系统行为:确认页强制展示警告计数;提交 API 若存在 UNMAPPED 且未传
ack_unmapped_channels=true→ 400 要求确认。 - 是否允许降级:用户显式确认后放行。
- 是否触发告警:
warn。 - 用户看到的结果:二次确认「存在未映射渠道,仍要提交?」。
9.2 浮点精度(体积/重量)
- 异常描述:Excel 浮点噪声。
- 系统行为:CBM/Weight 存 DECIMAL(18,4);展示与提交前
round(value,4);不在服务端做单位换算乘法链。 - 是否允许降级:四舍五入。
- 是否触发告警:否。
- 用户看到的结果:最多 4 位小数。
9.3 日期时区错误
- 异常描述:解析为 UTC 导致差一天。
- 系统行为:所有业务日期按
Asia/Shanghai日历日;写入 CC 格式YYYY-MM-DD;received_at存 UTC 瞬时并 UI 转上海。 - 是否允许降级:固定时区。
- 是否触发告警:跨日边界单测覆盖;运行时否。
- 用户看到的结果:与邮件 ETA 日历日一致。
9.4 F_ShippingLineId 匹配失败
- 异常描述:船名无法匹配。
- 系统行为:留空;确认页可选船司;不阻断提交(字段非 SaveContainer 必填)。
- 是否允许降级:是。
- 是否触发告警:
info。 - 用户看到的结果:「未匹配船司,可手动选择」。
9.5 勾选总数与提交数不一致
- 异常描述:前端展示 n,实际 payload m。
- 系统行为:服务端以请求体
shipment_ids[]为准再查parse_result;过滤非 VALID/非本 mail;响应回传accepted_count;与前端 n 不符则 UI 以服务端为准刷新。 - 是否允许降级:服务端权威。
- 是否触发告警:不一致
warnimport.count_mismatch。 - 用户看到的结果:Toast「实际提交 {accepted_count} 行」。
10. 权限与安全控制
10.1 未登录访问 API
- 异常描述:无 session 调
/api/mails、/api/import。 - 系统行为:统一 401;不返回业务数据。
- 是否允许降级:否。
- 是否触发告警:暴力 401 计数
security.unauthorized。 - 用户看到的结果:跳转登录。
10.2 越权查看(一期单租户)
- 异常描述:伪造 mail_id 扫库。
- 系统行为:一期单租户:登录即可访问全部邮件;禁止未登录;二期按
sender_customer_map/客户隔离时校验customer_code。 - 是否允许降级:一期 N/A。
- 是否触发告警:否(一期)。
- 用户看到的结果:正常列表(仅登录用户)。
10.3 Admin 改类型未授权
- 异常描述:
ENABLE_TYPE_OVERRIDE=false但前端暴露按钮;或普通用户调 API。 - 系统行为:服务端校验
role=admin AND ENABLE_TYPE_OVERRIDE;失败 403;前端按同条件隐藏(不可作唯一防线)。 - 是否允许降级:否。
- 是否触发告警:
warnsecurity.type_override_denied。 - 用户看到的结果:无按钮或 403。
10.4 伪造 loginMark / CC token
- 异常描述:客户端上传假 token 想直打 CC。
- 系统行为:浏览器永不持有 CC token;仅服务端 Adapter 使用;用户会话与 CC 凭证隔离。
- 是否允许降级:否。
- 是否触发告警:出现客户端传 cc_token 字段 →
errorsecurity.cc_token_probe。 - 用户看到的结果:忽略非法字段。
10.5 批量刷导入接口
- 异常描述:脚本对多邮件狂打确认。
- 系统行为:用户级限流:确认导入 10 次/分钟;同 mail 条件更新防重;超限 429。
- 是否允许降级:限流。
- 是否触发告警:
warnsecurity.rate_limit_import。 - 用户看到的结果:「操作过于频繁」。
10.6 审计(一期不做)
- 现行口径:不建
audit_log、无 Admin 审计页;关键操作依赖应用日志与/logs。 - 系统行为:原写审计点可空实现,不阻断主流程。
- 是否允许降级:是(已降级为不做)。
- 用户看到的结果:无审计查询入口。
11. 监控指标与告警阈值(落地用)
| 指标 | 级别 | 阈值 |
|---|---|---|
imap.poll_fail 连续 |
error | ≥3 轮 |
cc.login_fail |
error | ≥1/5min |
cc.ambiguous_success |
error | ≥1 |
state.stale_parsing |
error | ≥1 |
state.importing_timeout |
error | ≥1 |
compensation.exhausted |
error | ≥1 |
idempotency.hash_collision |
error | ≥1 |
parse.high_invalid_ratio |
warn | 单邮件 INVALID>30% |
import.contention |
warn | 同 mail 409>3/min |
security.rate_limit_import |
warn | 触发即记 |
一期告警出口:应用日志 + 管理页顶栏;企微后置。
12. QA 异常用例索引(按本章)
| 编号 | 对应用例要点 |
|---|---|
| A-1.x | 无主题/无正文/无附件/坏格式/缺列/坏柜号/半空行/20MB+/未映射渠道/坏数字日期 |
| A-2.x | 双击提交、双标签 version、刷新恢复、回退、误改类型 |
| A-3.x | IMAP 断网、CC 超时、模糊成功核对 |
| A-4.x | login 5xx、Save 400、List 超时阻断、410 重放 |
| A-5.x | 坏 MIME、坏 zip、sheet 回退、1001 行软限、柜号冲突、重新解析 |
| A-6.x | Seen 失败去重、卡 PARSING 回收、IMPORTING 超时 |
| A-7.x | token DB 权威、Message-ID 重复 |
| A-8.x | 双 worker 锁、双邮件同柜 |
| A-9.x | unmapped 需 ack、时区、accepted_count |
| A-10.x | 401/403 改类型、429 限流、无 CC token 出前端 |
12.1 自动化覆盖(2026-08-12)
跑法:pnpm test(单测/集成)+ pnpm test:e2e tests/e2e/resilience-auth.spec.ts(需本机可起 web)。
| 编号 | 覆盖 | 自动化入口 | 真人/联调仍需 |
|---|---|---|---|
| A-1.1 空主题 | ✅ | resilience-scenarios + resilience-metrics |
UI 列表展示「(无主题)」 |
| A-1.2/1.3 无正文无附件 / NEW 无清单 | ✅ | resolveNoSpreadsheetOutcome |
详情文案 |
| A-1.4 非表格附件 | ✅ | 同上 | 重新解析按钮 |
| A-1.5 缺列 | ✅ | packing-list | — |
| A-1.6 坏柜号 ISO | ✅ | iso6346 + scenarios | Admin 改柜号再校验 |
| A-1.7 半空行 INVALID | ✅ | packing-list scenarios | 确认页灰显 |
| A-1.8 >20MB | ⚠️ 规则在 pipeline | 未造 20MB fixture | 真附件拒收 + \Seen |
| A-1.9 未映射渠道 | ✅ | channel-map + packing-list | 确认页黄标 + ack |
| A-1.10 体积重量单位 | ✅ | packing-list | — |
| A-1.11 日期/地址截断 | ✅ | dates + packing-list | — |
| A-2.1/2.2 连点/并发 | ✅ 门禁+409 码 | forecast-gate + ConfirmImportError | 真双击/双请求打 DB CAS |
| A-2.3 刷新恢复 | ⚠️ | 无独立 E2E | 导入中刷新看 DB 状态 |
| A-2.4 回退确认页 | ⚠️ | 无独立 E2E | 浏览器前进后退 |
| A-2.5 version 冲突 | ✅ 错误码 | ConfirmImportError | 双标签真打 |
| A-2.6 误改类型 | ⚠️ API 有 MAIL_BUSY | type/route | Admin 二次确认弹窗 |
| A-3.1 IMAP 超时退避 | ✅ 阈值/退避公式 | runtime-status | 真断网轮询 |
| A-3.2/3.3 模糊成功窗口 | ✅ 10min 窗口 | compensation.isRecentlyCreated | live GetContainerList |
| A-3.4 弱网重试 | ⚠️ 依赖幂等键 | import CAS | 代理重放 POST |
| A-3.5 chunk 404 | ❌ | 无 | 强刷 error boundary |
| A-4.1 login token 解析 | ✅ extractLoginToken | auth.ts | live 5xx/410 |
| A-4.2 Save 410 重放 1 次 | ⚠️ http.ts 有 retried410 | 无单测打真实 410 | live token 过期 |
| A-4.3 Save 400 | ⚠️ mock 冲突 | cc-save + force-import | live 业务 400 文案 |
| A-4.4 List 超时阻断 | ⚠️ 代码默认阻断 | force-import | live timeout |
| A-4.5 schema drift | ✅ token/id 宽松解析 | extractLoginToken | 缺 F_Id → TIMEOUT_UNKNOWN |
| A-5.1 坏 MIME | ⚠️ pipeline MIME_PARSE_ERROR | 无坏 eml fixture | 重新解析+下载快照 |
| A-5.2 zip 一层/嵌套/穿越 | ✅ | zip-extract | — |
| A-5.3 sheet 回退 | ✅ | packing-list sheetFallback | — |
| A-5.4 表头失败 | ✅ 同 1.5 | — | — |
| A-5.5 软/硬行数 | ✅ hardLimit/softLimit | packing-list | 确认页虚拟滚动 UI |
| A-5.6 柜号冲突 | ⚠️ 规则有 | pipeline lineage | 确认页必须选定柜号 |
| A-5.7 类型 close call | ⚠️ classify 有样例 | classify.test | 证据面板 |
| A-5.8 重新解析门禁 | ✅ 状态机 | state-machine | 点按钮 |
| A-6.1 Seen 失败去重 | ❌ 需 IMAP | — | lookback 重试 Seen |
| A-6.2 卡 PARSING 回收 | ✅ 允许 PARSING→FETCHED | state-machine | worker reaper 真跑 |
| A-6.3 IMPORTING 超时 | ✅ 允许 IMPORTING→FAILED | state-machine | reaper + 补偿行 |
| A-6.4 解析中改类型 | ⚠️ API MAIL_BUSY | type/route | 并发点改类型 |
| A-6.5 补偿耗尽 | ⚠️ 代码有 EXHAUSTED | compensation | Admin 手工重置 |
| A-6.6 非法迁移 | ✅ | state-machine | — |
| A-7.1/7.2 token DB 权威 | ⚠️ 实现有 | 无多实例测 | 双 web 410 |
| A-7.3 hash 碰撞分叉 | ✅ | hash + resilience-metrics | — |
| A-7.4 Message-ID 重复/截断 | ✅ truncate 191 | snapshot | UNIQUE 插入失败 |
| A-8.1 双 worker 锁 | ❌ 需双进程+MySQL | db-lock | 双 worker lock_busy |
| A-8.2 双邮件同柜 | ⚠️ conflict mock | cc-save | 两运营同时确认 |
| A-8.3 改类型+导入竞态 | ⚠️ version | ConfirmImportError | — |
| A-8.4 补偿重复投递 | ⚠️ retry_token | compensation | worker 双 tick |
| A-9.1 unmapped ack | ✅ 错误码 + warning | ConfirmImportError | 确认页二次确认 |
| A-9.2 round4 | ✅ | dates | — |
| A-9.3 时区 | ✅ | hash-dates | — |
| A-9.4 船司匹配失败不阻断 | ⚠️ extract-header | extract-header.test | 确认页手选 |
| A-9.5 accepted_count/hash | ✅ shipments_hash | hash | Toast |
| A-10.1 未登录 401 | ✅ E2E | resilience-auth.spec | — |
| A-10.2 单租户 | N/A 一期 | — | — |
| A-10.3 改类型 403 | ⚠️ requireAdmin + ENABLE_TYPE_OVERRIDE | type/route | ops 调 API |
| A-10.4 前端无 CC token | ⚠️ 架构约束 | 无扫 bundle | Network 面板 |
| A-10.5 导入/登录限流 | ✅ 单测 + E2E 登录 429 | rate-limit + e2e | 多副本无效 |
| A-10.6 审计一期不做 | N/A | — | — |
图例:✅ 自动化已断言 · ⚠️ 代码有路径、缺完整 fixture/联调 · ❌ 尚未自动化(需 IMAP/双进程/真人 UI)