TT Lab
开始
学习 学习路径 课程

构建 EAI 中间层

定长报文与 JSON 之间的转换适配器

在 TT Lab 中继续学习

目标

读取布局文件和代码映射表,制作在定长正文(EUC-KR)与 JSON 之间相互转换的转换器,并让它拒绝违反字节长度、代码、编码、小数规则的值。

为什么重要

转换层的失误是悄无声息的。按字符数计算的长度会把后面的字段全部挤后,把未知的代码放行,内部系统会按别的含义去读,经过 float 的利率会少 1,而 Python 的 euc_kr 会把“ddom”换成 8 字节放行。所以要把规则作为数据(布局、映射表)存放,对无法修正的值不要修正,而是以错误码拒绝。

步骤

  1. 把定义书 /opt/lab/fixtures/eaimw/transform/FXTR0001.md 转换到 /root/eaimw/xform/FXTR0001.layout。表头为 name,length,type,key,map,scale,按定义书的顺序共 12 行。name 是英文名,key 是 JSON 键,map 是代码映射域(没有则留空),scale 在这个报文中留空。
  2. 把 /opt/lab/fixtures/eaimw/transform/CODES.md 转换到 /root/eaimw/xform/codemap.csv。表头为 domain,external,internal,只取使用中的代码,按 domain、external 的顺序排序(LC_ALL=C sort)。
  3. 创建 /root/eaimw/xform/f2j.py。python3 f2j.py <레이아웃> <매핑표> <본문파일>(占位符依次为布局、映射表、正文文件)输出一行 JSON,并以 0 结束。N 转为整数,AN、H 只去掉后面的空格,正文按 EUC-KR 严格读取,有 map 的字段通过映射表转换。拒绝时,标准错误的第一行以错误码开头,并以退出码 2 结束:映射表中没有的代码为 UNMAPPED,正文长度不一致、不是数字、无法按 EUC-KR 读取为 BAD_VALUE。
  4. 创建 /root/eaimw/xform/j2f.py。python3 j2f.py <레이아웃> <매핑표> <JSON파일>(占位符依次为布局、映射表、JSON 文件)把正文字节输出到标准输出。N 右对齐、前面补 0,AN、H 左对齐、后面补空格,长度按 EUC-KR 的字节数。拒绝(退出码 2):长度、位数超出为 OVERFLOW(不截断),映射表中没有的内部代码为 UNMAPPED,缺少键、N 不是 0 以上的整数为 BAD_VALUE。
  5. 创建 /root/eaimw/xform/roundtrip.py。python3 roundtrip.py <레이아웃> <매핑표> <본문파일>...(占位符依次为布局、映射表、正文文件)对每个文件输出一行 <파일이름>,<결과>(占位符依次为文件名、结果)——f2j 拒绝时为 REJECT,f2j→j2f 的结果与原始字节相同时为 SAME,不同时为 DIFF。并把收件箱中所有 FXTR0001-*.body 的结果(按名称顺序),连同表头 file,result,保存到 /root/eaimw/xform/roundtrip.csv。
  6. 把定义书 /opt/lab/fixtures/eaimw/transform/DPRT0001.md 转换到 /root/eaimw/xform/DPRT0001.layout(隐含小数点字段的 scale 为 4),并改进 f2j、j2f,使它们支持 scale。f2j 输出像 "3.2500" 这样写到 scale 位数的字符串,j2f 只接收字符串并用 decimal 计算。小数位数多于 scale 时为 OVERFLOW(禁止四舍五入),不是字符串时(包括 JSON 数字)为 BAD_VALUE。
  7. 改进 j2f,使它把 KS X 1001 预组合形之外的字(例如“ddom”、emoji)以 NOT_KSX1001 拒绝(退出码 2)。像汉字、符号这样在 EUC-KR 中为 2 字节的字,则照常接收。
  8. 用 f2j 转换收件箱 /opt/lab/fixtures/eaimw/transform/inbox/ 的全部内容。文件名的前半部分(FXTR0001·DPRT0001)就是布局名称。成功时保存为 /root/eaimw/xform/out/<이름>.json(占位符为名称,即去掉扩展名 .body 的名称),被拒绝时不留下 JSON。把结果以表头 file,result、按文件名排序、结果为 OK 或错误码的形式,写入 /root/eaimw/xform/summary.csv。

