💼 银行系统 | 支付报文(SWIFT)解析、入库



一般会提前先准备好報文 XSD 对应的 java 对象 然后 Swift 報文傳送至我方 MQ 再接收进我们系统后,使用定時任務監控,用 JAXB(Java Architecture for XML Binding: Java 中用于 XML 与 Java 对象互转的标准框架) 将 XML 转换成 java 对象,獲取所需保溫要素存入數據庫。

配置报文 XSD 文件

提前准备好 ISO20022 报文结构定义 (pacs.008.sxd)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
targetNamespace="urn:iso:std:iso:20022:tech:xsd:pacs.008.001.02"
xmlns="urn:iso:std:iso:20022:tech:xsd:pacs.008.001.02"
elementFormDefault="qualified">

<xs:element name="Document">
<xs:complexType>
<xs:sequence>
<xs:element name="FIToFICstmrCdtTrf" type="FIToFICstmrCdtTrfType"/>
</xs:sequence>
</xs:complexType>
</xs:element>

<xs:complexType name="FIToFICstmrCdtTrfType">
<xs:sequence>
<xs:element name="GrpHdr" type="GrpHdrType"/>
<xs:element name="CdtTrfTxInf" type="CdtTrfTxInfType"/>
</xs:sequence>
</xs:complexType>

<!-- More complex types... -->
</xs:schema>

使用 xjc 生成 Java Bean

使用 xjc 命令或 Maven 插件把 XSD 转换成 Java 对象(JAXB 对象)

1
xjc -d src -p com.bank.iso20022.pacs008 pacs.008.001.02.xsd

执行后会在 com.bank.iso20022.pacs008 包中生成 Document.java、FIToFICstmrCdtTrfType.java 等类。

接收 MQ 发送过来的报文 xml 文件

如 pacs.008 报文:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?xml version="1.0" encoding="UTF-8"?>
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:pacs.008.001.02">
<FIToFICstmrCdtTrf>
<GrpHdr>
<MsgId>ABC123456789</MsgId>
<CreDtTm>2025-07-18T12:00:00</CreDtTm>
</GrpHdr>
<CdtTrfTxInf>
<PmtId>
<InstrId>123456</InstrId>
</PmtId>
<Amt>
<InstdAmt Ccy="USD">1000.00</InstdAmt>
</Amt>
</CdtTrfTxInf>
</FIToFICstmrCdtTrf>
</Document>
1
String xmlContent = mqMessage.getText(); // MQ消息正文(即XML)

使用 JAXB 解析 XML 为 Java 对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import com.bank.iso20022.pacs008.Document;
import jakarta.xml.bind.JAXBContext;
import jakarta.xml.bind.Unmarshaller; // 反序列化
import java.io.StringReader;

public class XmlParser {

public static void main(String[] args) throws Exception {
// 接收报文 xml
String xmlContent = mqMessage.getText();
JAXBContext jaxbContext = JAXBContext.newInstance(Document.class);
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
StringReader reader = new StringReader(xmlContent);

// 得到报文对象
Document document = (Document) unmarshaller.unmarshal(reader);

System.out.println("Message ID: " +
document.getFIToFICstmrCdtTrf().getGrpHdr().getMsgId());

// 获取报文要素,存储报文原文至数据库
String msgId = doc.getFIToFICstmrCdtTrf().getGrpHdr().getMsgId();
BigDecimal amt = doc.getFIToFICstmrCdtTrf().getCdtTrfTxInf().getAmt().getInstdAmt().getValue();
}
}

获取到报文后,通常还有报文验签,报文幂等校验,报文原文备份等

以上只是介绍获取报文后怎么处理,通常银行系统为分布式架构,在 SpringCloud 框架中是如何接收转换还得将以上方法适配进 spring 框架,涉及到 MybatisPlus 的应用。

报文入库示例

报文表结构

