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