Nest 中处理 XML 类型的请求与响应
Nest 中处理 XML 类型的请求与响应
公众号及小程序的微信接口是通过 xml 格式进行数据交换的。
比如接收普通消息的接口:
当普通微信用户向公众账号发消息时,微信服务器将 POST 消息的 XML 数据包到开发者填写的 URL 上。
<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>
</xml>
不仅微信端推送给我们的数据是 XML 类型的,我们调用微信接口,也需要传递 XML 类型的数据。
比如被动回复用户消息:
当用户发送消息给公众号时(或某些特定的用户操作引发的事件推送时),会产生一个 POST 请求,开发者可以在响应包(Get)中返回特定 XML 结构,来对该消息进行响应(现支持回复文本、图片、图文、语音、视频、音乐)。严格来说,发送被动响应消息其实并不是一种接口,而是对微信服务器发过来消息的一次回复。
<xml>
<ToUserName><![CDATA[toUser]]></ToUserName>
<FromUserName><![CDATA[fromUser]]></FromUserName>
<CreateTime>12345678</CreateTime>
<MsgType><![CDATA[text]]></MsgType>
<Content><![CDATA[你好]]></Content>
</xml>
因此,不同于常见的 JSON,要求我们在接口中需要处理 XML 格式的数据。
创建示例项目
使用 Nest 的命令行工具创建一个示例项目用于后面的演示。
$ nest n xml-handling
XML 数据的接收
接收和处理 XML 类型的数据,可使用 body-parser-xml。
安装依赖
$ yarn add body-parser body-parser-xml
启用 body-parser
中间件
使用上面的中间件对请求传递的 XML 数据进行解析。
src/main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
const bodyParser = require('body-parser');
require('body-parser-xml')(bodyParser);
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.use(
bodyParser.xml({
xmlParseOptions: {
explicitArray: false, // 始终返回数组。默认情况下只有数组元素数量大于 1 是才返回数组。
},
}),
);
await app.listen(3000);
}
bootstrap();
接收并处理 XML 数据
假设配置的是微信在收到用户消息时,调用我们的 wxhandler
接口。
src/app.controller.ts
import { Body, Controller, Logger, Post } from '@nestjs/common';
import { inspect } from 'util';
/**
* 微信回调给开发者的消息
*/
interface IWxMessageXmlData {
/** 开发者微信号 e.g. `gh_019087f88815`*/
ToUserName: string;
/** 发送方帐号(一个OpenID)e.g.: `o5w5awUl***5pIJKY`*/
FromUserName: string;
/** 消息创建时间 (整型)e.g.`1595855711` */
CreateTime: string;
/** 消息类型,此处为 `event` */
MsgType: string;
/** 事件类型,subscribe(订阅)、unsubscribe(取消订阅) */
Event: 'subscribe' | 'unsubscribe';
/** 事件KEY值,目前无用 */
EventKey: string;
}
@Controller()
export class AppController {
@Post('wxhandler')
async wxhandler(@Body('xml') xmlData: IWxMessageXmlData) {
Logger.log(`xml data got: ${inspect(xmlData)}`);
return '';
}
}
测试:
$ curl --location --request POST 'localhost:3000/wxhandler' \
--header 'Content-Type: application/xml' \
--data-raw '<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>
</xml>'
从打印的日志中查看解析后的 XML 数据:
[Nest] 89424 - 10/19/2020, 7:58:00 PM xml data got:
{ ToUserName: 'toUser',
FromUserName: 'fromUser',
CreateTime: '1348831860',
MsgType: 'text',
Content: 'this is a test',
MsgId: '1234567890123456' }
+3850ms
XML 数据的返回
让接口响应 XML 数据,需要我们在代码中先创建该类型的数据,可通过 xmlbuilder2 来进行。
安装依赖
$ yarn add xmlbuilder2
使用 xmlbuilder2
创建 XML 数据
通过上述工具进行 XML 的创建然后作为请求的响应。
src/app.controller.ts
import { Body, Controller, Logger, Post } from '@nestjs/common';
import { inspect } from 'util';
+ import { create } from 'xmlbuilder2';
@Controller()
export class AppController {
@Post('wxhandler')
async wxhandler(@Body('xml') xmlData: IWxMessageXmlData) {
Logger.log(`xml data got:\n ${inspect(xmlData)}\n`);
+ const res = create({
+ xml: {
+ ToUserName: 'openid_xxx', // 接收方帐号(收到的OpenID)
+ FromUserName: 'openod_yyy', // 开发者微信号
+ CreateTime: new Date().getTime(), // 消息创建时间 (整型)
+ MsgType: 'text',
+ Content: 'some text',
+ },
+ }).end({ prettyPrint: true });
return res;
}
}
测试并查看返回:
$ curl --location --request POST 'localhost:3000/wxhandler' \
--header 'Content-Type: application/xml' \
--data-raw '<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>
</xml>'
<?xml version="1.0"?>
<xml>
<ToUserName>openid_xxx</ToUserName>
<FromUserName>openod_yyy</FromUserName>
<CreateTime>1603109187287</CreateTime>
<MsgType>text</MsgType>
<Content>some text</Content>
</xml>⏎
示例代码
以上代码可在这个示例仓库 wayou/xml-handling 中找到。