微信公众号_订阅号_源码_用户管理_自定义菜单_自动回复用户消息_素材上传与下载

 一个公众号,最多可以创建 100 个标签

查看手册,根据 请求 url,以及参数说明,请求体格式,进行编程 。

 

// 前端面试题
// 每当执行栈为空时,就检查微任务,有则进栈执行
// 当检查无微任务了,再检查宏任务,有则取一个宏任务进栈执行,执行完了,再检查有没有微任务......repeat

// 微任务:

// 1. process.nextTick (nodejs)
// 2. Promise.then catch

 

// 宏任务:

// 1. I/O (click事件、fs.writeFile)
// 2. setTimeout
// 3. setInterval
// 4. setImmediate (nodejs)
// 5. requestAnimationFrame

 

实例源代码:

config/index.js

  • const prefix = 'https://api.weixin.qq.com/cgi-bin/';
    module.exports = {
        SERVER_IP: 'localhost',
        SERVER_PORT: '3000',
        DB_PORT: '27017',
        
        token: 'FinnKou',
        APPID: 'wxba159db33d7d22c1d32d',
        APPSECRET: '62ad175995d2f246680fcb6218d77b24e31',
        
        prefix : prefix,
        ACCESSTOKEN: `${prefix}token?grant_type=client_credential&`,
    };

WeChat/index.js

  • /****
     *  access_token 对象____中控服务器----公众号的全局唯一接口调用凭据
     *
     *  {
     *      access_token: '17_Nq3M5HMdnX3Xwkbi48uPEaVZ4qnh_H5B8HOzBy-DnXqLz6s9h3ALAPd6sk11K0zclzu0Ap3cZciBVp2aml9EuJGmSZ-iGKe7gFOwVUEYGhOB70Il9GeCMWtppgpXcdMzm7YaqVE_W55L1bgfBEQcAHAGJV',
     *      expires_in: 7200
     *  }
     ****/
    const promiseRequest = require('request-promise-native');
    const {APPID, APPSECRET, ACCESSTOKEN} = require('../config');
    const {writeFile, readFile} = require('fs');
    
    class WeChat{
        getValidAccessToken(){
            // 1. 判断 wechat 对象里的  access_token
            if(this.access_token && this.isValidAccessToken(this)){
                return Promise.resolve({
                    access_token: this.access_token,
                    expires_in: this.expires_in
                });
            }else{
                return this.readAccessToken().then(async objAccessToken=>{
                    if  (this.isValidAccessToken(objAccessToken)){
                        return objAccessToken;    //
                    }else{
                        const newObjAccessToken = await this.requestAccessToken();
                        await this.saveAccessToken(newObjAccessToken);
                        return newObjAccessToken;
                    }
                }).catch(async err=>{
                    const newObjAccessToken = await this.requestAccessToken();
                    await this.saveAccessToken(newObjAccessToken);
                    return newObjAccessToken;
                }).then(objAccessToken=>{
                    // 更新 WeChat
                    this.access_token = objAccessToken.access_token;
                    this.expires_in = objAccessToken.expires_in;
                    
                    // 返回 Promise 的 access_token
                    return Promise.resolve(objAccessToken);
                });
            };
        }
        
        readAccessToken(){    // 一、读取access_token的方法
            return new Promise((resolve, reject)=>{
                readFile('./access_token.txt', (err, buffer)=>{
                    if(err){
                        reject('Read ./access_token.txt' + err);
                    }else{
                        resolve(JSON.parse(buffer.toString()));
                    }
                });
            });
        }
        
        isValidAccessToken({expires_in}){    // 二、判断 access_token 是可用的吗?
            return expires_in > Date.now();
        };
        
        async requestAccessToken(){    // 三、发送请求 getAccessToken() 获取 access_token
            // 1. access_token 请求 url
            const url = `${ACCESSTOKEN}appid=${APPID}&secret=${APPSECRET}`;
            
            // 2. POST 请求 access_token 对象
            const objAccessToken = await promiseRequest({
                method: 'POST',
                url,
                json: true
            });
            
            // 重写过期时间,提前 5 分钟刷新
            objAccessToken.expires_in = Date.now() - (7200 - 300)*1000;
            return objAccessToken;
        }
        
        saveAccessToken(objAccessToken){    // 四、保存 access_token 到文件
            return new Promise((resolve, reject)=>{    // 异步执行文件写完
                writeFile('./access_token.txt', JSON.stringify(objAccessToken), err=>{
                    if(err){
                        reject("Write Success.");
                    }else{
                        resolve('access_token 最新已保存');
                    };
                });
            });
        }
    };
    
    const wechat = new WeChat();
    
    module.exports = {
        wechat
    };

