一个公众号,最多可以创建 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 /*********************************************************************************/