抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

前面介绍了如何发布CPI接口与CPI接口的异常处理,本次介绍如何发布一个简单的调用外围系统接口的CPI配置。同时又如何在接口中设置映射用的Mapping组件。

配置调用外围系统的CPI接口

一、需求说明

1.需求说明

本次我在SAP发布了一个Rest接口,用来查询SFLIGHT表中的数据。作为这次的外围系统接口地址。

2.对端系统接口说明

(1)对端接口说明

接口地址:http://mysap.goodsap.cn:50400/yrest_interface

(2)输入参数

参数名 参数描述
IT_CARRID JSON数组(SIGN、OPTION、LOW、HIGH。普通Range表结构)
IT_CONNID JSON数组(SIGN、OPTION、LOW、HIGH。普通Range表结构)
IT_FLDATE JSON数组(SIGN、OPTION、LOW、HIGH。普通Range表结构)

(3)返回参数

返回JSON数据,数组名称为et_return,数组中的结构如下所示:

参数名 参数描述
carrid 航线代码
connid 航班连接编号
fldate 航班日期
price 航空运费
currency 航班的本地货币

3.SAP端接口说明

(1)接口说明

我们需要调用上面的对端接口,通过SAP(QAS) —> CPI —-> SAP(DEV)这样的流程路线进行。

(2)接口传出参数

参数名 参数描述
CARRID JSON数组(SIGN、OPTION、LOW、HIGH。普通Range表结构)
CONNID JSON数组(SIGN、OPTION、LOW、HIGH。普通Range表结构)
FLDATE JSON数组(SIGN、OPTION、LOW、HIGH。普通Range表结构)

(3)接口接收参数

参数名 参数描述
CARRID 航线代码
CONNID 航班连接编号
FLDATE 航班日期
PRICE 航空运费
CURRENCY 航班的本地货币

二、CPI端配置接口

1.创建iFlow

找到存放iFlow接口的Package,点击编辑然后点击下面的添加按钮,选择Integration Flow

1.创建iFlow接口

在弹框的界面填入iFlow的名称和描述内容。点击右下角的添加并在编辑器中打开按钮。

2.输入iFlow的名称

2.配置调用端适配器

从Sender组件拉出一条箭头指向Start组件,选择SOAP适配器与其对应的版本。双击适配器线段,设置其属性。此处需要设置的有两个地方,一个地方是Address和User Role。Address是该接口的CPI端接口后缀名,User Role对象基本是默认的CPI对象。该对象的查看方式可以看前面写的那一篇《通过Web Service发布CPI接口》。在这一篇笔记中详细记录了该对象是做什么的和查看位置。

3.配置调用段适配器对象

3.添加日志记录脚本_Request01

添加接口的请求输入报文记录日志脚本。内容其实和前面的一样。

4.添加日志记录脚本
1
2
3
4
5
6
7
8
def Message processData(Message message) {
def body = message.getBody(java.lang.String)
def messageLog = messageLogFactory.getMessageLog(message)
if (messageLog != null) {
messageLog.addAttachmentAsString('Request01', body, 'text/plain')
}
return message
}

4.添加字段映射组件

此处有个注意事项,那就是Message Mapping组件只能映射转换XML形式消息。JSON形式的消息是映射不了的,会报错映射时消息结构错误。而我们的接口是SOAP调用外围系统,在一开始传入CPI的消息形式就是XML形式的,所以要在日志记录完成之后先开始字段的映射。

5.添加Message Mapping组件

5.编写字段映射文件

在上面添加完成Mapping组件后,需要编写XSD文件与WSDL文件。其中XSD文件是对端系统的JSON字段结构,而WSDL文件则是SAP端传入消息的XML消息结构。内容如下所示。

(1)SAP端的WSDL文件示例

