本文整理 DGJ2 模板下载、文件上传、Excel 解析、逐行校验、批量落库、错误文件、同步导出、OSS 下载和大文件治理。项目中存在多代 Excel 实现,维护前必须先识别当前入口使用哪套工具。

适用于秒杀活动、商品/价格/客户基础资料、采购销售单据、盘点库存、微仓、活动、财务流水和报表的导入导出开发与故障排查。

1. 业务目标

  • 从页面入口定位控制器、解析工具、Service、业务表和文件存储位置。
  • 保证模板、解析、错误文件和导出字段四者口径一致。
  • 区分“全量失败”“逐行部分成功”“仅解析预览”“异步任务”四种原子性。
  • 正确处理编码、长数字、金额、日期、空单元格、公式和超过 26 列的数据。
  • 控制大文件的内存、执行时间、数据库批次、临时文件和 OSS 生命周期。
  • 让用户能知道成功多少、失败多少、失败在哪一行、如何修正后重试。

2. 导入导出全景

flowchart LR
    T["下载模板"] --> F["用户填写文件"]
    F --> U["上传/拉取OSS文件"]
    U --> P["Excel解析"]
    P --> H["表头映射"]
    H --> V["逐行格式与业务校验"]
    V --> MODE{"处理模式"}
    MODE -- 全量原子 --> ALL["任一错误全部拒绝"]
    MODE -- 部分成功 --> PART["有效行批量落库"]
    MODE -- 预览 --> PRE["返回标准化列表"]
    ALL --> RESP["错误摘要"]
    PART --> ERR["错误Excel/下载地址"]
    PRE --> SAVE["前端确认后统一保存"]
    RESP --> FIX["修正后重试"]
    ERR --> FIX

导出主链:

flowchart LR
    Q["查询条件与权限"] --> D["查询/聚合数据"]
    D --> M["字段映射与格式化"]
    M --> W["写Excel/CSV"]
    W --> MODE{"返回方式"}
    MODE -- 同步流 --> HTTP["php://output"]
    MODE -- 本地文件 --> LOCAL["UPLOAD_PATH"]
    MODE -- 对象存储 --> OSS["上传OSS"]
    OSS --> CLEAN["删除临时文件"]
    HTTP --> USER["用户下载"]
    LOCAL --> USER
    OSS --> USER

3. 入口地图

