【WEB系列】 XML传参返回实战

文章目录
  1. I. 项目搭建
    1. 1. pom依赖
    2. 2. 接口调研
  2. II. 实战
    1. 1.REST接口
    2. 2.请求参数与返回结果对象定义
    3. 3.测试
    4. 4.问题记录
      1. 4.1 HttpMediaTypeNotSupportedException异常
      2. 4.2 其他json接口也返回xml数据
      3. 4.3 微信实际回调参数一直拿不到
    5. 5.小结
  3. III. 其他
    1. 0. 项目与源码
    2. 1. 微信公众号: 一灰灰Blog

220704-SpringBoot系列教程之XML传参返回实战

最近在准备使用微信公众号来做个人站点的登录,发现微信的回调协议居然是xml格式的,之前使用json传输的较多,结果发现换成xml之后,好像并没有想象中的那么顺利,比如回传的数据始终拿不到,返回的数据对方不认等

接下来我们来实际看一下,一个传参和返回都是xml的SpringBoot应用,究竟是怎样的

I. 项目搭建

本文创建的实例工程采用SpringBoot 2.2.1.RELEASE + maven 3.5.3 + idea进行开发

1. pom依赖

具体的SpringBoot项目工程创建就不赘述了,对于pom文件中,需要重点关注下面两个依赖类

1
2
3
4
5
6
7
8
9
10
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</dependency>
</dependencies>

2. 接口调研

我们直接使用微信公众号的回调传参、返回来搭建项目服务,微信开发平台文档如: 基础消息能力

其定义的推送参数如下

1
2
3
4
5
6
7
8
9
10
<xml>
<ToUserName><![CDATA[toUser]]></ToUserName>
<FromUserName><![CDATA[fromUser]]></FromUserName>
<CreateTime>1348831860</CreateTime>
<MsgType><![CDATA[text]]></MsgType>
<Content><![CDATA[this is a test]]></Content>
<MsgId>1234567890123456</MsgId>
<MsgDataId>xxxx</MsgDataId>
<Idx>xxxx</Idx>
</xml>

要求返回的结果如下

1
2
3
4
5
6
7
<xml>
<ToUserName><![CDATA[toUser]]></ToUserName>
<FromUserName><![CDATA[fromUser]]></FromUserName>
<CreateTime>12345678</CreateTime>
<MsgType><![CDATA[text]]></MsgType>
<Content><![CDATA[你好]]></Content>
</xml>

上面的结构看起来还好,但是需要注意的是外层标签为xml,内层标签都是大写开头的;而微信识别返回是大小写敏感的

II. 实战

项目工程搭建完毕之后,首先定义一个接口,用于接收xml传参,并返回xml对象;

那么核心的问题就是如何定义传参为xml,返回也是xml呢?

没错:就是请求头 + 返回头

1.REST接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@RestController
public class XmlRest {

/**
* curl -X POST 'http://localhost:8080/xml/callback' -H 'content-type:application/xml' -d '<xml><URL><![CDATA[https://hhui.top]]></URL><ToUserName><![CDATA[一灰灰blog]]></ToUserName><FromUserName><![CDATA[123]]></FromUserName><CreateTime>1655700579</CreateTime><MsgType><![CDATA[text]]></MsgType><Content><![CDATA[测试]]></Content><MsgId>11111111</MsgId></xml>' -i
*
* @param msg
* @param request
* @return
*/
@PostMapping(path = "xml/callback",
consumes = {"application/xml", "text/xml"},
produces = "application/xml;charset=utf-8")
public WxTxtMsgResVo callBack(@RequestBody WxTxtMsgReqVo msg, HttpServletRequest request) {
WxTxtMsgResVo res = new WxTxtMsgResVo();
res.setFromUserName(msg.getToUserName());
res.setToUserName(msg.getFromUserName());
res.setCreateTime(System.currentTimeMillis() / 1000);
res.setMsgType("text");
res.setContent("hello: " + LocalDateTime.now());
return res;
}
}

注意上面的接口定义,POST传参,请求头和返回头都是 application/xml

2.请求参数与返回结果对象定义

上面的接口中定义了WxTxtMsgReqVo来接收传参,定义WxTxtMsgResVo来返回结果,由于我们采用的是xml协议传输数据,这里需要借助JacksonXmlRootElementJacksonXmlProperty注解;它们的实际作用与json传输时,使用JsonProperty来指定json key的作用相仿

下面是具体的实体定义

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
@Data
@JacksonXmlRootElement(localName = "xml")
public class WxTxtMsgReqVo {
@JacksonXmlProperty(localName = "ToUserName")
private String toUserName;
@JacksonXmlProperty(localName = "FromUserName")
private String fromUserName;
@JacksonXmlProperty(localName = "CreateTime")
private Long createTime;
@JacksonXmlProperty(localName = "MsgType")
private String msgType;
@JacksonXmlProperty(localName = "Content")
private String content;
@JacksonXmlProperty(localName = "MsgId")
private String msgId;
@JacksonXmlProperty(localName = "MsgDataId")
private String msgDataId;
@JacksonXmlProperty(localName = "Idx")
private String idx;
}