文件后缀WSDL。

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
<?xml version="1.0" encoding="UTF-8"?>
<wsdl:definitions targetNamespace="urn:smart:com:SAP-YTEST_OUT"
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:wsoap12="http://schemas.xmlsoap.org/wsdl/soap12/"
xmlns:http="http://schemas.xmlsoap.org/wsdl/http/"
xmlns:mime="http://schemas.xmlsoap.org/wsdl/mime/"
xmlns:tns="urn:smart:com:SAP-YTEST_OUT"
xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy"
xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
<wsdl:types>
<xsd:schema targetNamespace="urn:smart:com:SAP-YTEST_OUT"
xmlns="urn:smart:com:SAP-YTEST_OUT">
<xsd:element name="ZINPUT">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="CARRID" minOccurs="0" maxOccurs="unbounded">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="SIGN" minOccurs="0" type="xsd:string"/>
<xsd:element name="OPTION" minOccurs="0" type="xsd:string"/>
<xsd:element name="LOW" minOccurs="0" type="xsd:string"/>
<xsd:element name="HIGH" minOccurs="0" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
<xsd:element name="CONNID" minOccurs="0" maxOccurs="unbounded">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="SIGN" minOccurs="0" type="xsd:string"/>
<xsd:element name="OPTION" minOccurs="0" type="xsd:string"/>
<xsd:element name="LOW" minOccurs="0" type="xsd:string"/>
<xsd:element name="HIGH" minOccurs="0" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
<xsd:element name="FLDATE" minOccurs="0" maxOccurs="unbounded">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="SIGN" minOccurs="0" type="xsd:string"/>
<xsd:element name="OPTION" minOccurs="0" type="xsd:string"/>
<xsd:element name="LOW" minOccurs="0" type="xsd:string"/>
<xsd:element name="HIGH" minOccurs="0" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
<xsd:element name="ZOUTPUT">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="RETURN" maxOccurs="unbounded" minOccurs="0" type = "ET_RETURN"/>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
<xsd:complexType name="ET_RETURN">
<xsd:sequence>
<xsd:element name="CARRID" minOccurs="0" type="xsd:string"/>
<xsd:element name="CONNID" minOccurs="0" type="xsd:string"/>
<xsd:element name="FLDATE" minOccurs="0" type="xsd:string"/>
<xsd:element name="PRICE" minOccurs="0" type="xsd:string"/>
<xsd:element name="CURRENCY" minOccurs="0" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
</xsd:schema>
</wsdl:types>
<wsdl:message name="ZINPUT">
<wsdl:part name="parameter" element="tns:ZINPUT"/>
</wsdl:message>
<wsdl:message name="ZOUTPUT">
<wsdl:part name="parameter" element="tns:ZOUTPUT"/>
</wsdl:message>
<wsdl:portType name="SAP-YTEST_OUT_INTERFACE">
<wsdl:operation name="Request">
<wsdl:input name="Request" message="tns:ZINPUT">
</wsdl:input>
<wsdl:output name="Response" message="tns:ZOUTPUT">
</wsdl:output>
</wsdl:operation>
</wsdl:portType>
<wsdl:binding name="SAP-YTEST_OUT_INTERFACE_test_interface" type="tns:SAP-YTEST_OUT_INTERFACE">
<soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
<wsp:Policy
xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy">
<wsp:PolicyReference URI="#BN__binding"/>
</wsp:Policy>
<wsdl:operation name="Request">
<soap:operation soapAction="" style="document"/>
<wsdl:input name="Request">
<soap:body use="literal"/>
</wsdl:input>
<wsdl:output name="Response">
<soap:body use="literal"/>
</wsdl:output>
</wsdl:operation>
</wsdl:binding>
<wsdl:service name="service">
<wsdl:port name="port" binding="tns:SAP-YTEST_OUT_INTERFACE_test_interface">
<soap:address location="https://sandbox-n3optbe7.it-cpi010-rt.cpi.cn40.apps.platform.sapcloud.cn/cxf/Demo/YTEST_OUT_INTERFACE"/>
</wsdl:port>
</wsdl:service>
<wsp:UsingPolicy required="true"
xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy"/>
<wsp:Policy wsu:Id="BN__binding"
xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy"
xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
<wsp:ExactlyOne>
<wsp:All>
<sp:TransportBinding
xmlns:sp="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702">
<wsp:Policy>
<sp:TransportToken>
<wsp:Policy>
<sp:HttpsToken>
<wsp:Policy>
<wsp:ExactlyOne>
<sp:HttpBasicAuthentication/>
</wsp:ExactlyOne>
</wsp:Policy>
</sp:HttpsToken>
</wsp:Policy>
</sp:TransportToken>
<sp:AlgorithmSuite>
<wsp:Policy>
<wsp:ExactlyOne>
<sp:Basic128/>
</wsp:ExactlyOne>
</wsp:Policy>
</sp:AlgorithmSuite>
<sp:Layout>
<wsp:Policy>
<sp:Strict/>
</wsp:Policy>
</sp:Layout>
</wsp:Policy>
</sp:TransportBinding>
</wsp:All>
</wsp:ExactlyOne>
</wsp:Policy>
</wsdl:definitions>

(2)对端系统接口结构