业务入口说明
基础资料application/controllers/basedata/Import.php采购、销售退货、调拨等多种历史导入
商品/客户/货位application/controllers/basedata/Invlocation.php多套解析和错误文件逻辑集中区
价格管理application/controllers/basedata/PriceManager.php价格导入、校验和批次更新
秒杀活动application/controllers/inner/activity/FlashActivity.phpOPS 模板、商品导入、活动/商品导出
普通活动application/controllers/inner/activity/Activity.php多类活动商品、服务站、套包导入
采购application/controllers/scm/InvPo.php采购导出、到货/退货导入
销售application/controllers/scm/InvSa.php、PdImport.php销售、出库、导入错误文件
库存/盘点application/controllers/scm/InvOi.php安全库存、盘点和库存差异
财务application/controllers/scm/Payment.php、Receipt.php收付款、账户、授信流水导出
微仓application/Services/MoveMall/MoveStoSer.php、MoveSupplySer.php商品和供给导入
报表application/controllers/reports/*.php采购、销售、库存、利润等报表导出
CLI 导入任务application/controllers/tasks/Import.php历史数据批处理和缓存初始化

4. Excel 工具代际

4.1 工具矩阵

工具底层库读/写典型特点主要风险
Components/ExcelHelper.phpPHPExcel读写使用广、支持错误文件和报表通用导出列数组仅 A-Z
Utils/NewExcelUtil.phpPHPExcel读写返回 A/B/C 列键和错误表头上传类型配置过宽、列仅 A-Z
Services/BaseData/ExcelService.phpPHPExcel/旧 Excel reader读写OPS/站管家双上传、模板缓存、OSS全表进内存、始终 Excel5 写出
Services/Help/ExcelService.php同类实现读写Help 域复用与 BaseData 同名,引用易混
Components/ExportExcel.phpPhpSpreadsheet写主子行合并、样式、OSS同步构建整本工作簿,内存高
Components/Excel.phpPHPExcel写简单导出能力有限
控制器自定义PHPExcel/sys_csv多为写快速满足历史页面契约、编码、列宽和上限不统一

4.2 选型原则

  1. 先复用业务模块已经使用的工具,避免同一模板读写采用不同数据类型规则。
  2. 超过 26 列时不能直接使用 ExcelHelper::exportExcel/exportExcel2 的固定 A-Z 列数组。
  3. 主单加明细需要纵向合并时可考虑 ExportExcel。
  4. 纯大数据导出优先评估流式 CSV,而不是把整本 Excel 放内存。
  5. OPS 需要返回下载 URL 时使用“本地临时文件 -> OSS -> 删除本地”模式。
  6. 错误文件必须保留原始列和错误原因,不能只返回一段截断错误文本。
代码审计提示:项目里不止一种“列序号转字母”实现,部分历史算法在 Z、AA 等边界存在可疑下标计算。新增宽表导出应直接使用库提供的列转换 API,并用 25/26/27/51/52 列做边界测试。

5. 文件类型与上传

统一可信扩展名为:

xls
xlsx

RegularConst::EXCEL_EXTENSION 和 ExcelHelper::readExcel 都按扩展名选择 Excel5/Excel2007 Reader。

5.1 站管家普通上传

ExcelService::upload 使用 CI Upload:

  • allowed_types=xls|xlsx
  • 文件名重命名为唯一值
  • 保存到 UPLOAD_PATH/excel/upload/{sid}
  • 读取第一张 Sheet
  • 第一行作为表头
  • 后续行按表头列数补空值并 trim

5.2 OPS 上传

OPS 传入对象存储文件描述,Service 通过文件服务获取临时下载地址,再保存到本地站点目录后解析。业务文档和日志不得记录真实签名 URL、存储凭证或完整请求头。

5.3 历史上传风险

NewExcelUtil::upload_file 当前配置 allowed_types=*,实际安全依赖后续 Reader 是否能解析。新功能不要复制这一模式;应同时验证:

  • 扩展名白名单。
  • MIME/文件签名。
  • 文件大小。
  • 文件名不参与最终路径。
  • Reader 只读数据,不执行宏和外部链接。
  • 解析失败后删除临时文件。

6. 解析行为

ExcelHelper::readExcel 的关键行为:

  1. 根据扩展名选择 Excel5 或 Excel2007。
  2. 加载整个工作簿。
  3. 只取第 1 个 Sheet。
  4. 调用 toArray() 返回二维数组。

这意味着:

  • 公式单元格、日期序列值和格式化显示值要单独验证。
  • 大文件会把整张表和对象模型放入 PHP 内存。
  • 第二个及后续 Sheet 默认被忽略。
  • 合并单元格通常只有左上角有值。
  • 隐藏行、隐藏列不会自动代表忽略。

7. 表头契约

7.1 固定列序

历史导入常用 A/B/C 位置映射。模板一旦插列、换序或改标题,解析可能整体错位。

7.2 标题映射

秒杀导入采用标题映射:

  1. trim 表头。
  2. 删除空格和换行。
  3. 用别名字典映射到字段。
  4. 只要必填标题存在,允许用户调整列顺序。
flowchart LR
    HEADER["Excel第一行"] --> NORMAL["去空格/换行"]
    NORMAL --> ALIAS["标题别名 -> 字段"]
    ALIAS --> REQUIRED{"必填字段齐全?"}
    REQUIRED -- 否 --> ERR["提示下载最新模板"]
    REQUIRED -- 是 --> MAP["field -> column index"]

7.3 表头版本化建议

模板发生不兼容变化时建议新增隐藏或显式版本列,而不是靠标题猜测:

template_code: flash_activity_goods
template_version: 2
generated_at: YYYY-MM-DD HH:mm:ss

当前多数历史模板没有统一版本机制,修改时必须兼容已有文件或明确停止旧模板。

8. 单元格数据类型

类型常见问题推荐处理
物料编码/单号Excel 转科学计数法、去前导 0模板设文本,导出显式 string
金额浮点误差、元/分混淆明确单位,Decimal 校验,限制小数位
数量小数位和负数规则不同按业务单位校验精度和符号
日期Excel serial、文本、时区混用统一解析后格式化 Y-m-d
百分比0.8、80%、8折 混用模板说明和后端接受值必须一致
空值空字符串、null、0 混淆每字段定义是否允许空和默认值
布尔是/否、1/0、TRUE/FALSE显式映射,拒绝未知文本
公式显示值与公式内容不同决定读取计算值还是拒绝公式

ExcelHelper::exportExcel 把所有值写为字符串;exportExcel2 仅把 PHP int 写为数字;reportExportExcel 尝试按值推断类型。因此同一数据用不同工具导出,Excel 中的排序和求和表现可能不同。

9. 导入原子性模型

模式行错误时适用场景必须返回
全量原子一行错,整批不落库配置规则、强一致活动全部错误摘要
部分成功正确行落库,错误行回传大批基础资料成功数、失败数、错误文件
解析预览不落库,返回标准化数据需要前端确认的订单/活动标准行和校验结果
异步任务接收后排队超大数据或慢外部依赖task_id、进度、结果文件

产品、前端和后端必须对原子性达成一致。否则用户看到“导入失败”后重试,可能把已成功行重复写入。

10. 通用导入流程

sequenceDiagram
    participant U as 用户
    participant C as Controller
    participant X as Excel工具
    participant S as BusinessService
    participant DB as Database
    participant O as OSS/下载目录
    U->>C: 上传xls/xlsx
    C->>C: 权限/大小/类型检查
    C->>X: 解析第一个Sheet
    X-->>C: header + rows
    C->>S: 标准化与批量校验
    S->>S: 行内校验+跨行重复+库内冲突
    alt 全量通过
        S->>DB: 事务/分批落库
        C-->>U: 成功数量
    else 部分成功
        S->>DB: 写入有效行
        S->>O: 生成错误文件
        C-->>U: 成功/失败数+下载地址
    else 全量拒绝
        C-->>U: 行号化错误摘要
    end

11. 三层校验

11.1 文件级

  • 文件存在、非空。
  • 扩展名/MIME/签名允许。
  • 文件大小和行数不超限。
  • 工作簿可被 Reader 打开。
  • 第一个 Sheet 存在。

11.2 行级

  • 必填字段。
  • 数字、金额、日期、枚举格式。
  • 字符长度和特殊字符。
  • 物料/客户/供应商/仓库存在。
  • 数量和价格边界。

11.3 集合级

  • 文件内重复。
  • 与数据库现有数据冲突。
  • 同活动/订单唯一键冲突。
  • 总数量、总金额和明细合计。
  • 业务状态是否允许批量修改。
  • 同一批次内关联行是否完整。

12. 批量查询与落库

避免逐行查库和逐行写库:

  1. 收集文件内所有 SKU、客户、仓库等业务键。
  2. 去重后分批查询。
  3. 在内存建立映射。
  4. 逐行校验时只查映射。
  5. 有效数据按 200/500/1000 等受控批次写入。
  6. 每批记录数量和失败原因。

项目已有批次案例:价格导入 500 行一批、部分商品/缓存任务 1000 或 2000 一批、秒杀库存查询 200 一批。批次值要根据 SQL 长度、锁时间、内存和外部服务限制确定,不能机械复制。

13. 错误返回设计

理想错误行包含:

字段含义
原始行号从 Excel 第 2 行开始的真实行号
原始业务键SKU、单号、客户编码等
字段名失败字段
原始值用户填写内容,注意脱敏
错误码稳定机器码,可选
错误原因可操作中文说明

一行存在多个错误时,可在同一次校验中汇总,避免用户反复上传才能发现下一个问题。

14. 错误文件

项目常见目录:

UPLOAD_PATH/download/import/error/
UPLOAD_PATH/download/import/error/{sid}/
UPLOAD_PATH/excel/download/error/{sid}/

常见返回字段并不统一:errorFile、error_file、fail_file、file_url、errorFileName。修改接口时必须以实际前端消费字段为准。

错误文件最低要求:

  • 保留原模板列顺序。
  • 最后一列增加“错误原因”。
  • 只包含失败行,或明确标识成功/失败。
  • 业务编码按文本写出。
  • 文件名不可冲突。
  • 下载地址有合理有效期和访问控制。
  • 定期清理本地临时文件。

15. 模板缓存

BaseData/ExcelService::downloadTemplate 使用表头内容生成 hash 文件名:

模板物理文件 = hash(header) + .xls
用户下载文件名 = 业务标题 + .xls

优点:相同表头可复用已生成模板。风险:模板样式、说明或数据验证改变但表头没变时,hash 不变,旧文件仍可能被复用。模板缓存键应包含完整模板版本,而不只包含 header。

16. 同步下载、文件 URL 与 OSS

模式行为适用风险
HTTP 流Writer 写 php://output 后 exit小型同步导出超时后无法恢复、响应前不能有输出
本地下载保存到 UPLOAD_PATH,再由下载接口读取错误文件、模板磁盘堆积、路径校验
OSS URL临时文件上传后返回 URLOPS、异步导出URL 过期、上传失败、临时文件清理

ExportExcel::saveToOss 和秒杀 streamExcel 都是“生成本地文件 -> 上传 OSS -> 删除本地文件”。故障可能发生在三个阶段,日志要区分生成失败、上传失败和删除失败。

17. 下载安全

下载接口不能简单信任用户传入的相对路径。至少需要:

  • 对路径 realpath。
  • 确认最终路径位于允许的下载根目录下。
  • 限定扩展名。
  • 校验当前用户对 sid/业务文件的访问权限。
  • 文件不存在返回业务错误,不暴露服务器绝对路径。
  • OSS URL 不进入长期日志。

inner/Base::download 当前按用户 file 参数拼接到 UPLOAD_PATH,维护该公共入口时应重点验证目录穿越防护,不要扩大可下载目录。

18. Excel 公式注入

任何用户可控文本如果以 = + - @ 开头,Excel 打开时可能被解释为公式。错误文件和导出文件尤其容易原样回写用户输入。

安全策略:

  1. 业务编码统一显式写字符串。
  2. 对用户自由文本检查公式前缀。
  3. 必要时前置单引号或使用显式 string 类型。
  4. 不允许导入宏文件。
  5. 不自动访问工作簿外部链接。

19. 超过 26 列

ExcelHelper::exportExcel/exportExcel2 的列数组只定义 A-Z,超过 26 列会越界。秒杀活动列表导出有 28 列,因此控制器使用:

PHPExcel_Cell::stringFromColumnIndex($index)

来生成 A、B、...、Z、AA、AB。

维护规则:新增导出列前先数列数;超过 26 列时使用可靠的列转换 API,不要自行拼接字母。

20. 大文件资源模型

PHP Excel 库的内存消耗远大于文件大小,因为单元格会变成对象。

flowchart TD
    FILE["上传文件"] --> OBJECTS["Workbook/Sheet/Cell对象"]
    OBJECTS --> ARRAY["toArray二维数组"]
    ARRAY --> MAP["业务映射/错误数组"]
    MAP --> WRITER["错误/导出Workbook"]
    WRITER --> PEAK["内存峰值叠加"]

大文件治理:

  • 设置业务行数上限,而不是只提高 memory_limit。
  • 只读需要的 Sheet、列和行。
  • 分批查询和落库。
  • 错误数据过多时流式写错误文件。
  • 导出超过阈值改异步任务或 CSV。
  • 避免同时保留原二维数组、标准化数组、错误数组和完整 Workbook。
  • 记录总行数、有效行、失败行、耗时和峰值内存。

21. 秒杀商品导入业务契约

入口:inner/activity/FlashActivity::importFlashActivityGoods。

重要语义:导入接口只解析并返回 goodsList,不直接写活动商品表;最终由活动保存接口统一落库。

sequenceDiagram
    participant OPS as OPS页面
    participant C as FlashActivity Controller
    participant S as FlashActivitySer
    participant X as ExcelHelper
    participant DB as FlashActivity/Goods
    OPS->>C: activity_id + file_path + 页面活动上下文
    C->>S: assertImportGoodsAllowed
    C->>C: 从OPS文件服务下载本地文件
    C->>S: importGoods
    S->>X: readExcel
    X-->>S: 二维行
    S->>S: 表头映射/逐行归一/冲突校验
    S-->>OPS: import_count + goodsList
    OPS->>C: saveFlashActivity(goodsList)
    C->>DB: 事务保存活动和商品

21.1 请求示例

字段名以真实网关映射为准:

POST /inner/activity/flashActivity/importFlashActivityGoods
Content-Type: multipart/form-data

activity_id=123
activity_type=2
stock_scope=1
file_path=<OPS文件描述>

活动 ID 缺失时,控制器历史兼容会尝试从 Referer 查询参数获取。新前端应显式传 ID,不应依赖 Referer。

21.2 返回示例

{
  "import_count": 2,
  "goodsList": [
    {
      "id": 0,
      "sku_id": "SKU001",
      "price_mode": 1,
      "fixed_price": 990,
      "locked_qty": 0,
      "used_qty": 0,
      "version": 0,
      "reason": ""
    }
  ]
}

示例只表示字段结构;价格内部单位应以秒杀 Service 的归一逻辑为准。

22. 秒杀导入模板

模板文件名:秒杀活动商品导入模板.xlsx。

列语义核心规则
物料编码SKU必须能找到物料
价格维度一口价/固定折扣接受文本或枚举值
折扣0-1 小数最多 2 位小数,内部乘 100
折扣价固定价格与价格维度联动
仓库指定仓仅中文、英文、数字
促销数量活动数量单品活动可按业务语义为空
生产日期推算方式无/按月/按天决定倒推字段语义
倒推月数/天数从起始范围与推算方式联动
倒推月数/天数到结束范围起止关系必须合法

必填表头检查允许列换序,并兼容部分导出标题别名,例如“折扣价/固定价/一口价”“仓库/指定仓编号/指定仓编码”。

23. 秒杀导入校验顺序

  1. 活动存在且不处于进行中;新建活动 ID 可为 0。
  2. 本地导入文件存在。
  3. 至少有表头和一行明细。
  4. 表头包含物料编码或物料 ID,以及全部规则字段。
  5. 跳过全空行。
  6. 按真实 Excel 行号解析。
  7. 仓库文本字符校验。
  8. 根据 SKU 或物料 ID 定位物料。
  9. 解析价格维度和日期推算方式。
  10. 归一价格、折扣、数量、仓库、效期规则。
  11. 校验单行业务规则。
  12. 按活动商品唯一键检查文件内重复。
  13. 与活动当前商品检查冲突。
  14. 任一错误存在时整批拒绝。
  15. 清理运行时元数据并返回待保存商品。

24. 秒杀唯一键

当前导入重复键由以下字段组合:

inv_id
+ warehouseNo(仅仓库范围商品)
+ date_calc_mode
+ backward_from
+ backward_to

因此同一物料在不同指定仓或不同生产日期倒推区间可形成不同规则;完全相同组合不允许重复。

flowchart LR
    ROW["标准化商品行"] --> SCOPE{"是否仓库范围?"}
    SCOPE -- 是 --> W["保留warehouseNo"]
    SCOPE -- 否 --> E["warehouseNo置空"]
    W --> KEY["invId|warehouse|dateMode|from|to"]
    E --> KEY
    KEY --> DUP{"文件内/现有商品已存在?"}
    DUP -- 是 --> ERR["带行号拒绝"]
    DUP -- 否 --> OK["加入goodsList"]

25. 秒杀错误策略

秒杀当前是全量原子校验:逐行收集错误,合并当前商品冲突后,如果有任何错误则抛出格式化错误摘要,不返回错误 Excel。

优点:不会出现只导入半批规则。限制:超多错误可能导致响应过长。后续增强可保留全量原子性,同时生成错误文件并只在响应返回前 N 条摘要。

26. 秒杀模板与导出可回导

商品明细导出有 23 列,模板只有 9 列。导入别名兼容商品导出的关键列,因此可在一定程度上“导出后修改再导入”,但要注意:

  • 导出含只读物料/分类/品牌字段,导入会忽略。
  • 导出“一口价”可映射到模板“折扣价”。
  • 导出“数量”可映射到“促销数量”。
  • 活动进行中仍禁止批量导入。
  • 导入只返回新 goodsList,不代表自动覆盖原商品。
  • locked_qty/used_qty 等运行态字段不会从 Excel 回写。

27. 秒杀导出流程

三个 OPS 接口:

接口方法内容行维度
downloadFlashActivityGoodsTemplate空模板只有表头
exportFlashActivityGoods单活动商品明细一商品一行
exportFlashActivity活动列表加商品一活动商品组合一行

活动列表导出最多读取 5000 个活动,再批量查询商品。活动没有商品时仍输出一行活动信息。

sequenceDiagram
    participant OPS as OPS
    participant C as FlashActivity
    participant S as FlashActivitySer
    participant F as FileProvider
    S-->>C: filename + columns + data
    C->>C: 自定义PHPExcel写本地xlsx
    C->>F: uploadFileToOSS
    F-->>C: fileUrl
    C->>C: 删除本地临时文件
    C-->>OPS: JSON下载URL

当前自定义方法把 Sheet 标题设为 ERROR_INFO,即使导出并非错误文件。这不影响数据,但属于可维护性遗留,修改时应回归前端下载契约。

28. 基础资料导入

基础资料常见业务键:

模块主键/匹配键常见动态规则
商品SKU、物料编码、简码品牌分类、单位、价格、套包
客户/供应商联系人编号/名称/手机号类别、结算方式、期初余额
货位仓库+货位码是否默认、停用状态
客户类别价格SKU+客户类别动态类别列、价格精度
商品价格SKU+价格类型最低价、指导价、特殊价

basedata/Invlocation.php 存在多套 importError/importError2/importNewError。改表头时必须定位对应入口,不能只修改其中一个同名错误文件方法。

29. 采购销售导入

订单类导入除字段格式外,还要校验:

  • 单据状态是否允许导入。
  • 供应商/客户是否属于当前 sid。
  • 仓库、货位和门店数据范围。
  • SKU 是否存在且业务可采购/销售。
  • 数量、价格、折扣、税率和金额守恒。
  • 文件内重复商品的合并或拒绝规则。
  • 库存、可退数量、锁定数量。
  • 导入结果是“商品预览”还是“直接生成单据”。

订单导入重试前必须确认是否已经生成业务单号,避免重复单据。

30. 库存盘点导入

盘点、安全库存和货位调整特别关注:

  • 当前盘点单状态。
  • SKU 对应 invId。
  • 仓库/货位是否属于当前站点。
  • 盘点数量允许的小数位。
  • 重复 SKU+货位如何处理。
  • 导入值是“最终数量”还是“增减差额”。
  • 落库是否立即产生库存流水。
  • 错误行成功重试是否会重复盘点。

盘点修复必须与库存一致性手册联动验证实时库存、流水和盘点明细。

31. 报表导出

报表导出通常经历:查询条件 -> 报表 Service -> 汇总/合计 -> reportExportExcel。

reportExportExcel 支持:

  • 主标题和查询条件副标题。
  • 多行/合并表头。
  • 尾部合计行。
  • 最多定义到 AB 的固定列数组。
  • 数字值按默认 Binder 推断类型。

报表导出必须与页面保持:

  • 相同筛选条件。
  • 相同状态排除规则。
  • 相同金额、税额、成本和利润口径。
  • 相同数据权限和门店范围。
  • 相同总计口径。

32. CSV 导出

项目部分列表使用 sys_csv,商品价格有流式 CSV 示例。CSV 更适合大数据,但需处理:

  • UTF-8 BOM,避免 Excel 中文乱码。
  • 逗号、双引号和换行转义。
  • 长数字前导 0。
  • 公式注入。
  • 日期和金额格式。
  • 不能提供复杂样式、合并单元格和多 Sheet。

33. 常用排查命令

# 按业务找模板、导入、导出入口
rg -n "download.*Template|import.*Goods|export.*Goods|导入|导出" application/controllers application/Services application/service

# 确定使用哪代Excel工具
rg -n "ExcelHelper|NewExcelUtil|ExcelService|ExportExcel|PHPExcel|PhpSpreadsheet|sys_csv" application

# 查错误文件返回字段和生成位置
rg -n "errorFile|error_file|fail_file|file_url|createError|importError" application

# 查大文件保护和批次
rg -n "memory_limit|set_time_limit|array_chunk|limit.*5000" application/controllers application/Services application/service

# 秒杀模板/导入/导出
rg -n "buildGoodsImportTemplate|importGoods|buildGoodsExport|buildActivityExport|goodsImportHeaderAliasMap" application/Services/Activity/FlashActivitySer.php

# 报表导出
rg -n "reportExportExcel|exportExcel|sys_csv" application/controllers/reports application/Services/Report

34. 日志定位

一次导入建议记录这些非敏感指标:

request_id
sid(内部日志)
业务类型/template_code/template_version
原始文件扩展名和大小
总行数/空行数/有效行数/失败行数
批次数和每批耗时
生成业务单号或任务ID
错误文件内部标识
总耗时/峰值内存

禁止记录完整 OSS 签名 URL、用户隐私列、整行原始数据和认证信息。

35. 故障一:提示模板表头错误

  1. 下载当前环境最新模板。
  2. 比较第一行标题,注意隐藏空格和换行。
  3. 确认使用正确业务入口,避免把导出文件导入另一模块。
  4. 检查是否插列、合并表头或使用第二行作为标题。
  5. 对标题映射型导入,检查别名是否覆盖旧标题。
  6. 对位置映射型导入,检查列顺序是否完全一致。

36. 故障二:物料编码变成科学计数法

原因通常是 Excel 把长编码当数字。处理:

  • 模板编码列预设文本格式。
  • 用户填写前不要先粘贴为数字。
  • 导出使用显式 string。
  • 后端不要先转 float/int 再转字符串。
  • 错误文件也必须保持文本。

已丢失末位精度的编码无法通过格式切换恢复,必须重新从原始数据填写。

37. 故障三:导入成功数和数据库不一致

flowchart TD
    A["响应成功数不一致"] --> B{"接口是预览模式?"}
    B -- 是 --> R1["确认后续保存是否执行"]
    B -- 否 --> C{"部分成功模式?"}
    C -- 是 --> D["核对错误行/批次回滚"]
    C -- 否 --> E["核对事务提交"]
    D --> F{"重复键被更新还是跳过?"}
    E --> G{"后置任务失败?"}
    F --> H["按业务唯一键查库"]
    G --> H

不要只按文件总行数核对;空行、重复行、更新行和预览行都会影响统计。

38. 故障四:部分成功后重试重复

  1. 确认错误文件是否只含失败行。
  2. 查业务唯一键和已成功记录。
  3. 确认导入语义是插入、覆盖还是 upsert。
  4. 重试只上传错误文件修正后的行。
  5. 如接口没有幂等,先清理重复风险,不要整批原文件重传。

39. 故障五:导出为空但页面有数据

检查:

  • 导出是否收到与页面相同筛选参数。
  • 分页参数是否错误带入导出。
  • 导出权限与页面查询权限是否不同。
  • 导出 Service 是否固定上限或日期范围。
  • 状态、门店、sid、时间边界是否一致。
  • 页面数据是否来自缓存而导出读数据库。
  • 商品筛选时是否先求活动 ID,空集合提前返回。

40. 故障六:导出超时或内存溢出

  1. 查筛选范围和预计行数。
  2. 查是否一次加载全量明细。
  3. 查是否同时保留多份二维数组。
  4. 查列数、样式和合并单元格数量。
  5. 减小查询批次不一定降低 Workbook 内存。
  6. 改为异步分段查询、流式 CSV 或多文件压缩包。
  7. 仅临时提高 memory_limit 不是最终方案。

41. 故障七:下载地址打不开

阶段检查
本地生成文件是否存在、大小是否大于 0
上传 OSS返回数组是否有 fileUrl
临时 URL是否过期、域名是否可访问
本地清理是否在上传成功前被删除
API 契约前端期望 URL 字符串还是对象字段
权限下载接口是否允许当前用户访问

42. 数据修复门槛

导入引起的数据异常修复前:

  • 保存原始文件的受控副本和 hash。
  • 明确导入接口、时间、操作者、sid、请求 ID。
  • 确认是预览、全量、部分成功还是异步模式。
  • 统计成功业务键、失败业务键和重复业务键。
  • 检查是否生成订单、库存流水、财务单或 MQ。
  • 先设计业务层回滚,不直接按行删除主表。
  • 用同一模板版本做修复后验证。

43. 完整回归矩阵

维度用例
文件xls、xlsx、空文件、损坏文件、错误扩展、超大文件
Sheet单 Sheet、多 Sheet、空第一 Sheet、隐藏行列
表头最新、旧版、换序、缺列、重复列、空格换行
单元格空、0、负数、超长、科学计数、公式、特殊字符
业务正常、库内不存在、状态不允许、权限不足
集合文件内重复、库内重复、跨行合计错误
结果全成功、部分成功、全部失败、预览后保存
重试相同文件重复、仅错误行重试、超时后重试
导出空数据、1 行、上限行数、超过 26 列、中文文件名
下载同步流、本地文件、OSS、URL 过期、本地清理失败
安全公式注入、目录穿越、跨 sid 下载、恶意文件
性能行数上限、内存峰值、数据库批次、并发导入

44. 改动风险分级

级别改动风险
高公共 ExcelHelper/ExcelService影响大量业务入口
高上传/下载路径和权限文件泄露或任意文件访问风险
高导入原子性从全量改部分成功重试和数据一致性语义改变
高大批订单/库存直接落库可能产生下游流水和 MQ
中模板表头/列序旧文件兼容和错误文件错位
中金额/数量数据类型可能出现 100 倍或精度错误
中OSS 临时/永久参数下载有效期和存储成本变化
低文件名、列宽、Sheet 标题仍需回归中文下载和前端契约

45. 上线验收

45.1 模板

  • 下载文件可打开。
  • 标题、顺序、必填说明和示例正确。
  • 编码列为文本。
  • 模板版本与后端兼容。

45.2 导入

  • 总数、成功数、失败数可解释。
  • 每条错误带真实 Excel 行号。
  • 全量/部分/预览行为符合约定。
  • 重复上传不会产生不可控重复。
  • 权限、sid、门店范围不越界。

45.3 错误文件

  • 地址可下载且权限正确。
  • 原始值和错误原因完整。
  • 长编码和中文不乱码。
  • 文件会按生命周期清理。

45.4 导出

  • 与页面筛选、状态和合计一致。
  • 金额、数量、日期、长编码格式正确。
  • 空数据和上限数据均可用。
  • 超过 26 列没有截断。
  • OSS 上传失败有明确错误,不返回空 URL。

46. 证据来源

结论代码证据
Excel 仅支持 xls/xlsxRegularConst::EXCEL_EXTENSION、ExcelHelper::readExcel
第一 Sheet 全量 toArrayapplication/Components/ExcelHelper.php
通用导出 A-Z 上限ExcelHelper::exportExcel/exportExcel2
OPS/站管家双上传Services/BaseData/ExcelService.php::upload
模板按表头 hash 缓存ExcelService::downloadTemplate
本地/OSS 两种创建ExcelService::create
PhpSpreadsheet 主子行导出application/Components/ExportExcel.php
秒杀导入只返回预览FlashActivitySer::importGoods
秒杀活动进行中禁导入assertImportGoodsAllowed
秒杀标题别名和换序buildExcelHeaderMap/goodsImportHeaderAliasMap
秒杀 28 列自定义导出FlashActivity::exportFlashExcel
秒杀活动导出上限 5000FlashActivitySer::buildActivityExport
部分历史导入返回错误文件basedata/Invlocation.php、basedata/Import.php
报表多行表头与合计ExcelHelper::reportExportExcel

47. 待环境确认项

  • Web/PHP-FPM 实际上传大小、请求超时、执行超时和内存限制。
  • Nginx/Caddy/网关对大上传和大下载的限制。
  • OSS 临时 URL 有效期、永久文件策略和清理任务。
  • UPLOAD_PATH 磁盘容量、错误文件清理周期和访问控制。
  • 各业务前端实际消费的错误文件字段名。
  • 线上 Excel 库版本、公式计算和日期解析行为。
  • 秒杀错误摘要最大长度及 OPS 网关响应限制。
  • 报表导出实际数据上限和异步导出能力。
  • 公共下载接口目录穿越防护的现网验证结果。

这些环境项未确认前,不应仅凭本地小文件测试判断可以支撑生产大批量导入导出。

请求-日志-数据变更追踪卡

多入口请求链路

场景调用方与入口请求载荷/上下文Controller/ConsumerService/Provider汇合点最终业务事实
同步导入PC 基础资料/业务页上传文件、模板类型、sid、操作人basedata/Import.php 等Import Service、ExcelHelper导入批次 + 行号合法行写业务表,错误行形成反馈
活动导入OPS 活动接口活动 ID、商品 Excelinner/activity/FlashActivity.phpFlashActivity Import Service活动 ID + SKU活动商品批量新增/更新
异步导入大文件上传后 task上传文件路径、任务 ID、批次tasks/Import.phpImport Servicetask/batch ID分批处理并记录进度/错误
导出/模板PC/报表下载筛选条件、列配置、模板类型各 Controller/reportsExportExcel/Report Serviceexport task/request ID生成只读文件,不改业务事实

日志证据矩阵

| 链路段 | 日志来源 | 可检索锚点 | 成功信号 | 失败信号 | 与下一段关联方式 | | --- | --- | --- | --- | --- | --- | --- | | 上传解析 | Controller/ExcelHelper | request_id、文件名、模板类型、批次 | 表头识别、解析行数明确 | 格式/编码/大小/表头失败 | 批次 + 原始行号进入校验 | | 行校验 | Import Service | 批次、sheet/row、业务键 | 有效/无效计数可对账 | 错误未带行号、规则不一致 | 行号关联错误反馈和业务键 | | 批量落库 | Service/DB 日志 | batch ID、分片页、主键、影响行数 | 成功数 + 失败数 = 处理数 | 部分 commit、duplicate、超时 | 业务键回查目标表 | | 导出生成 | ExportExcel/Report | request/task ID、筛选摘要、行数 | 文件生成且行列数正确 | 内存溢出、超时、乱码、公式注入 | 查询条件与文件抽样行关联 |

环节数据变更台账

步骤代码位置事务读取事实写入表/缓存/MQ字段或数量变化回查证据
文件接收Controller + UPLOAD_PATH事务外文件类型、大小、用户临时文件/任务记录上传状态初始化;业务表零变化request ID、文件 hash、任务 ID
解析校验ExcelHelper/Import Service事务外表头、每行单元格、字典值错误集合/临时数据原始行 -> 标准字段;错误行不落业务表行号、字段、错误原因
分批写入领域 Import Service每批/每行事务需明确目标记录、唯一键、当前值目标业务表insert/update old -> new;失败批次 rollback 范围明确batch、业务键、影响行数
后置刷新Cache/Search/MQcommit 后异步成功写入的业务键缓存、索引、MQDB 已变 -> 派生层待刷新消费 ACK、索引/缓存回查
导出ExportExcel/Report Service只读查询结果、列定义下载文件DB 不变;危险公式值转义SQL 条件、行数、抽样字段

子模块追踪:excel-upload-parse 文件上传、解析与表头契约

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
上传解析PC/OPS 上传 xls/xlsx/csvfile ID、模板版本application/Components/ExcelHelper.phpMIME、大小、sheet、固定列序/标题解析阶段 不写业务表;事务外生成临时文件request ID + file ID + rows类型/表头错误零业务写入并清临时文件
表头回查模板错误或列偏移expected/actual headersapplication/Components/ExcelHelper.php模板版本和列索引查询只读 不写file ID + header diff历史模板明确版本,禁止静默错位

子模块追踪:excel-validate-write 三层校验与批量落库

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
校验文件/行/集合三级校验task、rowNo、业务键application/controllers/tasks/Import.php模板、类型、引用、文件内/库内重复校验阶段不写;生成 valid/error 集合task + row/error counts错误带行号字段;部分成功策略须明确
落库校验通过后分批写batch、business keysapplication/controllers/basedata/Import.php当前 DB 和幂等键每批本地事务 none/old -> imported/newtask + batch + affected rows中断只重跑失败批,成功键零增量

子模块追踪:excel-error-file 错误返回与错误文件

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
生成错误校验出现失败行task、rowNo、field/codeapplication/Components/ExportExcel.php脱敏原行和错误列表业务 DB 不变;事务外文件 pending -> ready/failedtask + error rows + file result文件失败不改变导入结果;不回显敏感值
下载用户取错误文件file/task ID、user/sidapplication/controllers/basedata/Import.php文件归属、过期与权限下载只读 不写request ID + file ID + HTTP code地址失效重生成文件,不重跑导入

子模块追踪:excel-download 模板缓存、下载与 OSS

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
模板生成下载业务模板template code/version、sidapplication/Components/ExportExcel.php列定义、下拉数据、缓存版本事务外 old template -> current,业务 DB 不变request ID + template/version + size列合同变更必须失效旧缓存
文件交付流式下载或 OSS URLfile/task IDapplication/Components/ExcelHelper.php状态、归属、URL 有效期下载只读 不写file ID + storage + HTTP codeOSS 配置待环境确认;超时只刷新 URL

子模块追踪:excel-security 公式注入、列数与大文件安全

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
安全校验公式前缀、超列或大文件file、row/column、sizeapplication/Components/ExcelHelper.php单元格类型、列索引、资源上限校验/转义不写业务表file ID + row/column + reason超限尽早失败并清临时文件
资源控制异步批处理/流式导出task、batch、memory/timeapplication/Components/ExportExcel.php水位和已写行文件事务外 processing -> success/failedtask + rows + resource usage从稳定水位重建,避免重复行

子模块追踪:flash-excel 秒杀商品导入导出

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
导入OPS 秒杀商品文件activity/file、SKU/效期/仓库application/controllers/inner/activity/FlashActivity.php -> application/Services/Activity/FlashActivitySer.php活动态、唯一键、价格数量冲突全部通过后本地事务 old goods -> imported goodsrequest ID + activity/file + errors一行失败按合同整批拒绝,不留部分商品
导出回导导出并验证可回导activity/task IDapplication/Services/Activity/FlashActivitySer.php商品、数量显示、仓库名查询只读 不写;文件异步生成activity/task + rows列版本一致;文件失败只重做导出

子模块追踪:base-excel 基础资料导入

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
导入商品、价格、仓位文件sid、task、业务编码application/controllers/basedata/Import.php编码、唯一性、状态、引用每批本地事务基础表 old -> newtask + row + business code引用无效零写入该批;成功批不重做
刷新基础资料提交后changed codes、batchapplication/controllers/inner/CacheManage.php已提交行和派生缓存commit 后事务外失效缓存/索引batch + code + resultDB 正确只补派生层

子模块追踪:trade-excel 采购、销售与盘点导入

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
交易导入采购/销售/盘点文件sid、task、来源/业务单application/controllers/scm/InvPo.php、application/controllers/scm/InvSa.php状态、基础资料、数量、来源唯一性每单本地事务调用领域 Service,none -> createdtask + row + bill/source ID禁止绕过 Service 直插;来源键幂等
验收成功数与 DB 不符task、成功键application/controllers/scm/InvOi.php任务、主明细和库存副作用查询只读 不写task + counts + billNos只重跑失败键,避免重复库存

子模块追踪:report-export 报表与 CSV 导出

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
导出查询页面筛选导出sid、filters、report codeapplication/Components/ExportExcel.php权限、时间/状态/软删口径、总数业务查询只读;任务 pending -> processingrequest/task + filters + rows页面/导出复用口径;空文件先比参数
文件完成Excel/CSV 生成task、row count、file IDapplication/Components/ExportExcel.php批次、列、文件态事务外 processing -> success/failedtask/file + written rows超时用分批流式重建,不改业务数据