第四十三章 · 归零¶
万钧的合作方技术资料在一月三日晚间更新了版本号。从 v2.3 改到 v3.0。接口规范改了一半——结算回调接口的签名算法从 HMAC-SHA256 换成了 RSA-SHA256,但文档正文还挂着旧版本的示例代码。十七个接口,六个改了签名方式,文档只标了三个。
陈稳把邮件往下翻了两页。没有变更说明。
他把两个版本的接口定义做了逐行对比。十个接口的请求体字段名有变化——下划线改驼峰。他把改动标红,附在邮件末尾,发送给万钧的邮箱。正文三行:
「六个接口签名方式变更,文档标注覆盖三个。 十个接口字段名不一致。 建议:统一命名规范后发版。」
万钧没有回。
十二月二十九日。他在结算组最后一天。
上午把推广方案汇金线的数据口径发给张磊确认。张磊回了一张新表——汇金线十二月实际结算量四万五千六百笔,比预估多六百笔。陈稳把这条数据写进方案版本号 v3.2,commit message 一行:「汇金线十二月数据确认。口径:线下台账+业务线回执。」
下午整理桌面。键盘下面有一张黄色的便利贴,上面是他自己写的:「10.14.1.88 → 张毅(年假排除) → 操作人用张毅的机器 → 同一块表 → 两个半月。」字迹已经磨得有些模糊。他没有扔,折了一下放进背包最里层。旁边就是那三张 A4 打印件——顾源留给他的那封邮件。
工位左上角有一圈浅色的印子。不是老梁那种杯底的白印,是显示器底座压出来的。他用湿布擦了一下,没擦掉。
蒋辉走过他工位的时候停了一步。「走了?」
「明天最后一天假。一月五号去报到。」
「那边用 Go。你写过吗。」
「没有。」
蒋辉点了点头,从口袋里掏出一张名片,放在显示器底座上。「一个以前做跨境结算的架构师。你有问题可以问他。我跟他说过你。」
名片上印着一个名字和一个手机号。陈稳把名片翻过来,背面有一行手写的小字:「跟他说你是做 settle.guard 的那个人。他知道。」
他没有说谢谢。把名片夹进记事本的末页。
蒋辉已经走远了。
十二月三十日。他把保密协议寄出。下午去了一趟二号楼,站在走廊尽头那间小会议室门口。门锁着。白板上残留的字还在——「接口规范 v2.3」「字段对齐原则」。他没有敲门。玻璃上他自己的影子叠在那些没擦掉的笔迹上面,像是他站在那些人曾经坐过的地方。
十二月三十一日。公司下午放假。他没有走,在工位上把刘铮发来的跨境结算入门资料读了六十二页。全是 PDF。一个 Go 项目的示例代码片段,几篇关于跨境资金链路的技术博客,还有一份恒澜内部的历史文档——《跨境结算系统一期技术方案》(二零二四年四月)。作者署名:刘铮。有一处引用标记写着「详见顾源架构评审意见」。
他把那份方案从头看到尾。四十二页。字不多,架构图占了将近一半。图上的节点和结算系统不太一样——多了一条合规检查的虚线,一个银行网关的方框,还有一个标着「SWIFT MT103 转换模块」的矩形。
他要做的系统,和结算系统是同一个问题域的不同方言。结算系统里的一笔对账,在跨境系统里要拆成:交易截停、合规放行、汇率折算、银行发出、银行回执、资金入账。每个节点都有自己的一本账。
合上 PDF 的时候已经快七点了。整层楼只有他的工位灯还亮着。
一月四日。假期最后一天。
他在家把 Go 的官方教程前四章看完了。fmt.Println。变量声明。切片和数组的区别。他把每个例子都敲了一遍,没有跑——他的电脑上没有装 Go 环境。他把代码写在记事本里,一行一行,保持语法正确。
何晚下午从她妈家回来,带了一保温盒的菜。排骨、藕汤、一碟凉拌木耳。她把保温盒放在桌上:「你学了一整天,一句没跟我说。」
「在学一个东西。」
「学什么。」
「Go。」
「比 Java 难吗。」
「不难。」他说。「是语法不一样。习惯就好。」
何晚没有继续问。她把碗筷摆好。坐下来的时候说了一句:「你上次学一个新东西的时候,是说'我不知道能不能学会'。现在是'习惯就好'。」
陈稳夹了一块排骨。骨头上的肉炖得很烂,轻轻一抿就下来了。
「你听到了。」他说。
「你也没低声说。」
一月五日。七点五十分。五楼西区。
走廊尽头的工位区亮着几排灯。他的工位在靠窗第三排,桌上放着一台新笔记本电脑、一个空显示器支架、一根网线。没有人。
他坐下来,打开电脑,连上 VPN。项目代码库的 README 第一行写着:
Go。
他把 ID 文件夹的代码结构看了一遍。cmd/ internal/handler/ internal/service/ internal/repository/ proto/。典型的 Go 项目布局。他在 PDF 里见过。
cmd/main.go。import 块。十三行。他认识其中六行——fmt log time net/http os strings。剩下的他一个一个看:context encoding/json google.golang.org/grpc google.golang.org/protobuf gitlab.henglan.internal/tsf/proto gitlab.henglan.internal/tsf/config。
有人在他背后停了一下。
「新来的?」
灰色卫衣。三十出头,没戴眼镜,左手腕上一块黑色的智能表。和刘铮 PDF 里的署名对得上。
「嗯。」
「万钧说的那个人?」
「对。」
刘铮把手里的咖啡杯放在他桌上。杯底磕了一下桌面。「刘铮。跨境这一块现在我在搭。你原来写什么。」
「Java 和 Python。结算组。」
「这边用 Go。」刘铮的语气是陈述句,不是刁难。「你大概多久能上手。」
陈稳看了一眼屏幕。「三天。」
刘铮没有纠正也没有信。「先把 proto 看了。接口定义在 proto/trade/ 下面。有问题来问我。」
他转身走了。杯垫留在桌上——黑色硅胶的,没有花纹。
陈稳把 proto/trade/settlement.proto 打开。一千一百行。
他没有从头读。搜索 service,跳到第一个 RPC 定义。
service SettlementService {
rpc CreateSettlement (CreateSettlementRequest) returns (CreateSettlementResponse);
rpc ConfirmSettlement (ConfirmSettlementRequest) returns (SettlementReceipt);
rpc QuerySettlement (QuerySettlementRequest) returns (SettlementRecord);
rpc ReconcileBatch (ReconcileBatchRequest) returns (ReconcileBatchResponse);
}
四个接口。创建、确认、查询、批量对账。和结算系统的接口结构不一样——那边是按流程拆的,这边按资源拆。SettlementRecord 消息体里有一百三十一个字段。他用三秒钟找到了两个金额字段——settlement_amount 和 transaction_amount。
settlement_amount 的注释:「结算金额(人民币等值,按成交时汇率折算)」
transaction_amount 的注释:「交易金额(原币种)」
他在记事本里写:
他没有问刘铮。先自己查。打开 compliance.proto。
service ComplianceService {
rpc PreCheck (PreCheckRequest) returns (PreCheckResult);
rpc Freeze (FreezeRequest) returns (FreezeResponse);
rpc ReportSuspicious (ReportSuspiciousRequest) returns (ReportSuspiciousResponse);
}
合规服务。风控冻结。可疑交易上报。在结算系统里,合规不在他的链路里——结算系统只关心对上了没有。但这里的结算依赖合规的放行结果。
他把三个 proto 文件的关键字段抄在记事本上,大约四十行。写在万钧项目计划书背面。
九点四十分。刘铮的工位离他大约十米,屏幕上开着终端窗口,旁边放着一杯已经喝完的咖啡。杯垫还在陈稳桌上。
他把杯垫拿起来,走过去,放在刘铮桌上。
「下面哪个是实时汇率,哪个是固定汇率的。」
刘铮看了他一眼,把终端窗口切到第二屏。一个配置文件:
「固定汇率。每天九点一刻拉一次央行中间价。加了千分之三的点差。」刘铮把杯垫压在键盘下面。「结算系统按成交时汇率折人民币,不是按结算时折。两个金额会差一个汇率波动——最后对不平的部分走汇兑损益科目。」
陈稳把这句话记下来了。汇兑损益科目。结算系统的账上没有这个东西。
「你原来的组不做跨境?」
「不做。只做国内结算。」
「那够你学的。这边三样东西和国内不一样——汇率、合规、资金链路。」刘铮转过椅子,敲了一个命令,屏幕上弹出一个架构图。不是拓扑图,是一条线:从用户发起交易到银行清算到对账到入账。线上标着节点,每个节点下面有数字。
「你先把这条线走一遍。不需要理解每个节点。知道名字就行。」
陈稳数了一下。十九个节点。结算系统只有七个。
十九个。
他把十九个节点名全部抄下来。写到第十三个的时候,刘铮说了一句:
「跨境做到九九点九就算高了。链路太长,你不控制的环节太多。」
「那你们现在做到多少。」
刘铮的手停在键盘上。「百分之九十九点六二。」
「十月到十二月。每月大约四百到六百笔失败。原因分布:汇率波动导致金额不符占三成、合规拦截占两成半、银行端超时占两成、数据格式错误占一成半、未知占一成。」
陈稳在心里估算了一下。如果月交易量和结算系统差不多——约十万笔——失败率在百分之零点四到零点六,和百分之九十九点六二对得上。
「未知的那一成在查吗。」
刘铮看了他一眼。有一秒没有回答。「没有专门的人查。谁有空谁看。」
陈稳没有继续说。他记下了。未知原因六十笔。如果按结算系统的排查方式——拆链路、打日志、分段定位——大约两周能覆盖百分之八十的根因。
但这个想法他没有说出来。他连 Go 都没写过。
他把笔记本合上了。「先看 proto。」
刘铮点了一下头。
十二点十分。五楼食堂。人比楼下少。
他端着餐盘找了一个靠窗的位置。坐下来的时候,对面有人坐下了——短头发,圆框眼镜,端着一碗面。
「你是结算组新来的?」她把面的包装纸撕开一半又停住了。
「嗯。」
「我叫林栩。做数据的。」她夹了一筷子面吹了一下。「这边的数据线比结算组乱很多。我怀疑有一部分数据是从 Excel 导入的。」
陈稳放下筷子。「哪个表。」
「一号库。trade_flow schema。trade_import_record。」
他没有听过这个表。他在这里什么都不知道。对面的人告诉他一个数据是从 Excel 导入的,而他甚至不知道这个表在哪个数据库里。
他把这句话记住了。
「数据源是谁给的。」
「业务线。」林栩吸了一口面。「业务线下单的时候手动填的,然后导入系统。没有校验。没有字段约束。几十个字段可以任意填。」
他想起十月份的数据治理项目。三个表,字段不一致,六十八行 SQL。现在这里是一个 Excel 导入表,四十七个字段,没有约束。
「有线上排查文档吗。」
「没有。」林栩说。「这边的文档很少。代码就是文档。」
下午一点四十。他把零号库的连接信息写在一张 A4 纸上。数据库地址、端口、用户名。密码没写——他还没有申请权限。
登录 VPN,连到开发环境的数据库。SELECT COUNT(*) FROM trade_flow.trade_import_record——
九万七千多行。
不到十万行的表,用 Excel 作为数据源。没有校验。没有约束。
他想起 settle_reconciliation 表被删索引的时候——也是不到十万行的数据,没有索引,连接池被打满。
他没有继续往下想。
打开 trade_import_record 的表结构。四十七个字段。字段名有下划线、有驼峰、有大写全拼、有拼音首字母。他数了一下,七种命名风格。
他把这七种风格抄在 A4 纸背面。
十四点二十。他打开 settlement.proto 里 ReconcileBatch 接口的定义,找到批量对账的请求体。请求里包含一个 date_range 字段、一个 currency_pair 字段、一个可选的 merchant_id 字段。他把字段抄下来,对照 trade_import_record 的字段名,看哪个字段可能作为对账的关联键。
trade_import_record 里有一个字段叫 orderNo,一个叫 trade_id,一个叫 trans_id,一个叫 biz_no。四个字段,四种命名风格,含义都是「这笔交易的唯一标识」。
他把四个字段名写在一行,中间加了一个问号。
四点多。刘铮从工位那边走过来,手里端着一杯新咖啡。
「你还在看 proto?」
「在看 trade_import_record 的表结构。」陈稳把 A4 纸转过去。
刘铮看了一眼那张纸。视线停了几秒。「你翻到那个表了。」
「午饭的时候有人告诉我的。林栩。」
「她跟你说什么了。」
「说数据是从 Excel 导入的。」
刘铮喝了一口咖啡。杯沿上沾了一点咖啡渍,他用拇指擦掉了。「你知道那个表有多少系统在用吗。」
「不知道。」
「四个。你们组的结算核对、合规那边的风控预审、财务对账、业务线自己的报表。」刘铮把杯子放在他桌上。「而且没人知道它有多少错。」
「之前有人查过吗。」
「有。」刘铮说。「查了两个月,查不下去了。因为源头数据是业务线给的。你改了数据,业务线说不对——他们那个数字是业务员打电话问来的。你没有别的地方可以核。」
陈稳把这句话记住了。源头不可核——每个数字都建立在「对方说的是真的」这个假设上。他想起汇金线的三个数字。43,200 / 44,180 / 45,000。张磊查不到真正的底数。
「你发什么呆。」
「在想你刚才说的——源头数据不可核。」
「这不是你一个人能解决的问题。」刘铮把手从桌上拿开。「先把 proto 看了。别一上来就踩到最深的坑里。」
五点零七分。他收拾东西的时候,手机亮了一下。何晚的消息:「第一天。怎么样。」
他打了一半「还行」,删掉,换成:「新的语言。新的业务线。新的数据源。什么都不知道。」
何晚那边隔了大约一分半回:「那你不是又变成新人了。」
「是。」
「你上次当新人的时候,背了 C。」
「我知道。」
「这次不会了。」
「你怎么知道。」
「因为你上次是一个人。现在不是。」
他没有回。把手机放进口袋。
他想起今天认识了两个人。一个告诉他他不知道 Go 怎么写。一个告诉他有个表是从 Excel 导入的。
他想起结算组的第一个月——老梁给了张架构图,他看了两遍全部记住了。但那边花了一个月才有人主动跟他说话。
这边只用了半天。
他走出五楼的时候,在电梯口停了一下。走廊尽头的墙上贴着一张纸,打印的——「跨境结算服务 —— 十二月运行数据」。下面是几行数字:
四百二十三笔失败,四十七笔查不到原因。他记下了。四十七。和六十笔的估算差十三笔——差值来源可能是十二月交易量比十月到十一月的均值低。
他走向电梯,玻璃门上映着走廊尽头的灯。
一月五日。第一天。他什么都不会。
但他知道四十七个查不到原因的失败在哪儿。
地铁上。没有座位,站在门边。窗玻璃上映着他的影子。外面的隧道灯一截一截地亮过去又暗下去,亮过去又暗下去。
他打开手机记事本。今天写了大约一百七十行笔记。
关掉记事本。打开 Go 官方教程第五章——函数定义。把屏幕亮度调低了两格。地铁在隧道里继续往前走,窗外的灯还在亮。
← 第四十二章 问易文 · 目录 · 第四十四章 万钧 →