文件后缀XSD。

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
33
34
35
36
37
38
<schema attributeFormDefault="unqualified" elementFormDefault="qualified" xmlns="http://www.w3.org/2001/XMLSchema">
<element name="root">
<complexType>
<sequence>
<element name="IT_CARRID" minOccurs="0" maxOccurs="unbounded">
<complexType>
<sequence>
<element name="SIGN" type="string" minOccurs="0" maxOccurs="1"/>
<element name="OPTION" type="string" minOccurs="0" maxOccurs="1"/>
<element name="LOW" type="string" minOccurs="0" maxOccurs="1"/>
<element name="HIGH" type="string" minOccurs="0" maxOccurs="1"/>
</sequence>
</complexType>
</element>
<element name="IT_CONNID" minOccurs="0" maxOccurs="unbounded">
<complexType>
<sequence>
<element name="SIGN" type="string" minOccurs="0" maxOccurs="1"/>
<element name="OPTION" type="string" minOccurs="0" maxOccurs="1"/>
<element name="LOW" type="string" minOccurs="0" maxOccurs="1"/>
<element name="HIGH" type="string" minOccurs="0" maxOccurs="1"/>
</sequence>
</complexType>
</element>
<element name="IT_FLDATE" minOccurs="0" maxOccurs="unbounded">
<complexType>
<sequence>
<element name="SIGN" type="string" minOccurs="0" maxOccurs="1"/>
<element name="OPTION" type="string" minOccurs="0" maxOccurs="1"/>
<element name="LOW" type="string" minOccurs="0" maxOccurs="1"/>
<element name="HIGH" type="string" minOccurs="0" maxOccurs="1"/>
</sequence>
</complexType>
</element>
</sequence>
</complexType>
</element>
</schema>

(3)上传映射文件

编写完成上面的文件之后,上传对应的映射文件并映射对应的字段。可以看到我们在上传WSDL文件的时候CPI弹框让我们选择两个结构中的其中一个。这是因为ZINPUT是SAP端的输入Request,而ZOUTPUT则是Response。我们此时设置的是Request的Mapping内容,所以选择上面的ZINPUT对象。

在将两边的映射文件导入完成之后的操作就和PO中间件类似了,可以使用连线将两边的字段和结构根据需求连接起来。这样就是映射两边的字段和结构。而在连接的时候下面有个和PO一样的控制台,里面是很多控制字段格式的标准Function,用于控制传入值的格式等。

6.上传映射文件并映射字段

6.添加日志记录脚本_Request02

此处添加的日志记录脚本是可选的,主要是方便我们后面对接口进行调试和问题排查的。

7.添加日志记录脚本_Request02
1
2
3
4
5
6
7
8
def Message processData(Message message) {
def body = message.getBody(java.lang.String)
def messageLog = messageLogFactory.getMessageLog(message)
if (messageLog != null) {
messageLog.addAttachmentAsString('Request02', body, 'text/plain')
}
return message
}

7.添加XML转JSON组件

在将SAP传入的XML文件转换为对端系统要求的XML形式传入请求后,我们还需要将XML转换为JSON形式然后再发送给外围系统。毕竟外围系统当时要求的是传输JSON形式的请求内容。

在添加完成转换之后,还需要XML TO JSON组件中设置XML Element。在这里设置XML在转换JSON时,哪些字段是需要转换为JSON数组的。比如本次需求中的IT_CARRID、IT_CONNID和IT_FLDATE这三个对象是JSON数组对象,而它们三个对象是存在于XML中root对象下的,所以编写时需要编写为下面的形式。

  • root/IT_CONNID
  • root/IT_CARRID
  • root/IT_FLDATE
8.添加XML转JSON组件对象

8.去除报文的root结构

在上面设置映射结构和转换消息格式的时候有提到过,经过Message Mapping转换后的报文结构是在一个root节点下面的。即下面的结构。

1
{"root":{"IT_CARRID":[{"SIGN":"I","OPTION":"EQ","LOW":"AA"}],"IT_CONNID":[{"SIGN":"I","OPTION":"EQ","LOW":"0017"}]}}

关于这个root节点的来源其实是映射时的root内容,因为在SAP的接口中结构中是需要存在一个外层的大结构来包含中间的各种结构的。在文件中不定义该节点名称就导入到Message Mapping文件中的话会报错。所以该名称是必须的,但是名字不一定要叫root。可以根据你的需求来定义名称。不过修改名称之后,后面所有的root就需要替换成你修改后的名称。

9.root节点名称来源

我们添加一个Groovy脚本,使用下面的代码去除掉经过XML TO JSON转换后的root节点。

