导入契约:十四列到十一个字段的验收表

客户发来的 14 列 CSV 打开是乱码,第三列把邮箱和姓名挤在一格。本篇给出 14 列到 11 个字段的映射表与五条导入契约,逐条列出失败样例和拒绝方式,再用 17,360 格与 16.8% 空值说明:导入失败多半不是代码问题,而是契约没写。

导入失败很少是代码写错了,多半是契约没写。

一、打不开的十四列 CSV

2026 年 9 月,陆青禾收到客户发来的 export.csv。双击打开是一屏乱码;用文本编辑器看,第一行最前面有一个看不见的 BOM,第三列表头写着「负责人(邮箱/姓名)」,每一格是「张三 zhangsan@example.com 」这种格式。

以下情境为教学假设,人物、引语和数字不代表经核实的真实案例。

他先用 Excel 打开,中文全是问号;换成文本编辑器才看出文件是 GBK 编码。客户说「我们系统导出来就这样」,语气里没有抱歉,因为在他的认知里,这就是标准导出。

第三列更麻烦:邮箱和姓名挤在一格,有的人只有姓名,有的人只有邮箱,还有 7 行整格为空。任务板的负责人是必填字段,这 7 行无论怎么解析都进不去。

他写了个按逗号切分的脚本,第二行就切错了:一个任务标题里带逗号,外面包着引号,简单 split(',') 把一行切成五段,后面的列全部错位。

改用 CSV 解析库之后,引号和换行的问题解决了,但新的问题冒出来:客户前后发过 3 个版本,其中 2 个表头不一致,一个列序不同,一个多了一列。脚本按位置取值,第二个文件把「优先级」读成了「截止日期」。

那天下午他没有继续写代码,而是把三份文件并排放在一起,开始倒推:一份合格的文件应该长什么样。编码、表头、必填列、类型、重复,这五件事必须在文件到达之前说清楚。

他把这份说明命名为「导入契约」,附在迁移确认邮件后面,要求客户导出时对照检查。第一版只有半页,后来每遇到一个新问题就补一条。

契约不是给工程师看的接口文档,而是给客户看的交件标准。

二、十四列与十一个字段的归属

客户的源文件有 14 列,任务板的目标表有 11 个字段。两边不是一一对应,先把每一列的归属列清楚,后面的校验才有依据。

源列(14)目标字段(11)处理方式
task_idtask_id直接
titletitle直接
descriptiondescription直接
statusstatus直接,值映射在下一层
prioritypriority直接
due_datedue_at直接,需解析日期
created_atcreated_at直接
updated_atupdated_at直接
project_nameproject_id直接,按名称查项目
assignee_emailassignee_id合并
assignee_nameassignee_id合并
notes—丢弃,内部备注
legacy_flag—丢弃,旧系统标记
owner_dept—丢弃,导入不需要部门

9 个直接列按名称或简单查表落到 9 个目标字段;assignee_email 与 assignee_name 合成 1 个 assignee_id;notes、legacy_flag、owner_dept 三列不进入目标表,成功行不留存原值,只有失败行才在清单里带上。第一节那个把邮箱和姓名挤在一格的第三列,在契约里被拆成两列,合并格式不再接受,因为拆分规则会随客户系统版本变化。

第 11 个字段是生成的:external_id 由来源标识和 task_id 一起做哈希,写进唯一索引。它不来自任何一列,却决定了同一份文件重跑时会不会重复插入。

合并列是最容易出错的地方。assignee_email 单独看是唯一标识,assignee_name 单独看会重名;先按邮箱匹配账号,匹配不到再用姓名,姓名也没有命中就建占位账号并标记待认领。这条规则要写进契约,不能只留在代码里。

丢弃列要写明「为什么不导入」。客户看到 owner_dept 没进来会问,提前写一句「部门信息不参与任务归属,导入后不保留」比事后解释省事,也符合最小化收集的口径。

表格之外还有一条:列名大小写敏感。Task_ID 和 task_id 在客户的文件里都出现过,前者被当成未知列,整份文件拒绝。

三、五条契约与三级拒绝

契约一共五条,每条都要有硬要求和拒绝方式。只写「请使用 UTF-8」不够,因为客户并不知道自己的文件是什么编码。

条款硬要求失败样例拒绝方式
编码UTF-8,允许 BOM 但解析前剥离GBK 文件打开是乱码;BOM 未剥离时表头匹配失败整文件拒绝
表头第一行 14 个列名,顺序与大小写固定列序不同、多 1 列、Task_ID 改名整文件拒绝
必填task_id、title、status 三列不可为空7 行缺必填,整格为空或只有空格逐行拒绝
类型枚举与日期按白名单解析priority 写成「高」;日期写成「下周三」逐行拒绝
幂等task_id 来源内唯一,external_id 稳定同一文件重跑后行数翻倍重复行跳过并记账

五条里最容易妥协的是表头。客户说「就多一列,你忽略掉不就行了」,但忽略一列意味着解析器要猜;这次能猜对,下次列序变了就猜错。表头不符的正确处理是整文件拒绝,并把期望的 14 个列名原样返回。

拒绝分三级:整文件拒绝用于编码和表头,一行都不写;逐行拒绝用于必填缺失和类型错误,好行照常入库,坏行进失败清单;警告放行用于可选列为空,只记录不阻断。

失败清单至少四列:行号、列名、原值、原因。行号用文件里的物理行号,不是解析后的序号,客户才能在自己的 Excel 里定位。原值要原样保留,不要转义成 [NULL] 之类看不懂的标记。

必填三列选 task_id、title、status 的理由是:没有 task_id 无法幂等,没有 title 任务没有名字,没有 status 看板不知道放哪一列。其他列都可以为空,包括负责人。