WeChat/fans.js

  • const {prefix} = require('../config');
    const promiseRequest = require('request-promise-native');
    
    const tagsCreate = `${prefix}tags/create?`;
    const tagsGet = `${prefix}tags/get?`;
    const tagsUpdate = `${prefix}tags/update?`;
    const tagsDelete = `${prefix}tags/delete?`;
    
    const usersGet = `${prefix}user/tag/get?`;
    const usersBatch = `${prefix}tags/members/batchtagging?`;
    
    const allUserGet = `${prefix}user/get?`;
    
    const userInfo = `${prefix}user/info?`;
    
    const sendall = `${prefix}message/mass/sendall?`;
    
    module.exports = {
    /**** 标签操作 ****/
        // 增:根据 idName 创建一个标签
        async createTag(wechat, idName){
            const {access_token} = await wechat.getValidAccessToken();
            const url = `${tagsCreate}access_token=${access_token}`;
            return await promiseRequest({method: 'POST', url, json: true, body:{"tag":{"name": idName}}});
        },
        
        // 查:根据 idNumber idName 获取一个标签
        async getTag(wechat){
            const {access_token} = await wechat.getValidAccessToken();
            const url = `${tagsGet}access_token=${access_token}`;
            return await promiseRequest({method: 'GET', url, json: true});
        },
        
        // 改:根据 idNumber newName 修改一个标签
        async updateTag(wechat, idNumber, newName){
            const {access_token} = await wechat.getValidAccessToken();
            const url = `${tagsUpdate}access_token=${access_token}`;
            return await promiseRequest({method: 'POST', url, json: true, body:{"tag":{"id": idNumber, "name": newName}}});
        },
        
        // 删:根据 idNumber 删除一个标签
        async deleteTag(wechat, idNumber){
            const {access_token} = await wechat.getValidAccessToken();
            const url = `${tagsDelete}access_token=${access_token}`;
            return await promiseRequest({method: 'POST', url, json: true, body:{"tag":{"id": idNumber}}});
        },
        
    /**** 根据标签 操作用户 ****/
        // 查:根据 idNumber 获取用户
        async getUsersByTag(wechat, idNumber, next_openid=''){
            const {access_token} = await wechat.getValidAccessToken();
            const url = `${usersGet}access_token=${access_token}`;
            return await promiseRequest({method: 'POST', url, json: true, body:{id:idNumber, next_openid}});
        },
        
        // 增:给一个标签 idNumber 添加用户 openid_list
        async addUsersToTag(wechat, idNumber, openid_list){
            const {access_token} = await wechat.getValidAccessToken();
            const url = `${usersBatch}access_token=${access_token}`;
            return await promiseRequest({method: 'POST', url, json: true, body:{id:idNumber, openid_list}});
        },
        
    /**** 获取公众号所有 用户 ****/
        async getAllUser(wechat, next_openid=''){
            const {access_token} = await wechat.getValidAccessToken();
            const url = `${allUserGet}access_token=${access_token}&next_openid=${next_openid}`;
            return await promiseRequest({method: 'GET', url, json: true});
        },
    
    /**** 根据 openid 操作用户 ****/
        // 查:根据 openid 获取用户信息
        async getUserInfo(wechat, openid){
            const {access_token} = await wechat.getValidAccessToken();
            const url = `${userInfo}access_token=${access_token}&openid=${openid}`;
            return await promiseRequest({method: 'GET', url, json: true});
        },
    /**** 群发消息给 标签 下的粉丝 ****/
        async sendToAllByTag(wechat, body){
            const {access_token} = await wechat.getValidAccessToken();
            const url = `${sendall}access_token=${access_token}`;
            return await promiseRequest({method: 'POST', url, json:true, body});
        }
    };