1
2
3
4
5
6
7
8
9
10
11
CREATE TABLE swift_pacs008_message (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
msg_id VARCHAR(64),
instr_id VARCHAR(64),
amount DECIMAL(18,2),
currency VARCHAR(10),
sender_bic VARCHAR(20),
receiver_bic VARCHAR(20),
raw_xml TEXT,
create_time DATETIME DEFAULT CURRENT_TIMESTAMP
);

报文对应实体类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Data
@TableName("swift_pacs008_message")
public class Swift_Pacs008_Message {
private Long id;

private String msgId;
private String instrId;
private BigDecimal amount;
private String currency;

private String senderBic;
private String receiverBic;

private String rawXml;

private LocalDateTime createTime;
}

mapper 接口

1
2
3
4
@Mapper
public interface Swift_Pacs008_MessageMapper extends BaseMapper<Swift_Pacs008_Message> {
// 可使用默认的 insert/update 方法
}

Service 接口与实现

Service 接口继承定义

1
2
3
public interface Swift_Pacs008_MessageService extends IService<Swift_Pacs008_Message> {
void saveParsedMessage(String xmlContent) throws Exception;
}

ServiceImpl 接口继承定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@Service
public class Swift_Pacs008_MessageServiceImpl extends ServiceImpl<Swift_Pacs008_MessageMapper, Swift_Pacs008_Message> implements Swift_Pacs008_MessageService {

@Override
public void saveParsedMessage(String xmlContent) throws Exception {
// 1. JAXB 解析 XML
JAXBContext context = JAXBContext.newInstance(Document.class);
Unmarshaller unmarshaller = context.createUnmarshaller();
Document doc = (Document) unmarshaller.unmarshal(new StringReader(xmlContent));

// 2. 抽取报文要素字段
String msgId = doc.getFIToFICstmrCdtTrf().getGrpHdr().getMsgId();
String instrId = doc.getFIToFICstmrCdtTrf().getCdtTrfTxInf().getPmtId().getInstrId();
BigDecimal amt = doc.getFIToFICstmrCdtTrf().getCdtTrfTxInf().getAmt().getInstdAmt().getValue();
String ccy = doc.getFIToFICstmrCdtTrf().getCdtTrfTxInf().getAmt().getInstdAmt().getCcy();
String sender = doc.getFIToFICstmrCdtTrf().getGrpHdr().getInstgAgt().getFinInstnId().getBICFI();
String receiver = doc.getFIToFICstmrCdtTrf().getGrpHdr().getInstdAgt().getFinInstnId().getBICFI();

// 3. 构建报文对象实体
Swift_Pacs008_Message entity = new Swift_Pacs008_Message();
entity.setMsgId(msgId);
entity.setInstrId(instrId);
entity.setAmount(amt);
entity.setCurrency(ccy);
entity.setSenderBic(sender);
entity.setReceiverBic(receiver);
entity.setRawXml(xmlContent);

// 4. 入库
this.save(entity);
}
}

Controller(或 MQ 消费监听)调用方式

提供接口接收报文 xml,由 ServiceImpl 部分完成解析入库。

Controller 调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@RestController
@RequestMapping("/swift")
public class SwiftController {

@Autowired
private SwiftMessageService swiftMessageService;

@PostMapping("/receive")
public ResponseEntity<String> receiveSwiftXml(@RequestBody String xml) {
try {
swiftMessageService.saveParsedMessage(xml);
return ResponseEntity.ok("OK");
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("解析失败:" + e.getMessage());
}
}
}

MQ 消费监听

MQ 接收方式可参考之前的笔记【支付 | ibm mq 接受报文】,查阅 ibm mq 大概是如何配置。

1
2
3
4
5
6
7
8
9
10
@Component
public class MqListener {
@Autowired
private SwiftMessageService swiftMessageService;

@JmsListener(destination = "${ibm.mq.queue}")
public void receive(String xml) {
System.out.println("接收到消息: " + xml);
}
}