各种消息交互

简介

TNWX: TypeScript + Node.js + WeiXin 微信系开发脚手架,支持微信公众号、微信支付、微信小游戏、微信小程序、企业号/企业微信。最最最重要的是能快速的集成至任何 Node.js 框架(Express、Nest、Egg、Koa 等)

测试号申请

测试时请自己的测试号各种消息交互 - 图1

开启开发者模式

这里说的各种消息交互是指的 开发者模式下的消息交互 如何开启开发者模式

接收各种消息

在 TNWX 中实现微信公众号各种消息交互非常简单,步骤如下:

  • 接收各种消息
  • 调用 WeChat.handleMsg(…) 方法处理分发消息
  • 实现 MsgAdapter 接口,实现业务逻辑以及各种消息回复 开发者 URL 的 POST 方法下接收各种消息 具体实现代码如下

Express 示例

  1. // 接收微信消息入口
  2. app.post('/msg', function (req: any, res: any) {
  3. console.log('post...', req.query);
  4. // 支持多公众号
  5. let appId: string = req.query.appId;
  6. if (appId) {
  7. ApiConfigKit.setCurrentAppId(appId);
  8. }
  9. // 获取签名相关的参数用于消息解密(测试号以及明文模式无此参数)
  10. let msgSignature = req.query.msg_signature,
  11. timestamp = req.query.timestamp,
  12. nonce = req.query.nonce;
  13. //监听 data 事件 用于接收数据
  14. let buffer: Uint8Array[] = [];
  15. req.on('data', function (data: any) {
  16. buffer.push(data);
  17. });
  18. req.on('end', function () {
  19. let msgXml = Buffer.concat(buffer).toString('utf-8');
  20. // 处理消息并响应对应的回复
  21. // ....
  22. });
  23. });

Nest 示例

  1. @Post("/msg")
  2. PostMsg(@Req() req: Request, @Res() res: Response) {
  3. let that = this;
  4. console.log('post...', req.query);
  5. // 支持多公众号
  6. let appId: string = req.query.appId;
  7. if (appId) {
  8. ApiConfigKit.setCurrentAppId(appId);
  9. }
  10. // 获取签名相关的参数用于消息解密(测试号以及明文模式无此参数)
  11. let msgSignature = req.query.msg_signature,
  12. timestamp = req.query.timestamp,
  13. nonce = req.query.nonce;
  14. //监听 data 事件 用于接收数据
  15. let buffer: Uint8Array[] = [];
  16. req.on('data', function (data: any) {
  17. buffer.push(data);
  18. });
  19. req.on('end', function () {
  20. let msgXml = Buffer.concat(buffer).toString('utf-8');
  21. // 处理消息并响应对应的回复
  22. // ...
  23. });
  24. }

处理并分发消息

  1. WeChat.handleMsg(msgAdapter: MsgAdapter, msgXml: string, msgSignature?: string, timestamp?: string, nonce?: string)

handleMsg 中包含了消息的解密、各种消息分发、消息加密、各种消息回复。这里就不贴源码了,感兴趣的可以看看源码,源码也有详细的注释。

Gitee-TNWX-WeChat各种消息交互 - 图2

GitHub-TNWX-WeChat各种消息交互 - 图3

其中 msgXmlmsgSignaturetimestampnonce 已在上面的 接收各种消息中 获得,就差 MsgAdapter 了。

MsgAdapter 介绍

MsgAdapter 接口中定义的方法如下:

  1. export interface MsgAdapter {
  2. // 处理文本消息
  3. processInTextMsg(inTextMsg: InTextMsg): OutMsg;
  4. // 处理图片消息
  5. processInImageMsg(inImageMsg: InImageMsg): OutMsg;
  6. // 处理声音消息
  7. processInVoiceMsg(inVoiceMsg: InVoiceMsg): OutMsg;
  8. // 处理视频消息
  9. processInVideoMsg(inVideoMsg: InVideoMsg): OutMsg;
  10. // 处理小视频消息
  11. processInShortVideoMsg(inShortVideoMsg: InShortVideoMsg): OutMsg;
  12. // 处理地理位置消息
  13. processInLocationMsg(inLocationMsg: InLocationMsg): OutMsg;
  14. // 处理链接消息
  15. processInLinkMsg(inLinkMsg: InLinkMsg): OutMsg;
  16. // 处理语音识别结果
  17. processInSpeechRecognitionResults(inSpeechRecognitionResults: InSpeechRecognitionResults): OutMsg;
  18. // 处理未定义的消息(其他消息...小哥该扩展了)
  19. processIsNotDefinedMsg(inNotDefinedMsg: InNotDefinedMsg): OutMsg;
  20. // 处理关注、取消关注事件
  21. processInFollowEvent(inFollowEvent: InFollowEvent): OutMsg;
  22. // 处理扫码事件
  23. processInQrCodeEvent(inQrCodeEvent: InQrCodeEvent): OutMsg;
  24. // 处理地理位置事件
  25. processInLocationEvent(inLocationEvent: InLocationEvent): OutMsg;
  26. // 处理地理位置事件
  27. processInMenuEvent(inMenuEvent: InMenuEvent): OutMsg;
  28. // 处理模板消息事件
  29. processInTemplateMsgEvent(inTemplateMsgEvent: InTemplateMsgEvent): OutMsg;
  30. // 处理摇一摇周边事件
  31. processInShakearoundUserShakeEvent(inShakearoundUserShakeEvent: InShakearoundUserShakeEvent): OutMsg;
  32. }