WeChat/menu.js

  • const {prefix} = require('../config');
    const promiseRequest = require('request-promise-native');
    
    const menuDelete = `${prefix}menu/delete?`;
    const menuCreate = `${prefix}menu/create?`;
    
    module.exports = {
        async deleteMenu(wechat){
            const {access_token} = await wechat.getValidAccessToken();
            const url = `${menuDelete}access_token=${access_token}`;
            return await promiseRequest({method: 'Get', url, json: true});
        },
        
        async createMenu(wechat, menu){
            const {access_token} = await wechat.getValidAccessToken();
            const url = `${menuCreate}access_token=${access_token}`;
            return await promiseRequest({method: 'POST', url, json: true, body: menu});
        },
        
        menu: {
            "button":[
                {
                    "type":"click",
                    "name":"一级菜单☀",
                    "key":"click"
                },
                {
                    "name":"一级菜单⛄",
                    "sub_button":[
                        {
                            "name":"百度",
                            "url":"http://www.baidu.com/",
                            "type":"view"
                        },
                        {
                            "name": "扫码带提示🌸",
                            "type": "scancode_waitmsg",
                            "key": "rselfmenu_0_0"
                        },
                        {
                            "name": "扫码推事件",
                            "type": "scancode_push",
                            "key": "rselfmenu_0_1"
                        },
                        {
                            "name": "系统拍照发图",
                            "type": "pic_sysphoto",
                            "key": "rselfmenu_1_0",
                            "sub_button": [ ]
                        },
                        {
                            "name": "拍照或者相册发图",
                            "type": "pic_photo_or_album",
                            "key": "rselfmenu_1_1",
                            "sub_button": [ ]
                        },
                    ]
                },
                {
                    "name":"一级菜单🌕",
                    "sub_button":[
                        {
                            "name": "微信相册发图",
                            "type": "pic_weixin",
                            "key": "rselfmenu_1_2"
                        },
                        {
                            "name": "发送位置",
                            "type": "location_select",
                            "key": "rselfmenu_2_0"
                        },
                        // {
                        //   "type": "media_id",
                        //   "name": "图片",
                        //   "media_id": "MEDIA_ID1"
                        // },
                        // {
                        //   "type": "view_limited",
                        //   "name": "图文消息",
                        //   "media_id": "MEDIA_ID2"
                        // }
                    ]
                }
            ]
        }
    };