1
2
3
4
5
6
7
8
9
10
def Message processData(Message message) {
//Body
def bodyStr = message.getBody(java.lang.String) as String;

bodyStr = bodyStr.replace(/{"root":/,"");

bodyStr = bodyStr.substring(0,bodyStr.length() - 1); // 去除掉root末尾的“}”
message.setBody(bodyStr);
return message;
}
10.去除root节点组件

9.添加日志记录脚本_Request03

添加一个日志记录脚本,代码内容如下所示。

1
2
3
4
5
6
7
8
def Message processData(Message message) {
def body = message.getBody(java.lang.String)
def messageLog = messageLogFactory.getMessageLog(message)
if (messageLog != null) {
messageLog.addAttachmentAsString('Request03', body, 'text/plain')
}
return message
}
11.添加日志管理_Request03

10.添加请求回复

经过上面的各种操作后设置的传入报文已经达到了和外围系统要求的格式,所以此时就需要设置一个请求答复来调用外围系统。然后将外围系统返回的参数在经过转换返回到SAP中。

(1)设置请求回复组件

12.设置请求回复

(2)设置Credential Name

这个对象是和上面的Authentication一起使用的,上面选择的是认证方式,而下面则是认证所需要的账密对象Credential Name。该对象的设置方式在下面的路径中:监控器 –> 管理安全 –> 安全材料

13.设置Credential Name_位置

进入安全材料界面后点击右上角的创建按钮,选择用户凭据这个也是和外围系统确定认证的方式,所以选择的类型于认证方式请根据自身的需求来灵活调整

14.Credential Name的类型

按照上面的操作流程进行后,界面会弹框要求输入对象的属性。此处使用我已经创建好的凭据做一个演示。

15.创建Credential Name对象

11.添加日志记录脚本_Response01

因为已经调用过外围系统了,所以此处我们记录一下接口返回的消息日志记录。日志记录的代码如下:

1
2
3
4
5
6
7
8
def Message processData(Message message) {
def body = message.getBody(java.lang.String)
def messageLog = messageLogFactory.getMessageLog(message)
if (messageLog != null) {
messageLog.addAttachmentAsString('Response01', body, 'text/plain')
}
return message
}
16.返回消息的日志记录

12.添加root节点

此处为接口返回的报文添加root节点,理由上面去除root节点的理由一致。我们在映射字段结构的时候需要以root节点为起始节点,此处使用内容修正符组件处理即可。

17.添加内容修正符

13.添加JSON转XML组件

进行完上面的操作之后,我们需要将对方接口返回的消息在映射到SAP的系统中。而Message Mapping组件对象映射字段需要消息格式是XML形式的。所以此处添加一个JSON转XML的组件对象。

因为我们已经自行添加过root节点了,所以我们就不再需要此处帮我们默认添加root节点需求了。将下面的Add XML Root Element选项取消掉。当然你也可以将前面的内容修正符去掉然后保留此处的勾选也是一样的。

18.添加JSON TO XML组件

14.添加字段映射组件

此处我们其实还可以在该组件之前再添加一个日志记录脚本文件用来查看转换后的XML文件内容。但是是可选内容,所以就暂时不做了直接进行字段映射。

19.Response Mapping组件的添加

15.编写字段映射文件

在映射前同样需要导入XSD与WSDL文件。SAP端的WSDL文件之前已经导入过了,所以这里我们就导入对端系统的返回XSD文件即可。

(1)对端系统的Response结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<schema attributeFormDefault="unqualified" elementFormDefault="qualified" xmlns="http://www.w3.org/2001/XMLSchema">
<element name="root">
<complexType>
<sequence>
<element name="et_return" minOccurs="0" maxOccurs="unbounded">
<complexType>
<sequence>
<element name="carrid" type="string" minOccurs="0" maxOccurs="1"/>
<element name="connid" type="string" minOccurs="0" maxOccurs="1"/>
<element name="fldate" type="string" minOccurs="0" maxOccurs="1"/>
<element name="price" type="string" minOccurs="0" maxOccurs="1"/>
<element name="currency" type="string" minOccurs="0" maxOccurs="1"/>
</sequence>
</complexType>
</element>
</sequence>
</complexType>
</element>
</schema>

(2)上传映射文件

20.映射Response

16.添加日志记录文件_Response02

最后再记录一下传入SAP端的日志信息即可。

1
2
3
4
5
6
7
8
def Message processData(Message message) {
def body = message.getBody(java.lang.String)
def messageLog = messageLogFactory.getMessageLog(message)
if (messageLog != null) {
messageLog.addAttachmentAsString('Response02', body, 'text/plain')
}
return message
}
21.日志记录文件_Response02

17.接口的异常处理

开发完成接口之后一定记得如果可以对他进行异常处理的话就给接口添加上异常处理的逻辑吧。此处就只演示下前面提到过的接口异常时发送邮件提醒管理员的异常处理方式。对这部分感兴趣的可以去查看之前的笔记《CPI邮件配置》

22.CPI的异常处理

18.发布部署接口

进行完上面的所有流程操作之后就可以部署接口了。点击右上角的部署按钮发布接口。

23.部署接口截图

最后到管理集成内容中查看接口的地址。

24.获取接口的URL地址

三、测试接口

因为没有编写SAP端的测试接口,所以这里就借用CPI自身的测试工具对接口进行测试。刚好也对这种测试方式做一个记录。

1.获取测试用报文

(1)SAP系统

① SAP端传入XML
1
<n0:ZINPUT xmlns:n0="urn:smart:com:SAP-YTEST_OUT" xmlns:soap-env="http://schemas.xmlsoap.org/soap/envelope/" xmlns:prx="urn:sap.com:proxy:QS4:/1SAI/TASF958F66990BA4F2D7010:755"><CARRID><SIGN>I</SIGN><OPTION>EQ</OPTION><LOW>AA</LOW></CARRID><CONNID><SIGN>I</SIGN><OPTION>EQ</OPTION><LOW>0017</LOW></CONNID></n0:ZINPUT>

(2)对端系统

① 对端传入JSON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"IT_CARRID": [
{
"SIGN":"I",
"OPTION":"EQ",
"LOW":"AA"
}
],
"IT_CONNID": [
{
"SIGN":"I",
"OPTION":"EQ",
"LOW":"0017"
}
]
}
② 对端传出JSON
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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
{
"et_return": [
{
"carrid": "AA",
"connid": 17,
"fldate": "2023-11-07",
"price": 422.94,
"currency": "USD"
},
{
"carrid": "AA",
"connid": 17,
"fldate": "2024-01-26",
"price": 422.94,
"currency": "USD"
},
{
"carrid": "AA",
"connid": 17,
"fldate": "2024-04-15",
"price": 422.94,
"currency": "USD"
},
{
"carrid": "AA",
"connid": 17,
"fldate": "2024-07-04",
"price": 422.94,
"currency": "USD"
},
{
"carrid": "AA",
"connid": 17,
"fldate": "2024-09-22",
"price": 422.94,
"currency": "USD"
},
{
"carrid": "AA",
"connid": 17,
"fldate": "2024-12-11",
"price": 422.94,
"currency": "USD"
}
],
"ev_code": "S",
"ev_message": "数据查询成功"
}

