一个公众号,最多可以创建 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
微信公众号_订阅号_源码_用户管理_自定义菜单_自动回复用户消息_素材上传与下载
原文:https://www.cnblogs.com/baixiaoxiao/p/10573466.html