Nest 中處理 XML 型別的請求與響應

劉哇勇發表於2021-04-25

公眾號及小程式的微信介面是通過 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 中找到。

相關資源

The text was updated successfully, but these errors were encountered:

相關文章