在装载之前把收到的文件退回去
目标
做出一个守门程序 filegate.py,在加载之前检查对方机构发来的定长和 CSV 接收文件,一旦不符就拒绝整个文件。判定以机器可读的报告、退出码,以及要退还给对方的拒绝文件的形式留下来。
为什么重要
加载之后再去找错误的行,永远比在门口拒绝更贵。已经进去的行,被别的批处理拿走了,要撤回,就会跟着出现更正单据和道歉电话。即使 998 笔完好,只要有 2 笔与规格不符,这个文件也一行都不放进去。如果只放进去一半,对方的尾记录和我们的账本就永远对不上。 定长文件的宽度不是字符数,而是字节数。一个韩文字符在 CP949 中占 2 个字节,在 UTF-8 中占 3 个字节,所以编码一变,行的长度本身就变了。CSV 不能把引号内的逗号和换行当作分隔符。 评分器不会相信你写的文字。它会在临时目录里摆好它自己做出的接收文件,运行你的脚本,并对照判定和错误码。机构代码、笔数和金额每次运行都不同。
步骤
- 创建并运行
/root/bankfile/gen_inbound.py,在/root/bankfile/inbound/中生成四家对方机构一整天的 5 个文件。 - 让
/root/bankfile/filegate.py对照 CSV 接收文件的骨架与汇总尾记录的笔数和合计,并输出报告和退出码。 - 在 filegate.py 中加入定长(.txt)的处理。要按字节而不是字符数,来量一行是不是恰好 80 个字节。
- 让 filegate.py 把以与规格不同的编码送来的文件,用 ENC 拒绝。每个文件读取时使用的编码都写入报告。
- 把 filegate.py 的 CSV 解析按 RFC 4180 改好。被引用的逗号和换行不是字段分隔符。
- 在 filegate.py 中加入字段级校验,并为每个被拒绝的文件留下拒绝文件。只要有一个错误,这个文件就一行都不加载。
- 让 filegate.py 把同一机构、同一序列号的重发用 DUP 拒绝。哈希相同是 resend,不同则是 conflict。
- 处理你自己的收件箱,留下
/root/bankfile/gate_report.json、/root/bankfile/rejected/和/root/bankfile/receipt.md。
参考
- 接收规格:名称是
IN-<YYYYMMDD>-<기관코드 6자리>-<꼬리>.txt(占位符依次为 6 位机构代码、尾部)或.csv。.txt是 CP949 定长,.csv是 UTF-8 的 CSV,行尾为 CRLF。 - 定长的一行恰好是 80 个字节。H =
H(1) + 机构代码(6) + 文件日期(8) + 序列号(3) + 空格(62)。D =D(1) + 交易编号(12) + 收款人姓名(20) + 账号(14) + 金额(13,左补 0) + 空格(20)。T =T(1) + 笔数(6) + 合计(15) + 空格(58)。宽度全部以字节计。 - CSV 记录是
H,기관코드,파일일자,일련번호(占位符依次为机构代码、文件日期、序列号) /D,거래번호,수취인명,계좌,금액(占位符依次为交易编号、收款人姓名、账号、金额) /T,건수,합계(占位符依次为笔数、合计)。 - 字段规格:交易编号是
TR+ 10 位数字,在文件内唯一,账号是110-0000-00000,金额是 1 以上、小于 10000000000 的整数,收款人姓名不能为空。 - 运行约定:
python3 /root/bankfile/filegate.py --in <수신디렉터리> --report <보고서.json> --reject <거절디렉터리>(占位符依次为接收目录、报告文件、拒绝目录) - 报告:
{"summary": "accept|reject", "accepted": [이름], "rejected": [이름], "files": [{"name", "verdict", "encoding", "records", "total", "sha256", "errors": [{"code", "line", "detail"}]}]}(占位符为文件名)。records是 D 记录的个数,total是金额之和。 - 错误码:
ENCWIDTHLAYOUTTRAILER_COUNTTRAILER_TOTALFIELDDUP。文件级错误的line为 0。如果交易编号重复,则指向后出现的那一行。 - 退出码:全部接收为 0,只要有一个被拒绝为 2,如果无法读取接收目录,则不生成报告,为 3。
- 拒绝文件:每个被拒绝的文件对应
<거절디렉터리>/<파일이름>.reject.csv(占位符依次为拒绝目录、文件名),表头是line,code,detail。 - 亲自测试:
python3 /root/bankfile/filegate.py --in /root/bankfile/inbound --report /tmp/r.json --reject /tmp/rej; echo $? - 常见错误:按字符数来填充韩文姓名,用
split(",")切 CSV,只去掉出错的行而加载其余的行,守门程序替对方修改文件。
生成一整天的收件箱
创建并运行 /root/bankfile/gen_inbound.py,在 /root/bankfile/inbound/ 中生成 5 个文件。它们是:定长(CP949)120 笔的文件、把它连字节都原样再发一次的重发件、CSV 90 笔(至少有 3 行收款人姓名中带逗号)、尾记录笔数比实际少 1 的 CSV 60 笔,以及虽是定长却以 UTF-8 送来的文件。
名称是 IN-<YYYYMMDD>-<기관코드 6자리>-<꼬리>.txt|.csv(占位符依次为 6 位机构代码、尾部)。定长的宽度是字节,所以要先用 text.encode('cp949') 量好,再补空格。重发件只要把同样的字节换个名字再写一遍就行。CSV 里带逗号的姓名,请用双引号括起来。
先核对尾记录的笔数与合计
让 /root/bankfile/filegate.py 确认 CSV 接收文件的 H、D、T 骨架,并把尾记录里的笔数和合计,与实际数出来的值核对。输出报告和退出码 0、2。
笔数不一致和合计不一致是不同的事故,所以要分别设码(TRAILER_COUNT、TRAILER_TOTAL)。报告中的 records 和 total,不是尾记录里写的值,而是你自己从 D 记录里数出来的值。评分器每次都用不同的机构代码和笔数来测试。
定长的宽度是字节,不是字符
在 /root/bankfile/filegate.py 中加入 .txt 定长的处理。如果一行按 CP949 不是恰好 80 个字节,就用 WIDTH 拒绝,并留下出错的行号。
len(line) 是字符数,而规格所说的是 len(line.encode('cp949'))。切分字段的位置也在字节上,而不是在字符串上。只有按字符数填充了韩文姓名的那一行,宽度才会对不上。
拦住以与规格不同的编码送来的文件
让 /root/bankfile/filegate.py 把 .txt 按 CP949、.csv 按 UTF-8 来读,如果不能用该编码解码,就用 ENC 拒绝。每个文件读取时使用的编码写入报告的 encoding。
bytes.decode() 失败时会抛出 UnicodeDecodeError。把异常的 start 和 reason 写进 detail,对方负责人就能知道是在哪个字节处乱掉的。守门程序不能自动换用别的编码去读。
引号内的逗号不是分隔符
把 /root/bankfile/filegate.py 的 CSV 解析按 RFC 4180 改好。含有被引用的逗号和换行的文件,其 records 和 total 必须正确,引号没有闭合的文件必须拒绝。
标准库的 csv 模块原样实现了引用规则。要传入字符串,就用 io.StringIO 包起来。如果引号没有闭合,后面的行就会被整个吸进同一个字段里,使字段个数对不上。
字段校验、拒绝文件,以及禁止部分加载
在 /root/bankfile/filegate.py 中加入对交易编号、收款人姓名、账号和金额的校验,并为每个被拒绝的文件,以表头 line,code,detail 留下 <거절디렉터리>/<파일이름>.reject.csv(占位符依次为拒绝目录、文件名)。只要有一个错误,这个文件就不能进入接收列表。
交易编号在文件内必须唯一,如果重复,就指向后出现的那一行。从定长中切出来的字段,要去掉左右的空格再检查。拒绝文件必须能被对方机构的批处理直接读取,所以要以表格而不是人写的句子来输出。
区分重发的文件与冲突的文件
让 /root/bankfile/filegate.py 把与已经接收的机构、序列号相同的文件,用 DUP 拒绝。文件哈希相同,就在 detail 中写 resend,不同则写 conflict,并在报告中留下 sha256。
序列号在表头记录里。只需要记住前面已被接收的文件——被拒绝的文件并没有加载,所以之后来的同号文件不算重复。哈希要用 hashlib.sha256,对整个文件的字节来求。
处理自己的收件箱并提交接收报告
处理你自己的收件箱,留下 /root/bankfile/gate_report.json 和 /root/bankfile/rejected/,并在 /root/bankfile/receipt.md 中写下 ## 수신 요약 ## 거절한 파일과 사유 ## 재전송으로 판정한 파일 ## 적재하지 않은 이유 ## 상대 기관에 요청할 것 五个小节。
直接使用前面步骤中做好的 /root/bankfile/filegate.py。摘要里要用数字写出接收笔数、拒绝笔数,以及实际将要加载的金额合计。被拒绝的文件要把名称原样写出来,对方才找得到。评分器会重新读取 /root/bankfile/inbound,与你的报告对照,所以报告不要手写,请使用 filegate.py 输出的结果。