参考

把定义书转换为布局文件

把 /opt/lab/fixtures/eaimw/transform/FXTR0001.md 的 12 个字段转换到 /root/eaimw/xform/FXTR0001.layout(name,length,type,key,map,scale)。

把定义书第 1 节的表原样转换过来。name 是英文名,key 是 JSON 键(大小写保持原样),map 是代码映射列。把长度全部加起来,必须与定义书所写的业务部分合计相同。

把代码定义书转换为映射表

把 /opt/lab/fixtures/eaimw/transform/CODES.md 中使用中的代码转换到 /root/eaimw/xform/codemap.csv(domain,external,internal),并按 domain、external 的顺序排序。

域有 BANK、CHANNEL、KIND 三个。不要载入状态为停用的行——这样含有该代码的报文才会在转换时被拦下。外部代码 01 前面的 0 是值的一部分(就是用 Excel 打开会消失的那个 0)。

定长 → JSON

让 /root/eaimw/xform/f2j.py <布局> <映射表> <正文> 输出 JSON,对未知的代码以 UNMAPPED 拒绝,对格式、编码错误以 BAD_VALUE 拒绝(退出码 2)。

用 open(文件, 'rb') 读取,按布局的长度切成字节。对每个字段做 decode('euc_kr'),半个韩文字和 CP949 专有字符就会以 UnicodeDecodeError 暴露出来。N 要在 strip 之后确认是数字再转为 int,AN、H 只做 rstrip(' ')。不要把映射表中没有的代码原样放行。

JSON → 定长,按字节计数

让 /root/eaimw/xform/j2f.py <布局> <映射表> 输出正文字节,长度超出以 OVERFLOW 拒绝,未知的内部代码以 UNMAPPED 拒绝,缺少键、不是整数以 BAD_VALUE 拒绝。

H 字段的长度不是 len(值),而是 len(值.encode('euc_kr'))。超出时不要截断来凑长度,而是拒绝——按字节截断,韩文字会被从中间劈开。映射要用把映射表翻转过来的字典(内部 → 外部)来查找。N 用 rjust(长度, '0'),其余用 ljust(长度, b' ')。

往返测试

让 /root/eaimw/xform/roundtrip.py 对每个正文区分 SAME、DIFF、REJECT,并把收件箱中 FXTR0001 全部样本的结果保存到 /root/eaimw/xform/roundtrip.csv。

把 f2j 和 j2f 作为函数 import 来使用。f2j 拒绝就是 REJECT,还原出的字节与原件相同就是 SAME。出现 DIFF 的样本可能是转换器的 bug,也可能是对方违反了标准——请用 od -c 查看那个字段的字节。

隐含小数点用 decimal

创建 /root/eaimw/xform/DPRT0001.layout(scale 4),并改进 f2j 输出像 "3.2500" 这样的字符串,改进 j2f 只接收字符串小数并用 decimal 计算。

用 scale 4 读取 0032500,就是 Decimal(32500).scaleb(-4) = 3.2500。转回去时是 Decimal("3.25").scaleb(4) = 32500。float("0.29") * 100 是 28.999999999999996,转成 int 就变成 28。小数位数多于 scale 时,不要四舍五入,而要拒绝。

拒绝预组合形之外的字

改进 j2f,使它把 KS X 1001 预组合形中没有的字(“ddom”、emoji)以 NOT_KSX1001 拒绝(退出码 2),并接收汉字、符号这样的 2 字节字。

先试试在 python3 -c 中,对那个以双辅音开头的韩文字(罗马字写作 ddom)调用 encode('euc_kr')——不会报错,而是得到 8 字节。只要逐字确认 encode('euc_kr') 的长度是否为 2 就行。连编码本身都失败的字(emoji),也用同一个错误码拒绝。

转换整个收件箱

用 f2j 转换收件箱中的所有正文,成功的保存为 /root/eaimw/xform/out/<名称>.json,结果清单写入 /root/eaimw/xform/summary.csv(file,result)。

文件名中“-”之前的部分就是布局名称。退出码为 2 时,标准错误第一行的第一个词就是错误码。shell 重定向即使失败也会留下空文件,所以要删掉被拒绝样本的 JSON 文件。