@Data
@JacksonXmlRootElement(localName = "xml")
public class WxTxtMsgResVo {

@JacksonXmlProperty(localName = "ToUserName")
private String toUserName;
@JacksonXmlProperty(localName = "FromUserName")
private String fromUserName;
@JacksonXmlProperty(localName = "CreateTime")
private Long createTime;
@JacksonXmlProperty(localName = "MsgType")
private String msgType;
@JacksonXmlProperty(localName = "Content")
private String content;
}

重点说明:

  • JacksonXmlRootElement 注解,定义返回的xml文档中最外层的标签名
  • JacksonXmlProperty 注解,定义每个属性值对应的标签名
  • 无需额外添加<![CDATA[...]]>,这个会自动添加,防转义

3.测试

然后访问测试一下,直接通过curl来发送xml请求

1
curl -X POST 'http://localhost:8080/xml/callback' -H 'content-type:application/xml' -d '<xml><URL><![CDATA[https://hhui.top]]></URL><ToUserName><![CDATA[一灰灰blog]]></ToUserName><FromUserName><![CDATA[123]]></FromUserName><CreateTime>1655700579</CreateTime><MsgType><![CDATA[text]]></MsgType><Content><![CDATA[测试]]></Content><MsgId>11111111</MsgId></xml>' -i

实际响应如下

1
2
3
4
5
6
HTTP/1.1 200
Content-Type: application/xml;charset=utf-8
Transfer-Encoding: chunked
Date: Tue, 05 Jul 2022 01:20:32 GMT

<xml><ToUserName>123</ToUserName><FromUserName>一灰灰blog</FromUserName><CreateTime>1656984032</CreateTime><MsgType>text</MsgType><Content>hello: 2022-07-05T09:20:32.155</Content></xml>%

4.问题记录

4.1 HttpMediaTypeNotSupportedException异常

通过前面的方式搭建项目之后,在实际测试时,可能会遇到下面的异常情况Resolved [org.springframework.web.HttpMediaTypeNotSupportedException: Content type 'application/xml;charset=UTF-8' not supported]

当出现这个问题时,表明是没有对应的Convert来处理application/xml格式的请求头

对应的解决方案则是主动注册上

1
2
3
4
5
6
7
@Configuration
public class XmlWebConfig implements WebMvcConfigurer {
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(new MappingJackson2XmlHttpMessageConverter());
}
}

4.2 其他json接口也返回xml数据

另外一个场景则是配置了前面的xml之后,导致项目中其他正常的json传参、返回的接口也开始返回xml格式的数据了,此时解决方案如下

1
2
3
4
5
6
7
8
9
10
11
12
@Configuration
public class XmlWebConfig implements WebMvcConfigurer {
/**
* 配置这个,默认返回的是json格式数据;若指定了xml返回头,则返回xml格式数据
*
* @param configurer
*/
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.defaultContentType(MediaType.APPLICATION_JSON, MediaType.TEXT_XML, MediaType.APPLICATION_XML);
}
}

4.3 微信实际回调参数一直拿不到

这个问题是在实际测试回调的时候遇到的,接口定义之后始终拿不到结果,主要原因就在于最开始没有在定义的实体类上添加 @JacksonXmlProperty

当我们没有指定这个注解时,接收的xml标签名与实体对象的fieldName完全相同,既区分大小写

所以为了解决这个问题,就是老老实实如上面的写法,在每个成员上添加注解,如下

1
2
3
4
@JacksonXmlProperty(localName = "ToUserName")
private String toUserName;
@JacksonXmlProperty(localName = "FromUserName")
private String fromUserName;

5.小结

本文主要介绍的是SpringBoot如何支持xml格式的传参与返回,大体上使用姿势与json格式并没有什么区别,但是在实际使用的时候需要注意上面提出的几个问题,避免采坑

关键知识点提炼如下:

  • Post接口上,指定请求头和返回头:
    • consumes = {"application/xml", "text/xml"},
    • produces = "application/xml;charset=utf-8"
  • 实体对象,通过JacksonXmlRootElementJacksonXmlProperty来重命名返回的标签名
  • 注册MappingJackson2XmlHttpMessageConverter解决HttpMediaTypeNotSupportedException异常
  • 指定ContentNegotiationConfigurer.defaultContentType 避免出现所有接口返回xml文档

III. 其他

0. 项目与源码

1. 微信公众号: 一灰灰Blog

尽信书则不如,以上内容,纯属一家之言,因个人能力有限,难免有疏漏和错误之处,如发现bug或者有更好的建议,欢迎批评指正,不吝感激

下面一灰灰的个人博客,记录所有学习和工作中的博文,欢迎大家前去逛逛

一灰灰blog


打赏 如果觉得我的文章对您有帮助,请随意打赏。
分享到