微信公众号开发 + node.js服务器部署(一)

一.申请微信公众号账号;官网网址

二.开发文档;网址

三.接口测试号申请并测试连接

url:要被外网访问,本地服务器,搭建ngrok 即可。当然有钱的话,可以买一个;ngrok每次运行都不一样。指令 ngrok http 端口号。内网穿透,有时效性。
token:微信消息加密的参数
测试是否连接,有什么参数
在这里插入图片描述
配置本地服务器
index.js

// 引入express模块
const express = require('express')
// app应用对象
const app = express();

app.use((req,res,next) => {
    // 微信服务器自动开发者服务器是哪个
        //测试号
        console.log(req.query)
})

//监听端口号
app.listen(4000,()=>console.log("服务器启用"))

确保本地服务器打开, 点击微信公众号刚刚配置的地方–提交按钮

在这里插入图片描述

// { signature: '35eab388c24c9d0deafa3bd12513d6f0cac7ac03', //微信加密签名
//   echostr: '4361760528703928317', // 微信的随机字符串
//   timestamp: '1555216285', // 微信的发生时间
//   nonce: '316998338' } // 随机数

四.计算出signature微信加密签名和微信传递过来的参数值是否一样。一样就说明是微信服务器传来的信息

a.将参与微信加密的三个参数(timestamp,nonce,token),组合在一起,安装字典排序并组合在一起形成一个数组;
b.将数组所有参数拼接一个字符串,进行sha1加密;
c.如果一样,就消息来自微信服务器,返回echostr给微信服务器;如果不一样,就报个错

index.js

// 引入express模块
const express = require('express')
// app应用对象
const app = express();
const  sha1= require('sha1')

const config = {
    token:'sd*********dssda22345',
    appID:'wxf*******0cf5a8b',
    appsecret:'9d1fa6c8eee80c5**********cf8b53'
}

app.use((req,res,next) => {
       // console.log(req.query)
        // { signature: '35eab388c24c9d0deafa3bd12513d6f0cac7ac03', //微信加密签名
        //   echostr: '4361760528703928317', // 微信的随机字符串
        //   timestamp: '1555216285', // 微信的发生时间
        //   nonce: '316998338' } // 随机数
    const {signature,echostr,timestamp,nonce}  = req.query;   
    const {token} =config;
    const arr = [timestamp , nonce , token]
    const arrSort = arr.sort();
    console.log(arrSort)
    const str = arrSort.join('')
    const sha1Str = sha1(str)
    console.log(sha1Str)
    if(sha1Str === signature){
        res.send(echostr)
    }else{
        res.send('error')
    }

})

//监听端口号
app.listen(4000,()=>console.log("服务器启用"))

微信公众号提示配置成功即可

五.获取access_token

access_token是公众号的全局唯一接口调用凭据,公众号调用各接口时都需使用access_token。开发者需要进行妥善保存。access_token的存储至少要保留512个字符空间。access_token的有效期目前为2个小时,需定时刷新,重复获取将导致上次获取的access_token失效,还有请求次数

https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET

设计思路:
1.首次本地没有,发送请求获取access_token,保存(本地)
2.第二次,先去本地读取,判断是否过期。没有直接使用;过期的话,重新请求。过期时间判断。当前时间+7200,之前就行

//获取access_token 微信调用接口的唯一凭据
// 需要的url参数
const {appID , appsecret} = require('../config')
const rp = require('request-promise-native')
const {readFile , writeFile} = require('fs')
class Wechat {
    constructor(){

    }
    //获取access_token
    getAccessToken(){
        const url =`https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${appID}&secret=${appsecret}`;
        // 发送请求
        // 服务器端不能ajax 使用 request request-promise-native,返回的是promise对象
        return new Promise((resolve,reject) => {
            rp({method:'get',url,json:true})
            .then(res =>{
                console.log(res)
                res.expires_in = Date() + (res.expires_in -300)*1000;
                resolve(res)
                // { access_token:
                //     '20_ZCrQnrFmz20x21BwfYy9b2fvJvqQPvQZ5NQMeiy5LZwRfKXgtgArpNFl-WnGDT0QvvTI94CjJ459Fy8IfFn4g00tskZ_Z76xgEDe8fXYnGXd9PwkZhQJ0_mN8FE_sVPVPz1x7OFy0RRPbyfMVXGaAJAIKT',
                //    expires_in: 7200 }
            })
            .catch(err =>{
                console.log(err);
                reject('getAccessToken errer')
            })
        })
    }
    //保存access_token
    /**
     * @param accessToken
     */
    saveAccessToken(accessToken){
        //将对象转化成json字符串
        accessToken = JSON.stringify(accessToken);

        return new Promise((resolve,reject)=>{
            writeFile('./accessToken.txt',accessToken,err => {
                if(!err){
                    console.log("file write success");
                    resolve();
                }else{
                    console.log(err);
                    reject();
                }
            })
        })
    }
    // 读取access_token
    readAccessToken(){
        return new Promise((resolve,reject)=>{
            readFile('./accessToken.txt',(err,data) => {
                if(!err){
                    console.log("file read success");
                    data = JSON.parse(data)
                    resolve(data);
                }else{
                    console.log(err);
                    reject();
                }
            })
        })
    }
    // 是否有效
    isValidAccessToken(data){
        if(!data&&data.access_token&&!data.expires_in){
            return false;
        }
        return data.expires_in > data.now()
    }
    // 获取没有过期的access_token
    /**
     * @param
     */
    fetchAccessToken(){
        // console.log(this.access_token)
        if(this.access_token && this.expires_in && this.isValidAccessToken(this)) {
            // 保存过access_token
            return Promise.resolve({
                access_token:this.access_token,
                expires_in:this.expires_in,
            })
        }
        return this.readAccessToken()
            .then( async res => {
                // 本地有文件
                if(this.isValidAccessToken(res)){
                    resolve(res)
                }else{
                    // 过期
                const res= await this.getAccessToken();
                await this.saveAccessToken(res);
                return Promise.resolve(res)
                }
            })
            .catch( async err => {
                const res= await this.getAccessToken();
                await this.saveAccessToken(res);
                return Promise.resolve(res)
            })
            .then(res => {
                this.access_token = res.access_token;
                this.expires_in = res.expires_in;
                return Promise.resolve(res);
            })        
    }
}
// 测试文件是否成功新建类,调用fetchAccessToken()方法