2.添加输入端调式点

在连接每个组件的横线上单击左键会有4个按钮。按照下面所示的方式添加调试断点。然后将上面提到的测试用数据填充到开始的调试断点中运行调试。

25.添加输入端调试点

可以看到我们使用上面的请求内容进行测试在调用外围系统之前他将我们传入的XML报文已经转换成了调用对方系统的JSON报文。

26.XML报文起始截图 27.请求调用前的正文内容

由上面的测试结果可以知道CPI的接口是按照我们的需求对请求的报文进行了XML到JSON的转换的。接下来就是测试对端系统返回的JSON转换为SAP端的XML。

3.添加输出端调试点

通过上面第一小项可以知道对端系统返回的JSON报文内容,我们这里直接使用这个报文进行测试。

28.添加输出端调式点

通过上面的测试可以发现在调试流程的起点到终点之间每个组件之后都会有一个信封图标。双击他们就可以查看到请求内容在他们之间的变化。包括我们添加的root节点与最后面的JSON转换为XML的最终形式,映射后的字段值等。

当中间存在异常无法执行时也可以通过双击信封的方式查看此处给出的报错参考。至少我认为此处的报错内容提示比监控器中给出的内容要容易排查的多。

4.取消调式点

可以添加也就可以一次性取消。在运行调式点按钮的后面的按钮就是取消调试按钮。点击一下之后就可以取消调试点了。

29.取消调试点

四、其他

以上就是CPI调用外围系统的CPI端配置,本来是想把SAP端的配置也一次性做了的。但是考虑到上面已经给出了WSDL的源码文件,对于创建SAP的接口已经很方便了所以就不演示SAP端如何发布接口的操作了。如果有任何疑问可以在评论区私信我。

评论