zoukankan      html  css  js  c++  java
  • 微信公众号_订阅号_被动回复用户消息

    node.js 作为服务器

    微信公众号(订阅号)

    给个人、企业、组织 提供业务服务用户管理能力的全新服务平台

    • 企业微信: 无需开发,直接使用
    • 小程序
    • 服务号: 单独的一条消息显示;偏向信息查询;一个月只能群发消息 4 条;需要企业认证
    • 订阅号: 收录在 "订阅号" 中;一天只能群发 1 条消息

    订阅号 与 服务号 开发模式 是一样的

    • 常见功能:
    • 微信分享
    • 群发
    • 自定义菜单
    • 打赏

    注册

    • 在线接口调试工具

    接口: 网址 url

    • web开发者工具
    • 公众平台测试账号

    无需申请公众账号即可在测试账号中测试所有高级接口

     

    • 基本交互流程
    • 用户 使用 微信 将消息发送到 微信公众号 上
    • 微信客户端 将消息发送到腾讯自己的微信服务器
    • 腾讯的微信服务器 会将用户消息转发到 开发者服务器(1. 搭建服务器____需要与服务器进行交互配置)
    • 开发者服务器 又将消息响应给 微信服务器器
    • 微信服务器 最后将消息响应给相应的用户
    • 1. 搭建服务器

    一、买域名

    二、使得 开发服务器地址 万维网能访问

    开启 node.js 写好的服务器

    双击启动 ngrok 客户端(ngrok 内网传透)

    ngrok http 3000

    • 填写的 接口配置信息 url
    • Token 参与微信签名加密的一个字段,越复杂越好
    • 确保 服务器开着的,点击 提交
    • 微信的加密签名 由 timestamp、nonce、token三个参数加密生成的签名

    2. 验证消息来自于服务器(只有验证成功了,才能继续开发。如果步骤无误,多提交几次,总会成功)

    // 1. 定义自己的 测试号及接口 配置信息

    const myWeChat= {

    token: 'atguiguHTML0920',
    appID: 'wxc8e92f7aada0fbca0',
    appsecret: 'b4054e90b7a8sdasdasd0af50bf7fc3e87' 

    };

    app.use((request, response, next)=>{

    // 2. 将 timestamp、nonce、token 三个参数组合成 数组,按照字典序进行排序

    const {token} = myWeChat;

    const {signature, echostr, timestamp, nonce} = req.query;

    const sortArr = [timestamp, nonce, token].sort();

    // 3. 将排序后的参数拼接成一个新的字符串,进行 sha1 加密,得到的就是 signature

    const sha1Str = sha1(sortArr.join(""));

    // 4. 将 加密后的签名 和 微信发送来的 signature 进行对比

    if(sha1Str === signature){

    // 一样,说明消息来自于服务器,返回 echostr 给微信服务器

    response.end(echostr);

    }else{

    // 不一样,说明消息不来自于微信服务器,返回 error 错误

    response.end('error');

    };

    next();

    });

    /* 此时,再次点击提交,显示 "配置成功" */

    /* 用手机扫描 测试账号的二维码 关注,并发送任意消息____可以看到服务器接受到的数据*/

    3. 验证服务器有效性模块化 源代码

    config/index.js

    • module.exports = {
          token: 'FinnKou',
          appID: 'wxba59db235745154d22cd32d',
          appsecret: '62a454d75919545d2f276680fcb6148d77b2e31'
      };

    wechat/handleRequest.js

    • const sha1 = require('sha1');
      const {token} = require('../config');
      
      module.exports = ()=>{
          return (request, response, next)=>{
              const {signature, echostr, timestamp, nonce} = request.query;
              
              const sha1Str = sha1([timestamp, nonce, token].sort().join(""));
              
              if(sha1Str === signature){
                  response.end(echostr);
              }else{
                  response.end("error");
              };
              
              next();
          };
      };

    index.js

    • const express = require('express');
      const handleRequest = require('./handleRequest');
      
      const app = express();
      
      app.use(express.urlencoded({extended: true}));
      
      app.use(handleRequest());
      
      app.listen(
          3000,
          err=>console.log(err?err:'
      
      服务器已启动
      		Hunting Happy!')
      );
    • 正式开发功能模块

    1. 被动回复用户消息789(此时,有两个公众号,一个本人的,一个测试的)

    验证服务器 有效性其实是 get 请求

    用户发送的消息是 post 请求

    if(request.method === 'GET'){    // 非用户发送的消息

    if(sha1Str === signature){

    response.end(echostr);

    }else{

    response.end('error');

    };

    }else if(request.method === 'POST'){    // 可能是用户发送的消息

     if(sha1Str !== signature)response.end('error');

    // 接收用户信息

    const userXMLData = await getUserXMLData(request);

    // 将 xml 数据转化为 js 对象____xml2js

    }else{

    response.end('error');

    };

    // 当服务器没有返回响应时,微信服务器会发送 3 次请求到开发者服务器

    // 每次请求都会占用使用接口的使用次数

    // 为了保证后续开发有次数,返回一个响应 resquest.end('');

    /*

    如果node 服务器 发送一个不正确的 xml 消息,会在用户微信报错

     

    */

    实例源代码:

    index.js

    • const express = require('express');
      const handleRequest = require('./handleRequest');
      
      const app = express();
      
      app.use(express.urlencoded({extended: true}));
      
      app.use(handleRequest());    // 使用中间件的方式 激活自定义模块
      
      app.listen(
          3000,
          err=>console.log(err?err:'
      
      服务器已启动
      		Hunting Happy!')
      );

    config/index.js

    • module.exports = {    // 微信公众号 配置信息
          token: 'FinnKou',
          appID: 'wxba554569dbd1127d22cd32d',
          appsecret: '62ad759915d251f276680fc153b618d77b2e31'
      };

    handleRequest/index.js

    • const sha1 = require('sha1');    // sha1 加密库
      const {token} = require('../config');
      const {getUserXMLData} = require('../utils/getUserDataAsync');
      const {XML2JSON, JSON2Obj} = require("../utils/tools");
      
      module.exports = ()=>{
          return async (request, response, next)=>{
              const {signature, echostr, timestamp, nonce} = request.query;
              
              const sha1Str = sha1([timestamp, nonce, token].sort().join(""));
              
              if(request.method === 'GET'){    // 服务器发过来的消息
                  if(sha1Str === signature){
                      response.end(echostr);
                  }else{
                      response.end("error");
                  };
              }else if(request.method === 'POST'){
                  if(sha1Str !== signature){    // 非微信用户发过来的消息
                      response.end("error");
                  };
                  // 用户微信客户端 发过来的消息
                  /****
                   <xml>
                   <ToUserName><![CDATA[gh_d9bf45407d2a]]></ToUserName>
                   <FromUserName><![CDATA[oSX3Z1aufrhsCwuEKXbVRfqOC1Wo]]></FromUserName>
                   <CreateTime>1545741162</CreateTime>
                   <MsgType><![CDATA[text]]></MsgType>
                   <Content><![CDATA[1]]></Content>
                   <MsgId>6638907739305821698</MsgId>
                   </xml>
                   ****/
                  const XMLData = await getUserXMLData(request);
                  const JSONData = XML2JSON(XMLData);
                  const objData = JSON2Obj(JSONData);
                  /*
                      {
                          ToUserName: 'gh_d9bf45407d2a',
                          FromUserName: 'oSX3Z1aufrhsCwuEKXbVRfqOC1Wo',
                          CreateTime: '1545744183',
                          MsgType: 'text',
                          Content: '1',
                          MsgId: '6638920714402022972'
                      }
                 */
                  
                  if(objData.Content === '1'){
                      response.send(`<xml>
                            <ToUserName><![CDATA[${objData.FromUserName}]]></ToUserName>
                            <FromUserName><![CDATA[${objData.ToUserName}]]></FromUserName>
                            <CreateTime>${Date.now()}</CreateTime>
                            <MsgType><![CDATA[text]]></MsgType>
                            <Content><![CDATA[圣诞节快乐]]></Content>
                            </xml>`
                      );
                  }else{
                      response.send(
                          `<xml>
                          <ToUserName><![CDATA[${objData.FromUserName}]]></ToUserName>
                          <FromUserName><![CDATA[${objData.ToUserName}]]></FromUserName>
                          <CreateTime>${Date.now()}</CreateTime>
                          <MsgType><![CDATA[text]]></MsgType>
                          <Content><![CDATA[hello world!]]></Content>
                          </xml>`
                      );
                  };
              }else{
                  response.end("error");
              };
          };
      };

    utils/tools.js

    • const {parseString} = require('xml2js');    // xml2js 是第三方库,XML 字符串 转 JSON 对象
      
      module.exports = {
          XML2JSON(originXML){    // XML 字符串 转 JSON 对象
              let JSONData = null;
              parseString(originXML, {"trim": true}, (err, result)=>{
                  if(!err){
                      JSONData = result;
                  };
              });
              return JSONData;
          },
          JSON2Obj({xml}){    // JSON 对象 转成 普通对象
              let Obj = {};
              for (let attr in xml){
                  const value = xml[attr];
                  Obj[attr] = value[0];
              };
              
              return Obj;
          }
      };

    utils/getUserDataAsync.js

    • module.exports = {
          getUserXMLData(request){    // 获取用户微信客户端 发送到 服务器的 XML 字符串
              return new Promise((resolve, reject)=>{
                  let resultStr = '';
                  request.on("data", browser_info=>{    // 流方式 接受
                      resultStr += browser_info.toString();
                  }).on("end", ()=>{
                      resolve(resultStr);
                  });
              });
          }
      };

    2. 看手册,根据用户的内容,类型,回复你想要回复的内容

    源代码:

    index.js

    • const express = require('express');
      const handleRequest = require('./handleRequest');
      
      const app = express();
      
      app.use(express.urlencoded({extended: true}));
      
      app.use(handleRequest());
      
      app.listen(
          3000,
          err=>console.log(err?err:'
      
      服务器已启动
      		Hunting Happy!')
      );

    config/index.js

    • module.exports = {
          token: 'FinnKou',
          appID: 'wxba59d182bd7d2245cd32d',
          appsecret: '62ad7594595d2f276680f454cb618d77b2e31'
      };

    handleRequest/index.js

    • const sha1 = require('sha1');
      const {token} = require('../config');
      const {getUserXMLData} = require('../utils/getUserDataAsync');
      const {XML2JSON, JSON2Obj} = require('../utils/tools');
      const {autoReply} = require('../autoReply');
      
      module.exports = ()=>{
          return async (request, response, next)=>{
              const {signature, echostr, timestamp, nonce} = request.query;
              
              const sha1Str = sha1([timestamp, nonce, token].sort().join(""));
              
              if(request.method === 'GET'){    // 服务器发过来的消息
                  if(sha1Str === signature){
                      response.end(echostr);
                  }else{
                      response.end("error");
                  };
              }else if(request.method === 'POST'){
                  if(sha1Str !== signature){    // 非微信用户发过来的消息
                      response.end("error");
                  };
                  // 用户发过来的消息
                  const XMLData = await getUserXMLData(request);
                  const JSONData = XML2JSON(XMLData);
                  const objData = JSON2Obj(JSONData);
          
                  console.log(objData);
                  response.send(autoReply(objData));    // 封装 自动回复用户消息
                  
              }else{
                  response.end("error");
              };
          };
      };

    utils/getUserDataAsync.js

    • module.exports = {
          getUserXMLData(request){
              return new Promise((resolve, reject)=>{
                  let resultStr = '';
                  request.on("data", browser_info=>{
                      resultStr += browser_info.toString();
                  }).on("end", ()=>{
                      resolve(resultStr);
                  });
              });
          }
      };
      /****    用户发过来的消息
          <xml>
          <ToUserName><![CDATA[gh_d9bf45407d2a]]></ToUserName>
          <FromUserName><![CDATA[oSX3Z1aufrhsCwuEKXbVRfqOC1Wo]]></FromUserName>
          <CreateTime>1545741162</CreateTime>
          <MsgType><![CDATA[text]]></MsgType>
          <Content><![CDATA[1]]></Content>
          <MsgId>6638907739305821698</MsgId>
          </xml>
      ****/

    utils/tools.js

    • const {parseString} = require('xml2js');
      
      module.exports = {
          XML2JSON(originXML){
              let JSONData = null;
              parseString(originXML, {"trim": true}, (err, result)=>{
                  if(!err){
                      JSONData = result;
                  };
              });
              return JSONData;
          },
          JSON2Obj({xml}){
              let Obj = {};
              for (let attr in xml){
                  const value = xml[attr];
                  Obj[attr] = value[0];
              };
              
              return Obj;
          }
      };
      // 用户微信客户端 发过来的消息
      /****
       {
          ToUserName: 'gh_d9bf45407d2a',
          FromUserName: 'oSX3Z1aufrhsCwuEKXbVRfqOC1Wo',
          CreateTime: '1545744183',
          MsgType: 'text',
          Content: '1',
          MsgId: '6638920714402022972'
       }
      ****/

    autoReply/index.js

    • const {getReplyInfo} = require('./getReplyInfo');
      
      module.exports = {
          autoReply(objData){
              
              const options = getReplyInfo(objData);
          
              let replyXML = `<xml>
                                <ToUserName><![CDATA[${options.toUserName}]]></ToUserName>
                                <FromUserName><![CDATA[${options.fromUserName}]]></FromUserName>
                                <CreateTime>${options.createTime}</CreateTime>
                                <MsgType><![CDATA[${options.msgType}]]></MsgType>
                             `;
              switch (options.msgType) {
                  case 'text':{
                      replyXML += `<Content><![CDATA[${options.content}]]></Content>`;
                  }break;
                  
                  case 'image':{
                      replyXML += `<MsgType><![CDATA[${options.msgType}]]></MsgType>`;
                      replyXML += `<Image><MediaId><![CDATA[${options.image}]]></MediaId></Image>`;
                  }break;
          
                  case 'voice':{
                      replyXML += `<MsgType><![CDATA[${options.msgType}]]></MsgType>`;
                      replyXML += `<Voice><MediaId><![CDATA[${options.voice}]]></MediaId></Voice>`;
                  }break;
          
                  case 'video':{
                      replyXML += `<MsgType><![CDATA[${options.msgType}]]></MsgType>`;
                      replyXML += `<Video>
                                      <MediaId><![CDATA[${options.video}]]></MediaId>
                                      <Title><![CDATA[${options.videoTitle}]]></Title>
                                      <Description><![CDATA[${options.videoDescription}]]></Description>
                                   </Video>`;
                  }break;
          
                  case 'music':{
                      replyXML += `<MsgType><![CDATA[${options.msgType}]]></MsgType>`;
                      replyXML += `<Music>
                                      <Title><![CDATA[${options.musicTitle}]]></Title>
                                      <Description><![CDATA[${options.musicDescription}]]></Description>
                                      <MusicUrl><![CDATA[${options.musicUrl}]]></MusicUrl>
                                      <HQMusicUrl><![CDATA[${options.hqMusicUrl}]]></HQMusicUrl>
                                      <ThumbMediaId><![CDATA[${options.musicMediaId}]]></ThumbMediaId>
                                   </Music>`;
                  }break;
          
                  case 'news':{
                      replyXML += `<MsgType><![CDATA[${options.msgType}]]></MsgType>`;
                      replyXML += `<ArticleCount>${options.content.length}</ArticleCount><Articles>`;
          
                      options.content.forEach(item => {
                          replyXML += `<item>
                                            <Title><![CDATA[${item.title}]]></Title>
                                            <Description><![CDATA[${item.description}]]></Description>
                                            <PicUrl><![CDATA[${item.picUrl}]]></PicUrl>
                                            <Url><![CDATA[${item.url}]]></Url>
                                       </item>`;
                      });
          
                      replyXML += `</Articles>`;
                  }break;
              };
              
              replyXML += '</xml>';
              return replyXML;
          }
      };

    autoReply/getReplyInfo.js

    • module.exports = {
          getReplyInfo(objData){
              let options = {
                  toUserName: objData.FromUserName,
                  fromUserName: objData.ToUserName,
                  createTime: Date.now(),
                  msgType: objData.MsgType,
                  
                  /**** 文本 ****/
                  content: objData.Content,
                  
                  /**** 图片 ****/
                  image: objData.MediaId,    // MediaId
                  
                  /**** 语音 ****/
                  voice: objData.MediaId,    // MediaId
                  recognition: objData.Recognition,    // MediaId
                  
                  /**** 视频 ****/
                  video: objData.MediaId,    // MediaId
                  videoTitle: objData.Title,
                  videoDescription: objData.Description,
                  
                  /**** 音乐 ****/
                  musicTitle: objData.Title,
                  musicDescription:objData.Description,
                  musicUrl: objData.MusicUrl,
                  hqMusicUrl: objData.HQMusicUrl,
                  musicMediaId: objData.MediaId,    // MediaId
                  
                  /**** 地理位置 ****/
                  location_X: objData.Location_X,
                  location_Y: objData.Location_Y,
                  scale: objData.Scale,
                  label: objData.Label,
          
                  /**** +位置 ****/
                  latitude: objData.Latitude,
                  longitude: objData.Longitude,
                  precision: objData.Precision,
          
                  /**** 菜单 CLICK ****/
                  eventKey: objData.EventKey
              };
              
              switch (options.msgType) {
                  case 'text':{    // 文本
                      switch (objData.Content) {
                          case 'news':{
                              options.msgType = 'news';
                              options.content = [{
                                  title: '微信公众号开发',
                                  description: '这里有最新的公众号教程',
                                  picUrl: 'https://ss2.baidu.com/6ONYsjip0QIZ8tyhnq/it/u=2136740882,3271518133&fm=58&bpow=630&bpoh=630',
                                  url: 'http://www.atguigu.com'
                              }];
                          }break;
                          
                          case '1':{
                              options.content = '我会对你 1 心 1 意~';
                          }break;
                          
                          case '2':{
                              options.content = '其实你一点都不 2 ~';
                          }break;
                          
                          case '3':{
                              options.content = '可能也会 3 心 二 意~';
                          }break;
                      };
                  }break;
          
                  case 'voice':{    // 语音
                      options.msgType = 'text';
                      options.content = `语音识别结果为:${options.recognition}`;
                  }break;
              
                  case 'location':{    // + 位置
                      options.msgType = 'text';
                      options.content = ` 地理位置纬度:${options.location_X}
                                          地理位置经度:${options.location_Y}
                                          地图缩放大小: ${options.scale}
                                          地理位置信息: ${options.label}`;
                  }break;
                  
                  case 'event':{    // 事件推送
                      switch (objData.Event) {
                          case 'subscribe':{    // 订阅
                              options.msgType = 'text';
                              options.content = '欢迎订阅 FinnKou 的内容!'
                              if (objData.EventKey) {
                                  //扫描带参数二维码关注的。 一般应用在活动上
                                  options.content = '扫描带参数二维码关注, 欢迎您关注公众号~';
                              }
                          }break;
                          
                          case 'unsubscribe':{    // 取消订阅
                              console.log('无情取关~');
                          }break;
          
                          case 'LOCATION':{    // 用户上报地理位置
                              // 当用户关注公众号时,它会问你是否允许上报地理位置,如果允许才会触发当前事件
                              options.msgType = 'text';
                              options.content = ` 地理位置纬度:${options.latitude}
                                                  地理位置经度:${options.longitude}
                                                  位置信息: ${options.precision}`;
                          }break;
          
                          case 'CLICK':{    // 用户点击菜单按钮
                              options.msgType = 'text';
                              options.content = `菜单 key:${options.eventKey}`;
                          }break;
                      };
                  }break;
          
                  case 'link':{    // 链接消息
              
                  }break;
                  
                  case 'image':{    // 图片
                  
                  }break;
                  
                  case 'video':{    // 视频
                  
                  }break;
          
                  case 'shortvideo':{    // 小视频
              
                  }break;
                  
                  case 'music':{    // 音乐
                  
                  }break;
                  
                  case 'news':{    // 图文
                  
                  }break;
              };
              
              return options;
          }
      };

     

    --------小尾巴 ________一个人欣赏-最后一朵颜色的消逝-忠诚于我的是·一颗叫做野的心.决不受人奴役.怒火中生的那一刻·终将结束...
  • 相关阅读:
    Android 开发笔记___复选框__checkbox
    Android 开发笔记___FrameLayout
    Android 开发笔记___RelativeLayout
    Android 开发笔记___初级控件之实战__计算器
    Android 开发笔记___shape
    Android 开发笔记___DateUtil——Time
    改良版 导航栏自动跟随
    简洁 js排序算法
    简单使用rem方案适配移动设备
    导航栏监听页面滚动跟随 简单封装
  • 原文地址:https://www.cnblogs.com/tianxiaxuange/p/10174357.html
Copyright © 2011-2022 走看看