字段映射不是把两列对上,是把两种业务语义对上。
一、看板少了三列
何嘉树的任务板产品在九月接了一次竞品 A 的迁移,客户在邮件里写得很确定:「状态都是对的,直接导就行。」导入跑完的第二天,客户打开看板,第一句话是:「怎么少了三列?」他截图过来,原来七列并排的看板只剩四列,review 和 archived 整个消失,backlog 也不见了。
以下情境为教学假设,人物、引语和数字不代表经核实的真实案例。
他先把源文件拉出来逐列看。客户那边的七种状态拼写确实没毛病:backlog、todo、in_progress、review、blocked、done、archived,一个拼错的都没有。问题不在拼写,在目标。任务板只有四列:todo、doing、blocked、done。七个值塞进四个格子,必然有三次合并,而合并规则他从没和客户确认过。
客户的「状态都是对的」说的是值本身,不是语义。他把这句话翻译了一遍:这七个词在你的系统里有七个含义,在我的系统里只能落进四个格子,哪些词可以共用一个格子,得你点头。
按行数算,这次改写动了 214 行,占 1,240 行的 17.3%:in_progress 72 行、review 88 行、archived 54 行。剩下几种状态原样落进同名或近名的列,看起来「没动」,其实也经过了映射,只是映射是恒等的。
最疼的是 review。客户团队的习惯是「开发完了先放 review,等产品经理验收」。在原来那套看板里,review 是独立一列,站会时大家看的是「还有多少在等验收」。合并进 doing 之后,这一列消失了,产品经理只能逐条点开看。数字没错,视图没了。
他后来在复盘里写:映射不是翻译,是有损压缩。翻译追求一一对应,映射做不到;能做的只有两件事——把损失写下来,让客户在导入前签字。这句话后来成了他导入流程的第一条规则。
二、七种状态压成四种
映射表本身只有七行,但每一行背后都有一个要回答的问题:这个源状态在目标系统里到底意味着什么。他最后定下来的表是这样。
| 源状态 | 目标状态 | 是否改写 | 判断依据 |
|---|---|---|---|
| backlog | todo | 否(同义) | 都表示「还没开始」 |
| todo | todo | 否 | 同名 |
| in_progress | doing | 否(同义) | 都表示「正在做」 |
| review | doing | 是 | 已提交、未验收,仍在进行中 |
| blocked | blocked | 否 | 目标系统保留了这个状态 |
| done | done | 否 | 同名 |
| archived | done | 是 | 默认按「已结束」处理,需客户确认 |
review 归 doing 而不是 done,理由只有一个:done 在目标系统里的定义是「已验收交付」,而 review 是「已提交待验收」。如果把 review 算成 done,客户的周报会在验收前就把这些任务计进完成量,数字会好看,但会误导排期。宁可让看板少一列,也不能让「完成」这个词变味。
导入契约:十四列到十一个字段的验收表 里那条「状态字段必须附带枚举清单」的规定,就是这次事故之后加的。契约管的是格式,映射管的是语义,两件事不能互相替代。
archived 是唯一一个他没有直接下结论的状态。客户系统里的 archived 既可能是「做完归档」,也可能是「不做了关掉」,这两种含义在目标系统里应该落到 done 和「取消」两个完全不同的地方,而任务板当时没有「取消」。他没有替客户选,而是在预演报告里单独列了一行:archived 54 行,默认映射为 done,如其中有取消项请指出。客户回复说确实混着,最后把其中几行改成了关闭;这几行不在复算里,因为它是人工判断的结果,不是规则算出来的。
映射表要能反查。 每一行都要写清「为什么」,而不是只写「从 A 到 B」。半年后客户问「我的 review 去哪了」,能翻出这句话的人只有写表的人;写清楚,下一个接手的人才不用重新推。
默认值必须显式。 archived 落到 done 是默认,不是事实。默认值写在表里、写进预演报告、让客户确认,三步缺一步,默认就变成了偷偷决定。
不改写的行也要过一遍。 恒等映射看起来安全,其实最容易藏问题:源系统的 todo 可能允许空负责人,目标系统不允许,这一列在映射表上是「否」,在执行时仍然会失败。
三、四种日期与十二行失败
日期列是这次导入里最容易「看起来成功」的一段。1,240 行里混着四种写法,肉眼扫过去都是日期,程序读进去却是四种类型。
| 形态 | 行数 | 占比 | 识别方式 |
|---|---|---|---|
| ISO 8601 | 901 | 72.7% | 形如 2025-03-14,直接解析 |
| DD/MM/YYYY | 214 | 17.3% | 带斜杠,按日/月顺序解析 |
| Excel 序列号 | 113 | 9.1% | 纯数字,落在合理的日期区间内 |
| Unix 秒 | 12 | 1.0% | 十位整数,按时间戳换算 |
识别顺序不能靠猜,要写死:先匹配 ISO 的严格格式,再判断是否为 Excel 序列号,再判断是否为十位 Unix 秒,最后才按 DD/MM 解析。顺序换一下,结果就会变——一个十位整数既可以当成 Unix 秒,也可能被当成 Excel 序列号,只有把区间和位数都写进判据,才不会有第二种解释。
DD/MM 这一种最危险的不是格式,是歧义。03/04/2025 在一个地区是三月四日,在另一个地区是四月三日。他没有让程序去猜,而是在导入前的确认邮件里放了三行样例,请客户确认「日在前还是月在前」。客户的回复只有一句「日在前」,这一句话让 214 行免于整体偏移一天。
解析失败 12 行,占 0.97%。失败原因不是格式不认识,而是值本身不成立:有的月份写了 13,有的日期是 2 月 30 日,还有几行是「待定」「下周」这类自然语言。这 12 行没有被静默丢弃,也没有被填成当天,而是进了失败清单,等客户逐条给值。
负责人这一列有两副面孔。1,182 行是邮箱,58 行只有姓名,加起来正好 1,240。邮箱可以直接对上账号;只有姓名的要靠匹配,51 行匹配成功,占 58 行的 87.9%,剩下 7 行建了占位账号,标记为「待认领」,等客户在设置页里绑定。
匹配之前先归一化:去首尾空格、全角转半角、大小写统一、连续空格压成一个。姓名的匹配还多一步 Unicode 规范化,否则同一个字的两种写法在字节层面不相等,看起来一样的两个名字会匹配失败。
失败清单固定四列:行号、字段、原因、原文。客户下载下来就是一张能直接改的表,改完重传,只跑失败的那几行。清单里不放内部字段名,也不放堆栈,客户看不懂的东西一律不出现。
| 列 | 写什么 | 不要写什么 |
|---|---|---|
| 行号 | 源文件里的物理行号 | 程序内部的循环下标 |
| 字段 | 用户能认出的列名 | 数据库的列名 |
| 原因 | 一句话,说明为什么不收 | 异常类名与堆栈 |
| 原文 | 该行该列的原始值 | 被截断或改写后的值 |
四、先归一化,再校验,最后写库
类型问题看起来零散,处理次序却只有一条:先归一化,再校验,最后才写库。顺序颠倒,前面两步就白做。
归一化负责把「同一种东西的不同写法」收成一个;校验负责判断收完之后是否合法;写库只接受已经合法的值。很多实现把三步揉在一起,解析的同时就插入,一旦某行失败,事务里已经躺了半条记录,回滚和重试都变得麻烦。
复算:这次映射的规模与失败率放在一起看。
状态 7 → 4:backlog/todo→todo、in_progress/review→doing、blocked→blocked、done/archived→done
改写 72 + 88 + 54 = 214 行 = 214 ÷ 1,240 = 17.3%
日期 4 种:ISO 901(72.7%)+ DD/MM 214(17.3%)+ Excel 113(9.1%)+ Unix 12(1.0%)
解析失败 12 行 = 12 ÷ 1,240 = 0.97%
负责人:邮箱 1,182 + 仅姓名 58 = 1,240;姓名匹配 51 = 51 ÷ 58 = 87.9%
1,240 行 × 14 列 = 17,360 格,其中空值 2,914 格,占 16.8%。空值不是错误,但必填三列的空是:缺失 7 行,占 0.56%。这三列必须在契约里写死,导入器才有权拒绝整行,而不是替用户填一个空字符串。
映射与类型这一段,在人工代导入的 270 分钟里对应的是清洗的 90 分钟。90 分钟不算长,但它决定了后面 120 分钟的执行会不会返工:映射表错一行,执行完再改,就得整批重来。
去重与合并:八十六行重复、两种判据与一次误合并 会接着处理「同一行进来两次」的问题。两件事的边界很清楚:本篇保证一行进来之后含义正确,去重保证同一行不会进来两次。
失败行必须可下载,这一条没有例外。不能下载的失败清单等于没有清单,客户只能拿着截图来找你,你只能打开数据库一条条查。清单的格式也要稳定,列名和顺序不要随版本变,客户可能已经写好脚本在处理它。
写库之前还有一道检查容易被跳过:把映射后的分布打出来,和目标系统的枚举对一遍。七个源状态应该变成四个目标状态的某个分布,如果某个目标状态是 0 行,先别高兴,很可能是映射写错了一个分支。
五、行动清单与复算验收
八条按「先列表、再定判据、最后留证据」的顺序做,一个人半天能做完第一版。
- 导出源系统的全部枚举值,逐个数清,不靠记忆。
- 为目标系统的每个状态写下语义定义,一句话,可被客户读懂。
- 出一张七行的映射表,每行写「是否改写」与判断依据。
- 把有歧义的映射(如 archived)单独列出,请客户书面确认。
- 为日期列写死识别顺序,并对歧义格式取样例请客户确认。
- 负责人先归一化再匹配,匹配不上的建占位并标记待认领。
- 失败清单固定四列,可下载、可重传、可只重跑失败行。
- 写库前打印映射后的分布,与目标枚举逐项对数。
清单可以压成一句话:值要归一,义要确认,错要留痕。三件事里最容易被省掉的是第二件,因为它需要客户回一句话,而代码不会因为你没问就报错。
验收标准:任取一行导入后的任务,都能说出它来自哪个源值、经过了哪条映射、为什么落到这个目标值;说不出来就是映射没做完。
使用边界与实操验收
边界一:不替用户猜业务语义。 archived 是否等于 done、review 是否算完成,这些是客户的业务定义,不是技术选择。导入器可以提供默认值,但默认值必须被看见、被确认。
边界二:本篇不做 schema 迁移。 表结构怎么加列、怎么回填、怎么双写,属于架构组的范围;本篇只处理「值从一种表示变成另一种表示」。
边界三:本篇不涉及法定导出与删除。 数据可携带权下的导出格式、删除请求的响应时限,按隐私组的口径执行,映射表不能替代那些义务。涉及法定权利时以适用法域与专业意见为准。
边界四:本篇不做去重与合并。 一行进来两次是下一篇的问题;本篇的判据只保证单行含义正确,不保证集合唯一。
边界五:映射不承诺无损。 七变四一定有信息损失,能做的是把损失写明、让客户签字,而不是假装四个格子能装下七种含义。金额、税率、时区这类高风险字段,映射后必须抽样人工核对,不能只看程序报成功。
关键参考与核验范围
- RFC 3339:日期和时间格式 :用于核对 ISO 8601 子集的写法与时区偏移。
- W3C CSV on the Web 入门 :用于核对表格数据的列类型、空值与日期解析约定。
- Unicode TR15:规范化 :用于核对姓名匹配前的 Unicode 规范化步骤。
- IANA 时区数据库 :用于核对带时区时间戳的换算与夏令时边界。
- Jira CSV 导入 :用于对照商业产品在字段映射与失败行处理上的做法。
以上链接于 2026-10-26 实测可公开访问;未核验版本、价格与配额。核验日期:2026-10-26。
本文的映射表、日期分布与失败率来自一个 1,240 行的教学样例,不代表任何具体产品的导入行为。不同产品的状态枚举、日期默认格式与负责人模型都不同,落地前应以目标系统的官方文档为准;涉及语义判断的部分,以客户书面确认为准。
系列导航
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。