一、XML 是申报数据,PDF 是留档与随附,两者不能混为一谈
跨境报关单证自动化的第一个架构决策,是分清 XML 和 PDF 的角色。XML 是海关系统真正消费的申报数据,遵循海关统一的数据元规范(如 CEB603 报文结构),字段有明确的格式、长度和必填约束。PDF 是辅助单证,用于低值快速货物报关单的随附单据上传、企业内部留档和审计,不替代 XML 申报,但它是合规链条中不可缺少的一环。
两者的关系应当是同一数据源,双轨输出 :从订单数据映射为统一的报关数据对象,XML 和 PDF 都从这个对象生成,避免各写各的导致"XML 申报金额和 PDF 留档金额不一致"。XML 走同步主链路,PDF 可以异步生成,但必须可重放------同一票数据随时能重新生成同一份 PDF,用于海关的补传指令。

二、XML 报文:结构化生成 + XSD 校验,不能字符串拼接
海关报文有严格的节点顺序、命名空间和必填约束。用 StringBuilder 手工拼接 XML,会在字段值包含特殊字符(&、<)时产生格式错误,在报文规范调整时(海关新增节点)需要改代码。
正确的做法是结构化生成 + XSD 校验:用 DOM 或 JAXB 按 XSD 组织节点,生成后必须用海关提供的 XSD 做一次校验,而不是"看起来对"就发出去。字段映射应当配置化------内部字段到海关节点路径的映射表放在配置中心,报文版本与海关公告版本挂钩,规范调整时只改映射和 XSD 规则,不改生成逻辑的主干。
单一窗口的客户端导入规范给出了一个关键的文件结构约束:不含随附单据的报文可以直接传 XML;包含随附单据时,必须以 ZIP 包导入,ZIP 内同时包含结构化 XML 和对应的 PDF 文件,且文件名必须唯一,否则会被覆盖。这意味着 XML 生成和 PDF 生成在文件打包层必须协同,而不是两个独立模块各自产出、最后由人工打包。
三、PDF 套打:iText 7 + 模板填充,与 XML 同源
报关单 PDF 的特点是表格密集、字段对齐要求高、需要中文支持。iText 7 在 Java 生态中对表格和字体的控制精细,适合从零绘制报关单布局;如果已有既定的 PDF 模板,可以用 AcroForm 表单域做字段填充,业务人员维护模板版式,代码只负责字段映射。
PDF 的输入必须与 XML 完全相同 ,都是同一个 CustomsDeclarationData 对象。PDF 生成器不直接读数据库、不调用其他服务,只消费已校验通过的数据对象。这是保证两者一致性的唯一方式。
四、上传:签名自检 + 幂等键 + 超时回查
4.1 签名在发送前自检
单一窗口的接入需要数字签名。不同接入方式的签名参数不同(REST 接入的 sign/appId/timestamp、报文接入的证书签名)。签名失败是"数据对了但传不上去"的最常见原因。工程上把签名封装为独立组件,在发送前做一次本地验签自检,失败时记录完整的原始参数(脱敏后)便于排查。
4.2 幂等键用 guid,不用时间戳
海关申报的业务唯一标识是报文中的 guid(36 位系统唯一序号)。上传请求以 guid 作为幂等键:本地已记录该 guid 的提交结果时,直接返回首次结果,不重复推送。用时间戳或随机数做幂等键,重试时会产生新的键,海关侧可能出现同一票的多条申报记录。
4.3 超时进入异步回查,不直接重推
超时不能判定为失败并重推。重推可能导致同一批次产生多条申报记录,触发一致性异常。正确做法是进入异步回查 :用 guid 查询海关侧的实际状态,确认未入库后再重推。本地必须记录每次推送的报文快照和幂等键,回查结果与原快照比对,而不是重新生成报文。
五、回执状态机:退单可修复,审结不可逆
海关回执不是二值信号。100(退单)是可修复的,399(审结)是终态,4(发送失败)需要检查通道或签名。状态机必须把"接收、审核、退单、审结、作废"显式建模:
java
public enum CustomsStatus {
SUBMITTED, REJECTED, UNDER_REVIEW, APPROVED, VOID
}
Map<CustomsStatus, Set<CustomsStatus>> ALLOWED = Map.of(
SUBMITTED, Set.of(UNDER_REVIEW, REJECTED, VOID),
UNDER_REVIEW, Set.of(APPROVED, REJECTED),
REJECTED, Set.of(SUBMITTED), // 修改后重报
APPROVED, Set.of(), // 终态
VOID, Set.of() // 终态
);
REJECTED 不是终点,它允许回到 SUBMITTED 修改后重报,但每次重报必须生成新的提交批次标识,旧单据保留为作废记录。APPROVED 和 VOID 是终态,任何试图从终态流转的操作都应被拒绝。退单原因必须解析为结构化的错误码(金额不一致、编码不存在、收发货人不符),才能让修复动作从"人工猜"变成"按规则改"。
六、避坑指南
坑1:XML 用字符串拼接。 字段值包含特殊字符时格式错误,规范调整时改代码。必须用结构化生成 + XSD 校验,映射配置化。
坑2:含随附单据时只传 XML,不打包 ZIP。 单一窗口要求含 PDF 随附单据时必须用 ZIP 包导入,ZIP 内 XML 和 PDF 同时存在,文件名唯一。只传 XML 会被拒绝或丢失随附单据。
坑3:超时直接重推。 海关侧产生重复申报记录。超时进入异步回查,确认未入库后再处理。
坑4:退单后修改原记录再重推。 审计链路断裂。退单重报必须生成新的提交批次,旧单据保留为作废记录。
七、总结
报关单证自动化的核心链路是:统一报关数据对象 → XML 结构化生成 + XSD 校验(主链路) → PDF 同源生成 + 可重放(辅助留档) → guid 幂等上传 + 超时回查 → 状态机管理回执(退单可修复,审结不可逆) 。含随附单据时按单一窗口规范打包 ZIP,XML 和 PDF 从同一数据源产出,签名发送前自检,超时不直接重推。单证正确、传输可靠、回执可追溯,才是"快速清关"的前提。
下一篇预告:单证解决了"申报数据怎么出去",但跨境物流的另一端是"钱怎么收、怎么分"。后续在「跨境物流架构」系列中继续聊支付网关的 Webhook 防线与汇率快照,以及海外尾程的加盟代理打单与司机调派协同。
> 本文作者:深圳+Eyf•半字节科技▪技术产品团队
> 物流、快递、快运作业管理系统,让每一票,都清晰可控。从任务分配到签收回传,一站式管理每个作业环节。