类型与单位要写成白名单。priority 只接受 P0 到 P3;due_date 接受 ISO 8601、DD/MM/YYYY、Excel 序列号和 Unix 秒四种格式,其余一律进失败清单。自然语言日期不在契约里,因为解析结果不可复现。

幂等键单独一条,因为它跨越多次导入。同一份文件里 task_id 重复出现就是重复行;external_id 用来源加 task_id 哈希,保证跨文件也稳定。没有这一条,客户重跑一次导出就是灾难。

四、17,360 格与 16.8% 的空值

把客户的矩阵摊开:1,240 行 × 14 列 = 17,360 格。空值 2,914 格,占 16.8%。只看这个比例,容易以为文件质量很差,但空值集中在 3 个丢弃列和可选的描述列,真正阻断导入的必填缺失只有 7 行,占 0.56%。

这组数字说明「导入失败」要分层:文件级失败一条就拒绝整份;行级失败只影响那一行;单元格级空值大多数不是错误。三层混在一起统计,就会得出「16.8% 的数据有问题」这种没法行动的结论。

表头不符的有 2 个文件:一个列序不同,一个多 1 列。修复方式不是让解析器兼容,而是把标准表头发回去让客户重导;修复后 14/14 列可读。这 2 个文件如果靠位置解析放过去,错列会静默写进数据库,比拒绝难查得多。

契约和 schema 是两件事。schema 描述数据库内部的列名、类型、约束和索引;契约描述客户交来的文件长什么样。把 assignee_id 这种内部字段名写进给客户的模板,等于让客户替你维护外键;客户给不出,就会在模板里乱填。

下一篇要处理的是值层面的映射:7 种状态怎么并成 4 种、4 种日期格式怎么识别、负责人姓名匹配不上怎么办。那是契约之后的第二道关;契约只保证文件能读进来,不保证值有业务含义,详见字段映射与类型:七种状态、四种日期与十二行失败 。

复算:

源 14 列 = 直接 9 + 合并 2 + 丢弃 3
目标 11 字段 = 直接 9 + 合并 1 + 生成 1(external_id)
矩阵 1,240 × 14 = 17,360 格;空值 2,914 = 16.8%
必填 3 列缺失 7 行 = 7 ÷ 1,240 = 0.56%
表头不符 2 个文件(列序不同、多 1 列);修复后 14/14 列可读

17,360 格里 16.8% 是空值,但空值不等于错误;真正需要客户重导的是 2 个表头文件,真正需要逐行处理的是 7 行必填缺失。把这三个数字分开,才能决定哪一步该自动化、哪一步该找人。

本篇只讨论文件进入解析器之前的那一层。数据库 schema 怎么改归架构组的数据模型与迁移:三十四张表与一次只能动一处的改动 ;法定导出与删除归隐私组;备份与恢复归资产组。三件事都不在导入契约里。

五、行动清单与复算验收

  • 收到第一个文件之前,先写一页导入契约,列出 14 列与 11 个目标字段。
  • 用文本编辑器确认编码,剥离 BOM,非 UTF-8 文件先转码再解析。
  • 固定表头 14 列的名称与顺序,缺列、多列、改名一律整文件拒绝。
  • 标出 3 个必填列,缺失行进入失败清单,不静默补空。
  • 为每个枚举与日期列写明允许的值和格式,不接受自然语言。
  • 用来源加 task_id 生成 external_id,并在数据库建唯一索引。
  • 失败清单至少包含行号、列名、原值、原因四列,且可下载。
  • 拿同一份文件重跑一次,确认新增为 0 行。

验收标准:14 列全部有明确归属,11 个目标字段全部有来源,3 个必填列缺失的行全部进入可下载失败清单,且同一文件重跑新增 0 行。

八条里最容易被跳过的是第一条和第七条。没有契约,解析器只能拿到什么猜什么;没有可下载的失败清单,客户只能反复发邮件问「到底哪一行错了」。

契约写完那天,他把半页说明放进迁移确认邮件,附上一个只有表头的 template.csv。之后再收到的文件,乱码少了,表头对了,他的下午也回来了。

使用边界与实操验收

契约不是 schema。 契约描述客户交来的文件,schema 描述数据库内部结构;把内部字段名、外键和索引暴露给客户,只会让模板变成第二个需要维护的接口。

契约不替客户猜业务语义。 archived 是否等于 done、空白负责人是否等于未分配,这些要问清楚再写进映射;猜错一次,后面每一条数据都跟着错。

编码问题不能靠 try/except 吞掉。 解码失败就整文件拒绝并提示转码,不要逐行跳过;跳过的那几行往往正是客户最在意的记录。

必填列的判断要写进契约。 写在代码里,客户看不到;写在契约里,客户导出时就能自查,失败清单也会少一大半。

本篇不做什么。 字段值映射归本组第三篇,数据库 schema 迁移归架构组,法定导出与删除归隐私组,备份与恢复归资产组;四者边界不要混。

关键参考与核验范围

以上链接于 2026-10-26 实测可公开访问;未核验版本、价格与配额。核验日期:2026-10-26。

上述文档只用于核对 CSV 引号规则、表头模型、字符规范化与幂等设计;14 列、11 个字段、17,360 格与全部比例均取自本组教学基线,与文档内容无对应关系。

系列导航

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「Indie」更多文章

  1. 开源项目验收:十项能力、四维评分与三种不该开源的项目
  2. 治理、fork 与退出:17 位贡献者、82% 的提交与一次改协议的代价
  3. 双许可与企业版:九家询价、两家成交与 4,800 美元