六.接受回复用户信息

数据信息流式数据 xml格式文本
自动回复
注意点
1.5秒内自动回复
2.开发者数据格式

until.js

const { parseString } = require('xml2js')
module.exports = {
    // 流式信息拼接
    getUserDataAsync(req) {
        return new Promise((resolve, reject) => {
            let xmlData = '';
            req.on('data', data => {
                console.log(data)
                xmlData += data.toString();
            })
                .on('end', () => {
                    resolve(xmlData)
                })
        })
    },
    //xml to js
    parseXMLAsync(xmlData){
        return new Promise((resolve,reject)=>{
            parseString(xmlData,{trim:true},(err,data)=>{
                if(!err){
                    resolve(data)       
                }else{
                    reject("parseXMLAsync errer:" +err)
                }
            })
        })
    },
    // 字段将数组改成对应值
    formatMessage(jsData){
        let message = {};
        jsData = jsData.xml;
        //判断数据是否是一个对象
        if(typeof jsData === "object"){ 
            for(let key in jsData){
                let value = jsData[key];
                // 过滤空的数据
                if(Array.isArray(value)&&value.length>0){
                    message[key] = value[0];
                }
            }            
        }
        return message;
    }
}

auth.js

// 类似中间件,封装
const config = require('../config')
const sha1 = require('sha1')
const { getUserDataAsync , parseXMLAsync ,formatMessage} = require('../utils/tool.js')
module.exports = () => {
    return async (req, res, next) => {
        // console.log(req.query)
        // { signature: '35eab388**************6f0cac7ac03', //微信加密签名
        //   echostr: '436176052*******8317', // 微信的随机字符串
        //   timestamp: '155****16285', // 微信的发生时间
        //   nonce: '316998338' } // 随机数
        const { signature, echostr, timestamp, nonce } = req.query;
        const { token } = config;
        const arr = [timestamp, nonce, token]
        const arrSort = arr.sort();
        const str = arrSort.join('')
        const sha1Str = sha1(str)
        // const sha1Str =  sha1([timestamp,nonce,token].sort().join(''))
        // console.log(sha1Str)


        /**
         * 微信服务器发送get post两种类型的开发者给服务器
         */

        // { signature: 'd25a9573617f**********7e87d4062475a',
        // timestamp: '1558****176',
        // nonce: '8018***15',
        // openid: 'o*************w3nObg' }
        if (req.method === "GET") {
            if (sha1Str === signature) {
                res.send(echostr)
            } else {
                res.send('wechat server error')
            }
        } else if (req.method === "POST") {
            if (sha1Str !== signature) {
                res.send("errer")
            } else {
                console.log(req.query)
            }
            const xmlData = await getUserDataAsync(req);
            // buffer数据
            console.log(xmlData)


                // < xml > 
                //     <ToUserName><![CDATA[gh_d772f38c5ba2]]></ToUserName> //开发者id
                //     <FromUserName><![CDATA[o1Q3x03Pn0eLpM8wlwFHn7w3nObg]]></FromUserName> //用户openid
                //     <CreateTime>1555231639</CreateTime> //发送的时间
                //     <MsgType><![CDATA[text]]></MsgType> //信息类型
                //     <Content><![CDATA[123]]></Content> //信息内容
                //     <MsgId>22265565114514992</MsgId> //消息id 微信服务器保存三天
                // </xml >
                // xml对象转为js对象
            const jsData = await parseXMLAsync (xmlData)
                console.log(jsData)

            /**
             * 格式化
             */
            var message = formatMessage(jsData)

            // 用户信息是否是文本信息
            let content = '您在说什么,我听不清';
            if(message.MsgType == 'text'){
                if(message.Content === '0'){
                    content = '对不起,我是个警察';
                }else if (message.Content === '1'){
                    content = '以前没得选';
                }else if (message.Content === '2'){
                    content = '现在我想做个好人';
                }else if (message.Content.match('讨厌')){
                    content = '没事,有我在';
                }else if (message.Content.match('喜欢')){
                    content = '我也喜欢你啊';
                }
            }

            let replyMessage =`<xml>
            <ToUserName><![CDATA[${message.FromUserName}]]></ToUserName>
            <FromUserName><![CDATA[${message.ToUserName}]]></FromUserName>
            <CreateTime>${Date.now()}</CreateTime>
            <MsgType><![CDATA[text]]></MsgType>
            <Content><![CDATA[${content}]]></Content>
          </xml>`
            //返回给服务器
            console.log(replyMessage)
            res.send(replyMessage);
            
            //请求没响应的话是三次
            // res.end('')
        } else {
            res.send("errer")
        }
    }
}

猜你喜欢

转载自blog.csdn.net/weixin_44420276/article/details/89301267