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



We usually prepared the XSD file and the Java Object generated by that XSD using XJC command.
Then we needed to prepare the message database table and this message entity class.
Next we can created the message mapper interface extends BaseMapper and defined the message Service interface and ServiceImpl class according to the Message Type.
After that, we wrote the method which parsed message XML to Java Object using JAXB, got the required message elements and stored the elements and original message in the database.
Finally, we can configured the mq to listen to message recieving.

一般会提前先准备好報文 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
);

报文对应实体类 PO

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_MessagePO {
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
11
12
@Component
public class MqListener {
@Autowired
private SwiftMessageService swiftMessageService;

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