装载之后才发现错行,已经太晚了
一句话总结
从对方机构收到的清算和转账指令文件,在加载之前要先用格式契约来检查,只要有一行对不上,就把整个文件退回去。在门口拒绝,永远比加载之后再对账便宜。
为什么需要它
银行信息系统的一天,从文件开始,也以文件结束。对方机构凌晨通过 FTP 放上来一整天的指令文件,我们接收并加载,晚上再用同样的方式把我们这边的结果送回去。这个关口出事故的方式总是很相似。文件到了,加载批处理以“成功”结束,可是对方发来的汇总尾记录(trailer)里的笔数,与我们放进去的笔数不一样。
接下来才是真正的问题。已经进去的那些行,被别的批处理拿走了。余额变了,通知发出去了。要想撤回,就得写更正单据,重新数清哪些行进去了、哪些行没进去。如果当初没有接受这个文件,5 分钟就能结束的事,会变成一整天的对账和道歉电话。
所以接收路径上必须站一个守门程序。守门程序做的事很简单:只看这个文件有没有遵守我们约定的格式契约。如果没有遵守,就一行也不加载,并附上原因退回。没有“只放进去一半”这个选项。
工作原理
格式契约通常有四层。
第一,文件外观。名称规则、编码、行尾。韩国金融业的对外文件中,仍然保留着很多定长(fixed width)的 EUC-KR 系列文件。在 Python 里使用 cp949 编解码器,标准编码表 把这个编解码器的别名写作 949、ms949、uhc,并把语言归为 Korean。一个韩文字符在 CP949 中占 2 个字节,在 UTF-8 中占 3 个字节,这个差别会直接连到下一层。
第二,记录结构。如果是定长,就看一行恰好是多少字节。最常见的失误,是按字符数来填充。如果收款人姓名的位置是 20,那它不是二十个字符,而是二十个字节。以一个由三个韩文字符组成的姓名(例如 Kim Seojun 的韩文写法)为例,如果把它当作三个字符,用十七个空格去补,那么这一行就会偏离规定的宽度,后面的字段会整体错位。正如 Unicode 使用指南 所说明的,Python 字符串的长度是码点的个数,而写进文件的是编码之后的字节。切分的位置必须在 bytes 上,而不是在 str 上。
如果是 CSV,标准是 RFC 4180。这份文档的 Category 是 Informational,并且自己声明“不规定任何互联网标准”,但它作为事实上的共识被广泛使用。核心是三条。含有换行、双引号、逗号的字段,要用双引号括起来。括起来的字段里面的双引号,写成两个双引号。记录以 CRLF 结尾。所以一旦用 line.split(",") 来切,收款人姓名中带逗号的行,字段就会多出一个,账号的位置会冒出姓名的碎片。交给 csv 模块,引号内的逗号和换行就不会被当作分隔符。
第三,表头与汇总尾记录(trailer)。把文件末尾 T 记录里写的笔数和合计,与实际数出来的值核对。这是抓住传输中被截断的最便宜的装置。笔数对得上,合计却不同,说明不是被截断,而是值被改了,这是两种不同的事故,所以错误码也要分开设。
第四,字段级规格与重复。检查交易编号的形态、账号格式、金额范围。而且,同一个文件来两次不是例外,而是日常。对方如果没收到我们的响应,就会再上传同一个文件。文件的 sha256 相同,是单纯的重发,序列号相同而内容不同,那就是事故。用 hashlib 留下文件哈希,就能把这两种情况分开。
수신함 ──▶ 이름·인코딩 ──▶ 레코드 폭·인용 ──▶ 트레일러 ──▶ 필드 ──▶ 중복
│ │ │ │ │
└──────────── 하나라도 걸리면 파일 전체를 거절 ──────┘
在现场相遇的样子
第一种是悄悄损坏的编码。规格是 CP949,对方机构换了系统之后,开始用 UTF-8 发送。运气好的话,解码会以异常结束,当场被发现。运气不好的话,一部分字节被解释成别的字符,只有姓名乱掉,却照样被放了进去。所以不能只看编码,还要同时看字节宽度。一个韩文字符从 2 个字节变成 3 个字节,行的长度就会变,宽度检查可以把编码事故再抓一遍。
第二种是部分加载的诱惑。“998 笔都是好的,就因为 2 笔,要全部退回吗”,这种话一定会出现。必须退回。如果只放进 998 笔,对方的尾记录 1000 笔与我们的账本 998 笔就会一直对不上,第二天对方发来的更正文件里,那 2 笔又会进来,这次就成了重复。全有或全无,事故一天就能结束。
第三种是没有原因的拒绝。如果只写“格式错误”就退回去,对方的负责人不知道该改什么。如果把“哪一行的哪个字段因为什么被拦下”做成机器可读的拒绝文件一并退回,对方就能把这个文件直接挂到自己的批处理上。
实际工作中真正重要的事
- 拒绝以文件为单位。一旦允许按行拒绝,对账就永远结束不了。
- 判定要附上观测值。像“尾记录合计 000000000050000 · 实际 46000”这样,把两边都写出来。
- 错误码就是契约。批处理自动化根据错误码来选择下一步行动。尾记录不一致是请求重发,重复是忽略,编码问题是呼叫对方系统的负责人。
- 重发是正常行为。不要阻止,要区分。哈希相同是重发,只有序列号相同是冲突。
- 守门程序不修改文件。一旦开始替对方裁掉空格、转换编码,对方机构就永远不会知道自己的文件违反了规格。
下一项实验要做什么
下一个实验所使用的 80 字节记录布局、错误码名称和退出码,是本实验的假设。每个机构的规格都不同,在现场作为原本依据的,是与对方机构互相交换的约定文档。不变的,是检查各层的顺序,以及全有或全无的原则。
亲手做出四家对方机构发来的一整天收件箱,并在它前面一步步搭起守门程序。从尾记录核对开始,接着加上定长的字节宽度、与规格不同的编码、RFC 4180 的引用、字段校验与拒绝文件,再到重发判定。评分器每次都会用不同的机构代码和笔数,做出它自己的收件文件,运行你的守门程序,并对照判定。最后处理你自己的收件箱,留下接收报告。