WeChat/mediaSpace.js

  • /****
     图片(image)    2M    支持PNG\JPEG\JPG\GIF格式
     语音(voice)    2M    播放长度不超过60s,支持AMR\MP3格式
     视频(video)    10MB    支持MP4格式 ---- http GET 方式获取
     缩略图(thumb)    64KB    支持JPG格式
     ****/
    
    const {prefix} = require('../config');
    
    const promiseRequest = require('request-promise-native');
    const request = require('request');
    const {createReadStream, createWriteStream} = require('fs');
    
    const mediaUpload = `${prefix}media/upload?`;
    const mediaGet = `${prefix}media/get?`;
    
    const materialNews = `${prefix}material/add_news?`;
    const materialNewsPic = `${prefix}media/uploadimg?`;
    const materialOthers = `${prefix}material/add_material?`;
    
    const materialGet = `${prefix}material/get_material?`;
    
    module.exports = {
        mediaSpace: {
        
        /*------------------------ 临时素材 ------------------------*/
            // async upload(wechat, type, filePath){
            //     const {access_token} = await wechat.getValidAccessToken();
            //     const url = `${mediaUpload}access_token=${access_token}&type=${type}`;
            //
            //     // 以 form 表单方式 发送 post 请求上传 文件流
            //     return await promiseRequest({method: 'POST', url, json: true, formData:{"media": createReadStream(filePath)}});
            // },
            
            // async download(wechat, media_id, type, filePath){
            //     const {access_token} = await wechat.getValidAccessToken();
            //     let url = `${mediaGet}access_token=${access_token}&media_id=${media_id}`;
            //
            //     if(type === 'video'){
            //         url.replace('https', 'http');
            //         return await promiseRequest({method: 'GET', url, json:true});
            //     }else{
            //         await new Promise((resolve, reject)=>{
            //             request(url)    // 返回一个可读流,使用管道写入文件
            //                 .pipe(createWriteStream(filePath))
            //                 .once('close', err=>{
            //                     resolve(err);
            //                 });
            //         });
            //     };
            // },
            
        /*------------------------- 素材 ---------------------------*/
            async upload(wechat, type, filePathOrNewsBody, isMeterialOrVideoBody = true){
                const {access_token} = await wechat.getValidAccessToken();
                let options = {method: 'POST', json: true};
                
                if(isMeterialOrVideoBody === false){
                    options.url = `${mediaUpload}access_token=${access_token}&type=${type}`;
                    // 以 form 表单方式 发送 post 请求上传 文件流
                    options.formData = {media:createReadStream(filePathOrNewsBody)};
                }else if(type === 'news'){
                    options.url = `${materialNews}access_token=${access_token}`;
                    options.body = filePathOrNewsBody;
                }else if(type === 'newsImage'){
                    options.url = `${materialNewsPic}access_token=${access_token}`;
                    options.formData = {media:createReadStream(filePathOrNewsBody)};
                }else{
                    options.url = `${materialOthers}access_token=${access_token}&type=${type}`;
                    console.log(options.url);
                    options.formData = {media:createReadStream(filePathOrNewsBody)};
                    if(type === 'video'){
                        options.body = isMeterialOrVideoBody;
                    };
                };
                return await promiseRequest(options);
            },
            
            async download(wechat, media_id, type, filePath, isMeterial = true){
                const {access_token} = await wechat.getValidAccessToken();
                let url = `${materialGet}access_token=${access_token}`;
                
                if(isMeterial === false){
                    url = `${mediaGet}access_token=${access_token}&media_id=${media_id}`;
                    if(type === 'video'){
                        url.replace('https', 'http');
                        return await promiseRequest({method: 'GET', url, json:true});
                    }else{
                        return await new Promise((resolve, reject)=>{
                            request(url)    // 返回一个可读流,使用管道写入文件
                                .pipe(createWriteStream(filePath))
                                .once('close', err=>{
                                    resolve(err);
                                });
                        });
                    }
                }else if(type === 'video' || type ===  'news'){
                    return await promiseRequest({method: 'POST', url, json: true,
                        body: {media_id}
                    });
                }else{
                    await new Promise((resolve, reject)=>{
                        console.log(url);
                        request({method: 'POST', url, json: true, body: {media_id}})
                            .pipe(createWriteStream(filePath))    // 返回一个可读流, 使用管道写入文件
                            .once('close', resolve);
                    });
                };
            },
        }
    };