InXxxxMsg 统一继承自 InMsgInXxxxEvent 统一继承自 EventInMsgEventInMsg 又继承自 InMsg ,所以在任何的 inXxxxx 中都很容易获取到 toUserName(开发者微信号即appId)fromUserName(发送方帐号openId)TNWX 支持多公众号,后面会使用到此 appId 来实现不同公众号回复不同的消息

响应对应的回复

代码实现比较简单就不过多介绍了,请看源码

提醒:回复消息时可以对不同的公众号做特殊的处理

  1. export class MsgController implements MsgAdapter {
  2. processInTextMsg(inTextMsg: InTextMsg): OutMsg {
  3. let outMsg: any;
  4. let content: string = "IJPay 让支付触手可及 \n\nhttps://gitee.com/javen205/IJPay";
  5. if ("极速开发微信公众号" == inTextMsg.getContent) {
  6. // 多公众号支持 分别给不同的公众号发送不同的消息
  7. if (ApiConfigKit.getApiConfig.getAppId == 'wx614c453e0d1dcd12') {
  8. content = "极速开发微信公众号 \n\nhttps://github.com/javen205/weixin_guide"
  9. outMsg = new OutTextMsg(inTextMsg);
  10. outMsg.setContent(content);
  11. } else {
  12. content = "极速开发微信公众号 \n\nhttps://github.com/javen205/TNWX"
  13. outMsg = new OutTextMsg(inTextMsg);
  14. outMsg.setContent(content);
  15. }
  16. } else if ("聚合支付" == inTextMsg.getContent) {
  17. // 最新规则:开发者只能回复1条图文消息;其余场景最多可回复8条图文消息
  18. outMsg = new OutNewsMsg(inTextMsg);
  19. outMsg.addArticle("聚合支付了解下", "IJPay 让支付触手可及",
  20. "https://gitee.com/javen205/IJPay/raw/master/assets/img/IJPay-t.png", "https://gitee.com/javen205/IJPay")
  21. outMsg.addArticle("jfinal-weixin", "极速开发微信公众号",
  22. "https://gitee.com/javen205/IJPay/raw/master/assets/img/IJPay-t.png", "https://gitee.com/JFinal/jfinal-weixin")
  23. } else {
  24. // outMsg = new OutTextMsg(inTextMsg);
  25. // outMsg.setContent(content);
  26. // 转发给多客服PC客户端
  27. outMsg = new OutCustomMsg(inTextMsg);
  28. console.log("转发给多客服PC客户端");
  29. }
  30. return outMsg;
  31. }
  32. processInImageMsg(inImageMsg: InImageMsg): OutMsg {
  33. let outMsg = new OutImageMsg(inImageMsg);
  34. outMsg.setMediaId = inImageMsg.getMediaId;
  35. return outMsg;
  36. }
  37. processInVoiceMsg(inVoiceMsg: InVoiceMsg): OutMsg {
  38. let outMsg = new OutVoiceMsg(inVoiceMsg);
  39. outMsg.setMediaId = inVoiceMsg.getMediaId;
  40. return outMsg;
  41. }
  42. processInVideoMsg(inVideoMsg: InVideoMsg): OutMsg {
  43. let outMsg = new OutVideoMsg(inVideoMsg);
  44. outMsg.setMediaId = inVideoMsg.getMediaId;
  45. outMsg.setDescription = "IJPay 让支付触手可及";
  46. outMsg.setTitle = "视频消息";
  47. return outMsg;
  48. }
  49. processInShortVideoMsg(inShortVideoMsg: InShortVideoMsg): OutMsg {
  50. let outMsg = new OutVideoMsg(inShortVideoMsg);
  51. outMsg.setMediaId = inShortVideoMsg.getMediaId;
  52. outMsg.setDescription = "TypeScript + Node.js 开发微信公众号";
  53. outMsg.setTitle = "短视频消息";
  54. return outMsg;
  55. }
  56. processInLocationMsg(inLocationMsg: InLocationMsg): OutMsg {
  57. return this.renderOutTextMsg(inLocationMsg,
  58. "位置消息... \n\nX:" + inLocationMsg.getLocation_X + " Y:" + inLocationMsg.getLocation_Y + "\n\n" + inLocationMsg.getLabel);
  59. }
  60. processInLinkMsg(inLinkMsg: InLinkMsg): OutMsg {
  61. let text = new OutTextMsg(inLinkMsg);
  62. text.setContent("链接频消息..." + inLinkMsg.getUrl);
  63. return text;
  64. }
  65. processInSpeechRecognitionResults(inSpeechRecognitionResults: InSpeechRecognitionResults): OutMsg {
  66. let text = new OutTextMsg(inSpeechRecognitionResults);
  67. text.setContent("语音识别消息..." + inSpeechRecognitionResults.getRecognition);
  68. return text;
  69. }
  70. processInFollowEvent(inFollowEvent: InFollowEvent): OutMsg {
  71. if (InFollowEvent.EVENT_INFOLLOW_SUBSCRIBE == inFollowEvent.getEvent) {
  72. return this.renderOutTextMsg(inFollowEvent,
  73. "感谢你的关注 么么哒 \n\n交流群:114196246");
  74. }
  75. else if (InFollowEvent.EVENT_INFOLLOW_UNSUBSCRIBE == inFollowEvent.getEvent) {
  76. console.error("取消关注:" + inFollowEvent.getFromUserName);
  77. return this.renderOutTextMsg(inFollowEvent);
  78. } else {
  79. return this.renderOutTextMsg(inFollowEvent);
  80. }
  81. }
  82. processInQrCodeEvent(inQrCodeEvent: InQrCodeEvent): OutMsg {
  83. if (InQrCodeEvent.EVENT_INQRCODE_SUBSCRIBE == inQrCodeEvent.getEvent) {
  84. console.debug("扫码未关注:" + inQrCodeEvent.getFromUserName);
  85. return this.renderOutTextMsg(inQrCodeEvent,
  86. "感谢您的关注,二维码内容:" + inQrCodeEvent.getEventKey);
  87. }
  88. else if (InQrCodeEvent.EVENT_INQRCODE_SCAN == inQrCodeEvent.getEvent) {
  89. console.debug("扫码已关注:" + inQrCodeEvent.getFromUserName);
  90. return this.renderOutTextMsg(inQrCodeEvent);
  91. } else {
  92. return this.renderOutTextMsg(inQrCodeEvent);
  93. }
  94. }
  95. processInLocationEvent(inLocationEvent: InLocationEvent): OutMsg {
  96. console.debug("发送地理位置事件:" + inLocationEvent.getFromUserName);
  97. return this.renderOutTextMsg(inLocationEvent,
  98. "地理位置是:" + inLocationEvent.getLatitude);
  99. }
  100. processInMenuEvent(inMenuEvent: InMenuEvent): OutMsg {
  101. console.debug("菜单事件:" + inMenuEvent.getFromUserName);
  102. return this.renderOutTextMsg(inMenuEvent,
  103. "菜单事件内容是:" + inMenuEvent.getEventKey);
  104. }
  105. processInTemplateMsgEvent(inTemplateMsgEvent: InTemplateMsgEvent): OutMsg {
  106. console.debug("模板消息事件:" + inTemplateMsgEvent.getFromUserName + " " + inTemplateMsgEvent.getStatus);
  107. return this.renderOutTextMsg(inTemplateMsgEvent,
  108. "消息发送状态:" + inTemplateMsgEvent.getStatus);
  109. }
  110. processInShakearoundUserShakeEvent(inShakearoundUserShakeEvent: InShakearoundUserShakeEvent): OutMsg {
  111. console.debug("摇一摇事件:" + inShakearoundUserShakeEvent.getFromUserName + " " + inShakearoundUserShakeEvent.getUuid);
  112. return this.renderOutTextMsg(inShakearoundUserShakeEvent,
  113. "uuid:" + inShakearoundUserShakeEvent.getUuid);
  114. }
  115. processIsNotDefinedMsg(inNotDefinedMsg: InNotDefinedMsg): OutMsg {
  116. return this.renderOutTextMsg(inNotDefinedMsg,
  117. "未知消息");
  118. }
  119. renderOutTextMsg(inMsg: InMsg, content?: string): OutTextMsg {
  120. let outMsg = new OutTextMsg(inMsg);
  121. outMsg.setContent(content ? content : " ");
  122. return outMsg;
  123. }
  124. }

开源推荐