定长报文与 JSON 之间的转换适配器
目标
读取布局文件和代码映射表,制作在定长正文(EUC-KR)与 JSON 之间相互转换的转换器,并让它拒绝违反字节长度、代码、编码、小数规则的值。
为什么重要
转换层的失误是悄无声息的。按字符数计算的长度会把后面的字段全部挤后,把未知的代码放行,内部系统会按别的含义去读,经过 float 的利率会少 1,而 Python 的 euc_kr 会把“ddom”换成 8 字节放行。所以要把规则作为数据(布局、映射表)存放,对无法修正的值不要修正,而是以错误码拒绝。
步骤
- 把定义书
/opt/lab/fixtures/eaimw/transform/FXTR0001.md转换到/root/eaimw/xform/FXTR0001.layout。表头为name,length,type,key,map,scale,按定义书的顺序共 12 行。name 是英文名,key 是 JSON 键,map 是代码映射域(没有则留空),scale 在这个报文中留空。 - 把
/opt/lab/fixtures/eaimw/transform/CODES.md转换到/root/eaimw/xform/codemap.csv。表头为domain,external,internal,只取使用中的代码,按 domain、external 的顺序排序(LC_ALL=C sort)。 - 创建
/root/eaimw/xform/f2j.py。python3 f2j.py <레이아웃> <매핑표> <본문파일>(占位符依次为布局、映射表、正文文件)输出一行 JSON,并以 0 结束。N 转为整数,AN、H 只去掉后面的空格,正文按 EUC-KR 严格读取,有 map 的字段通过映射表转换。拒绝时,标准错误的第一行以错误码开头,并以退出码 2 结束:映射表中没有的代码为UNMAPPED,正文长度不一致、不是数字、无法按 EUC-KR 读取为BAD_VALUE。 - 创建
/root/eaimw/xform/j2f.py。python3 j2f.py <레이아웃> <매핑표> <JSON파일>(占位符依次为布局、映射表、JSON 文件)把正文字节输出到标准输出。N 右对齐、前面补 0,AN、H 左对齐、后面补空格,长度按 EUC-KR 的字节数。拒绝(退出码 2):长度、位数超出为OVERFLOW(不截断),映射表中没有的内部代码为UNMAPPED,缺少键、N 不是 0 以上的整数为BAD_VALUE。 - 创建
/root/eaimw/xform/roundtrip.py。python3 roundtrip.py <레이아웃> <매핑표> <본문파일>...(占位符依次为布局、映射表、正文文件)对每个文件输出一行<파일이름>,<결과>(占位符依次为文件名、结果)——f2j 拒绝时为REJECT,f2j→j2f 的结果与原始字节相同时为SAME,不同时为DIFF。并把收件箱中所有FXTR0001-*.body的结果(按名称顺序),连同表头file,result,保存到/root/eaimw/xform/roundtrip.csv。 - 把定义书
/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。 - 改进 j2f,使它把 KS X 1001 预组合形之外的字(例如“ddom”、emoji)以
NOT_KSX1001拒绝(退出码 2)。像汉字、符号这样在 EUC-KR 中为 2 字节的字,则照常接收。 - 用 f2j 转换收件箱
/opt/lab/fixtures/eaimw/transform/inbox/的全部内容。文件名的前半部分(FXTR0001·DPRT0001)就是布局名称。成功时保存为/root/eaimw/xform/out/<이름>.json(占位符为名称,即去掉扩展名.body的名称),被拒绝时不留下 JSON。把结果以表头file,result、按文件名排序、结果为OK或错误码的形式,写入/root/eaimw/xform/summary.csv。
参考
- 按字节处理:正文用
open(f, "rb").read(),字段用body[pos:pos + 길이](占位符为长度),转为字符时用.decode("euc_kr")。长度检查用len(값.encode("euc_kr"))(占位符为值)。 - 错误输出:
print("UNMAPPED ...", file=sys.stderr); sys.exit(2)。字节输出:sys.stdout.buffer.write(본문)(占位符为正文)。 - j2f 可以直接使用 f2j 的
load_layout、load_codemap(from f2j import ...——同一目录下的文件可以直接 import)。 - 常见错误:按字符数计算长度;把超出的值截断后放行;把未知代码原样放行;用 float 来乘利率。
- 评分器每次都会生成新的布局、映射表和值,并作为参数传入。如果把定义书中的字段名写死在代码里,是无法通过的。
- 公共库
/opt/lab/fixtures/eaimw/lib/lhconv.py从第 4 模块开始使用。本实验中要自己制作。
把定义书转换为布局文件
把 /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 文件。