app.js        

  • const express = require('express');
    const {SERVER_PORT} = require('./config');
    const handleRequest = require('./handleRequest');
    const {wechat} = require('./WeChat');
    
    const app = express();
    
    // app.use(express.urlencoded({extended: true}));
    
    // app.use(handleRequest());
    
    app.listen(
        SERVER_PORT,
        err=>console.log(err?err:'\n\n服务器已启动\n\t\tHunting Happy!')
    );
    
    
    /****************************** 自定义菜单 ***************************************/
    // const {menu, deleteMenu, createMenu} = require('./WeChat/menu');
    //
    // (async ()=>{
    //     console.log('---- 先删除菜单 ----');    // 如果有 
    //     const deleteRet = await deleteMenu(wechat);
    //     console.log(deleteRet);
    //
    //     console.log('---- 再创建菜单 ----');
    //     const createRet = await createMenu(wechat, menu);
    //     console.log(createRet);
    // })();
    
    /****************************** 标签 与 用户 *************************************/
    const {
        createTag, getTag, updateTag, deleteTag,    // 标签的 增删改查
        getUsersByTag, addUsersToTag,    // 使用 标签
        getAllUser,    // 获取所有用户
        getUserInfo,    // 获取用户信息
        sendToAllByTag    // 标签 群发
    } = require('./WeChat/fans');
    
    (async ()=>{
        let ret = await createTag(wechat, '0940_HTML5');    // 45157 重名
        console.log('创建一个标签: ');
        console.log(ret);
    
        const {tags} = await getTag(wechat);
        console.log('获取所有标签对象: ');
        console.log(tags);
        /**
         * [
         { id: 2, name: '星标组', count: 0 },    // 默认就有的 标签
         { id: 100, name: '0920_class', count: 0 },
         { id: 101, name: 'beijing', count: 0 }
         ] * */
        if(tags[2]){
            ret = await updateTag(wechat, tags[2].id, 'BeiPiao');
            console.log('改标签名字: ');
            console.log(ret);
        };
    
        // ret = await deleteTag(wechat, tags[1].id);
        // console.log('删除一个标签: ');
        // console.log(ret);
    
        const {data: usersId, next_openid} = await getAllUser(wechat);
        console.log('获取所有用户: ');
        console.log(usersId);
        console.log(next_openid);
    
        ret = await getUserInfo(wechat, usersId.openid[0]);
        console.log('查询 usersId[0] 的用户信息: ');
        console.log(ret);
        
        // oSX3Z1aufrhsCwuEKXbVRfqOC1Wo 我的 openid
        ret = await sendToAllByTag({
            filter:{
                is_to_all: false,
                tag_id: 'oSX3Z1aufrhsCwuEKXbVRfqOC1Wo'
            },
            text:{
                content: '元旦快乐!'
            },
            msgtype: 'text'
        });
    })();
    
    /****************************** 素材上传下载 *************************************/
    // const {mediaSpace} = require('./WeChat/mediaSpace');
    // const {resolve} = require('path');
    //
    // (async ()=>{
    //     // console.log('---- 上传临时媒体素材 ----');
    //     // const moose = await mediaSpace.upload(wechat, 'image', resolve(__dirname, './1.jpg'), false);
    //     // console.log(moose);
    //     /*
    //         {
    //             type: 'image',
    //             media_id: 'liuEDUcNu68MuWndCSopvv3Qr-d1qdgOKwkefKLVeRTZXdfhUVoC4q6I5viOStT_',
    //             created_at: 1545979479
    //         }
    //      */
    //     // console.log('---- 下载临时媒体素材 ----');
    //     // await mediaSpace.download(
    //     //     wechat,
    //     //     'liuEDUcNu68MuWndCSopvv3Qr-d1qdgOKwkefKLVeRTZXdfhUVoC4q6I5viOStT_',
    //     //     'image',
    //     //     './2.jpg',
    //     //     false
    //     // );
    //     /*-------------------------------------- 永久素材 ----------------------------------------------*/
    //     // console.log('---- 上传永久媒体素材 ----');
    //     // const moose = await mediaSpace.upload(wechat, 'image', resolve(__dirname, './1.jpg'));
    //     // console.log(moose);
    //         /*
    //         *   {
    //                 media_id: 'bfzYrEwXqmeYiJIdBvbZ1E7Ox7UH8DfiSo66kKWZ4FM',
    //                 url: 'http://mmbiz.qpic.cn/mmbiz_jpg/8hj96GVnlibDVib3LJoyZSJFNpa7aIITL6nvCXrOszRiahQkPoZSQUS5Lpw8RiaibrAGia03JMkeKcibY9B6jcyuAcIhA/0?wx_fmt=jpeg'
    //             }
    //         * */
    //     console.log('---- 下载永久媒体素材 ----');
    //     await mediaSpace.download(
    //         wechat,
    //         'bfzYrEwXqmeYiJIdBvbZ1JrCiKteRvzzqLK-_hZlBYg',
    //         'image',
    //         './3.jpg',
    //     );
    // })();
    // 前端面试题:
    // 每当执行栈为空时,就检查微任务,有则进栈执行
    // 当检查无微任务了,再检查宏任务,有则取一个宏任务进栈执行,执行完了,再检查有没有微任务......repeat
        // 微任务:
            // 1. process.nextTick    (nodejs)
            // 2. Promise.then catch
    
        // 宏任务:
            // 1. I/O (click事件、fs.writeFile)
            // 2. setTimeout
            // 3. setInterval
            // 4. setImmediate (nodejs)
            // 5. requestAnimationFrame
    /*********************************************************************************/

 

 

posted @ 2018-12-28 09:44  耶梦加德  阅读(345)  评论(0编辑  收藏  举报