一,创建template模块
1.auth模块引入template,let定义options传值
let options = {
toUserName:message.FromUserName,
fromUserName:message.ToUserName,
createTime:Date.now(),
msgType:'text',
content:1,
}
2.新建template模块
module.exports = options => {
let replyMessage='';
if(options.msgType === 'text'){
replyMessage='<xml>\n' +
' <ToUserName><![CDATA['+options.toUserName+']]></ToUserName>\n' + //用户id
' <FromUserName><![CDATA['+options.fromUserName+']]></FromUserName>\n' + //开发者id
' <CreateTime>'+options.createTime+'</CreateTime>\n' + //回复的时间戳
' <MsgType><![CDATA['+options.msgType+']]></MsgType>\n' + //回复的类型
' <Content><![CDATA['+options.content+']]></Content>\n' + //回复的内容
'</xml>';
}
return replyMessage;
}
3.优化template模块减少代码冗余量
4.进一步完善template模块的其他代码
/*
用来加工处理最终回复用户消息的模板
*/
module.exports = options => {
let replyMessage='<xml>\n' + //初始化一部分代码 减少代码冗余量
' <ToUserName><![CDATA['+options.toUserName+']]></ToUserName>\n' + //用户id
' <FromUserName><![CDATA['+options.fromUserName+']]></FromUserName>\n' + //开发者id
' <CreateTime>'+options.createTime+'</CreateTime>\n' + //回复的时间戳
' <MsgType><![CDATA['+options.msgType+']]></MsgType>\n'//回复的类型;
if(options.msgType === 'text'){
replyMessage+='<Content><![CDATA['+options.content+']]></Content>\n' //回复的内容
}else if(options.msgType === 'image'){
replyMessage+='<Image><MediaId><![CDATA['+options.imageId+']]></MediaId></Image>'
}else if(options.msgType === 'voice'){
replyMessage+='<Voice><MediaId><![CDATA['+options.voiceId+']]></MediaId></Voice>'
}else if(options.msgType === 'video'){
replyMessage+=' <Video><MediaId><![CDATA['+options.videoId+']]></MediaId>\n'+
'<Title><![CDATA[title]]></Title> <Description><![CDATA[description]]></Description></Video>'
}else if(options.msgType === 'music'){
replyMessage+=' <Music><Title><![CDATA[TITLE]]></Title><Description><![CDATA[DESCRIPTION]]></Description>\n' +
' <MusicUrl><![CDATA['+options.ptmusicId+']]></MusicUrl><HQMusicUrl><![CDATA['+options.HQmusicId+']]>\n' +
'</HQMusicUrl><ThumbMediaId><![CDATA['+options.music+']]></ThumbMediaId></Music>'
}else if(options.msgType === 'news'){
replyMessage+='<ArticleCount>'+options.content.length+'</ArticleCount><Articles>\n' +
//引用的content中length的长度就是声明下面的item到底有多少个
options.content.forEach( item => {
replyMessage+='<item>\n' +
' <Title><![CDATA[title1]]></Title><Description><![CDATA[description1]]></Description>\n' +
' <PicUrl><![CDATA['+item.picId+']]></PicUrl><Url><![CDATA[url]]></Url>\n' +
' </item>'
})
replyMessage+='</Articles>'
}
replyMessage+='</xml>';
return replyMessage;
}
二,创建reply模块
1.新建reply模块将auth模块中检测用户发送的消息判断代码移动过来
/*
处理用户发送的消息类型和内容,决定返回不同内容给用户
*/
module.exports = message => {
let options = { //存放各种id
toUserName:message.FromUserName,
fromUserName:message.ToUserName,
createTime:Date.now(),
msgType:'text',
imageId:1, //图片id
voiceId:1, //音频id
videoId:1, //视频id
ptmusicId:1, //普通音乐
HQmusicId:1, //高清音乐
music:1, //音乐id
picId:1, //图片链接
}
let content = '你在说什么,我不知道';
if(message.MsgType === 'text'){ //判断用户发送的消息类型
if(message.Content === '1'){ //全匹配
content='请不要扣1';
}else if(message.Content.match('520')){ //半匹配
content='520';
}
}
options.content=content;
return options;
}
2.头部引入这个模块中间把message的值传入reply模块中
3.测试语言和图片的回复功能和地理位置信息获取(接受普通消息)
3.1图片将用户发送过来的图片返回回去
3.2开启语言识别功能
3.3地理位置获取
4.用户关注取关和地理位置获取以前点击菜单事件(接受事件推送)
else if(message.MsgType === 'event'){ //接受事件推送功能
if(message.Event === 'subscribe'){ //用户订阅事件
content='欢迎你的关注';
if(message.EventKey){
console.log('用户扫描带参数的二维码关注事件');
}
}else if(message.Event === 'unsubscribe'){ //用户取消事件
console.log('无情取关');
}else if(message.Event === 'SCAN'){
console.log('用户已经关注过');
}else if(message.Event === 'LOCATION'){
let X=message.Latitude;let Y =message.Longitude;//获取经纬度
let L=message.Precision;//获取精度
content='您的纬度:'+X+'\n'+'您的经度:'+Y+'\n'+'您的地理位置精度:'+L+'\n';
console.log(content);
}else if(message.Event === 'CLICK'){ //用户点击自定义菜单事件
content = '你点击了按钮'+message.Eventkey;
console.log(content);
}
三,实现自定义菜单
3.1创建menu模块
/*
自定义菜单
*/
module.exports = {
"button":[
{
"type":"click",
"name":"今日歌曲",
"key":"click1"
},
{
"name":"菜单2",
"sub_button":[
{
"type":"view",
"name":"跳转链接",
"url":"http://www.soso.com/"
}, {
"type": "scancode_waitmsg",
"name": "扫码带提示",
"key": "扫码带提示",
},
{
"type": "scancode_push",
"name": "扫码推事件",
"key": "扫码推事件",
},
]
},
{
"name": "发图3",
"sub_button": [
{
"type": "pic_sysphoto",
"name": "系统拍照发图",
"key": "rselfmenu_1_0",
},
{
"type": "pic_photo_or_album",
"name": "拍照或者相册发图",
"key": "rselfmenu_1_1",
},
{
"type": "pic_weixin",
"name": "微信相册发图",
"key": "rselfmenu_1_2",
"sub_button": [ ]
}, {
"name": "发送位置",
"type": "location_select",
"key": "rselfmenu_2_0"
},
]
},
]
}
3.2将access_token模块更名为wechat,并在此js文件中添加自定义菜单方法
createMenu(menu){
//异步的东西为了更好的执行下去包装一层promise对象
return new Promise(async (resolve,reject) =>{
try{
//获取access_token
const data = await this.fetchAccessToken()
//定义请求的地址
const url = 'https://api.weixin.qq.com/cgi-bin/menu/create?access_token='+data.access_token+'';
//发送请求
const result = await rp({method:'POST',url,json:true,body:menu}); //如果把菜单放在body里面是以请求体的形式发送过去
resolve(result);
}catch (e){
reject('createMenu方法出了问题'+e);
}
})
}
3.3增加删除自定义菜单方法
//删除自定义菜单
deleteMenu(){
return new Promise(async (resolve,reject) => {
try{
//获取access_token
const data = await this.fetchAccessToken();
//定义一个请求地址
const url = 'https://api.weixin.qq.com/cgi-bin/menu/delete?access_token='+data+'';
//发送删除请求
const result = await rp({method:'GET',url,json:true})
resolve(result);
}catch (e){
reject('deleteMenu方法出了问题'+e);
}
})
}
3.4由于wechat模块可以脱离服务器运行 将之前 “模拟测试” 后面的代码替换为下面的代码
//因为是异步函数 所以我们写成立即执行函数
(async ()=>{
//模拟测试
const test = new Wechat();
//删除之前创建的菜单
let result = await test.deleteMenu();
console.log(result);
//创建新的菜单
let result1 = await test.createMenu(menu);
console.log(result1);
})()
3.5wechat模块完整代码
//一个实现所有的接口的功能模块
//完完全全可以脱离服务器去运行
// 整理思路:
// 读取本地文件( readAccessToken)
// -本地有文件
// 判断它是否过期(isValidAccessToken)
// -过期了
// -重新请求获取access_ token(getAccessToken), 保存下来覆盖之前的文件(保证文件是唯一 -的) (saveAccess Token)
// -没有过期
// -直接使用
// -本地没有文件
// -发送请求获取access_ token(getAccessToken), 保存下来(本地文件) (saveAccessToken), 直接使用
// get方式请求地址https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET
//引入request-promise-native库
const rp=require('request-promise-native');
//引入fs模块
const {writeFile,readFile} = require('fs');
//引入config
const {appID,appsecret} = require('../config/config');
//引入menu模块
const menu =require('./menu');
class Wechat{
constructor() {
}
//获取access_token
getAccessToken() {
const url = `https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${appID}&secret=${appsecret}`;
return new Promise((resolve,reject) =>{
//发送请求
rp({method: 'GET', url:url, json: true})
.then(res => { //成功时的回调
// console.log('获取access_token成功');
// access_token: '57_ZQgtMRumuV5zTyEujjT8K86Xn7eds3CSRmKFEaSHQL7r2xHNpcDWb5-760xWom3He4e_SaPWO00UxE0AAjdmlpDv77P6FrPybFXWGaef4ovG3jZTW1fb
// ZhulAWYG56Bou7Qoh7vLLkddAh3uUYPaABANRI',
// expires_in: 7200 过期时间
//设置access_token的过期时间 -300是提前五分钟 单位是秒所以乘以1000
res.expire_in = Date.now() + (res.expire_in - 300) * 1000;
//将Promise的状态改为成功的状态
resolve(res);
})
.catch(err => { //失败时的回调
console.log(err);
//将Promise的状态改为失败的状态
reject('getAccessToken请求出现了问题'+err);
})
})
}
//保存accesstoken
saveAcessToken(accessToken){
return new Promise((resolve,reject) => {
accessToken = JSON.stringify(accessToken) //将对象保存成JSON字符串
//将access保存成一个文件
writeFile("./accessToken",accessToken,err => {
if(!err){
console.log("文件保存成功");
resolve();
}else{
console.log("文件保存失败")
reject("getAccessToken方法出了问题"+err);
}
})
})
}
//读取accesstoken
readAcessToken(){
return new Promise((resolve,reject) => {
readFile("./accessToken",(err,data) => {
if(!err){
// console.log("文件读取成功");
//将JSON字符串转化为js对象
data = JSON.parse(data);
resolve(data);
}else{
console.log("文件读取失败")
reject("readAccessToken方法出了问题"+err);
}
})
})
}
//检查accessToken是不是有效的
isValidAccesstoken(data){
//检查传入的参数是否有效
if(!data && !data.accessToken && !data.expire_in){
//代表access_token无效
return false;
}
//检查access_token是否在有效期内
// if(data.expire_in < Date.now()){
// //过期
// return false;
// }else{
// return true;
// }
return data.expire_in > Date.now();
}
//获取没有过期的AccessToken
fetchAccessToken() {
if(this.accesss_token && this.expires_in && this.isValidAccesstoken(this)) {
//说明之前保存过access_token,并且他是有效的,直接使用
return Promise.resolve({
access_token:this.accesss_token,
expires_in:this.expires_in
})
} //是fetchAccessToken函数的返回值
return this.readAcessToken()
.then(async res => {
//本地有文件
// 判断它是否过期(isValidAccessToken)
if (this.isValidAccesstoken(res)) {
//有效的
// resolve(res);
return Promise.resolve(res);
} else {
//过期了
//-发送请求获取access_ token(getAccessToken)
const res = await this.getAccessToken()
// 保存下来(本地文件) (saveAccessToken), 直接使用
await this.saveAcessToken(res)
//将请求回来的access_token返回出去
// resolve(res);
return Promise.resolve(res);
}
})
.catch(async err => {
//本地没有文件
//-发送请求获取access_ token(getAccessToken)
const res = await this.getAccessToken()
// 保存下来(本地文件) (saveAccessToken), 直接使用
await this.saveAcessToken(res)
//将请求回来的access_token返回出去
// resolve(res);
return Promise.resolve(res);
})
.then(res => {
//将access_token挂载到this上
this.accesss_token = res.accesss_token;
this.expires_in=res.expire_in;
//返回res包装了一层promise对象(此对象为成功的状态)
return Promise.resolve(res);
})
}
//创建自定义菜单 menu菜单的配置对象
createMenu(menu){
//异步的东西为了更好的执行下去包装一层promise对象
return new Promise(async (resolve,reject) =>{
try{
//获取access_token
const data = await this.fetchAccessToken()
//定义请求的地址
const url = 'https://api.weixin.qq.com/cgi-bin/menu/create?access_token='+data.access_token+'';
//发送请求
const result = await rp({method:'POST',url,json:true,body:menu}); //如果把菜单放在body里面是以请求体的形式发送过去
resolve(result);
}catch (e){
reject('createMenu方法出了问题'+e);
}
})
}
//删除自定义菜单
deleteMenu(){
return new Promise(async (resolve,reject) => {
try{
//获取access_token
const data = await this.fetchAccessToken();
//定义一个请求地址
const url = 'https://api.weixin.qq.com/cgi-bin/menu/delete?access_token='+data+'';
//发送删除请求
const result = await rp({method:'GET',url,json:true})
resolve(result);
}catch (e){
reject('deleteMenu方法出了问题'+e);
}
})
}
}
//因为是异步函数 所以我们写成立即执行函数
(async ()=>{
//模拟测试
const test = new Wechat();
//删除之前创建的菜单
let result = await test.deleteMenu();
console.log(result);
//创建新的菜单
let result1 = await test.createMenu(menu);
console.log(result1);
})()
四,获取ticket和微信验证JS-SDK
4.1 下载ejs
4.2在app.js页面设置路由和配置模板引擎和资源目录
//配置模板资源目录
app.set('views','./views');
//配置模板引擎
app.set('view engine','ejs');
//网页路由
app.get('/search',(req,res) =>{
//渲染页面,将渲染好的页面给用户
res.render('search');
})
4.3在新建views文件夹下新建search.ejs文件
4.4把之前wechat的五个方法复制一份用来获取临时票据(jsapi_tiket)
4.5在utils文件夹下新建api.js(优化weatch模块代码)
//地址前缀
const prefix = 'https://api.weixin.qq.com/cgi-bin/';
module.exports={
accessToken:`${prefix}token?grant_type=client_credential`,
ticket:`${prefix}ticket/getticket?type=jsapi`,
menu:{
create:`${prefix}menu/create?`, //新建子菜单
delete:`${prefix}menu/delete?` //删除子菜单
}
}
4.6 将复制过来的五个方法修改完善并且调用测试
//一个实现所有的接口的功能模块
//完完全全可以脱离服务器去运行
// 整理思路:
// 读取本地文件( readAccessToken)
// -本地有文件
// 判断它是否过期(isValidAccessToken)
// -过期了
// -重新请求获取access_ token(getAccessToken), 保存下来覆盖之前的文件(保证文件是唯一 -的) (saveAccess Token)
// -没有过期
// -直接使用
// -本地没有文件
// -发送请求获取access_ token(getAccessToken), 保存下来(本地文件) (saveAccessToken), 直接使用
// get方式请求地址https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET
//引入request-promise-native库
const rp=require('request-promise-native');
//引入fs模块
const {writeFile,readFile} = require('fs');
//引入config
const {appID,appsecret} = require('../config/config');
//引入menu模块
const menu =require('./menu');
//引入api.js模块
const api = require('../utils/api');
class Wechat{
constructor() {
}
//获取access_token
getAccessToken() {
const url = `${api.accessToken}&appid=${appID}&secret=${appsecret}`;
return new Promise((resolve,reject) =>{
//发送请求
rp({method: 'GET', url:url, json: true})
.then(res => { //成功时的回调
// console.log('获取access_token成功');
// access_token: '57_ZQgtMRumuV5zTyEujjT8K86Xn7eds3CSRmKFEaSHQL7r2xHNpcDWb5-760xWom3He4e_SaPWO00UxE0AAjdmlpDv77P6FrPybFXWGaef4ovG3jZTW1fb
// ZhulAWYG56Bou7Qoh7vLLkddAh3uUYPaABANRI',
// expires_in: 7200 过期时间
//设置access_token的过期时间 -300是提前五分钟 单位是秒所以乘以1000
res.expire_in = Date.now() + (res.expire_in - 300) * 1000;
//将Promise的状态改为成功的状态
resolve(res);
})
.catch(err => { //失败时的回调
console.log(err);
//将Promise的状态改为失败的状态
reject('getAccessToken请求出现了问题'+err);
})
})
}
//保存accesstoken
saveAcessToken(accessToken){
return new Promise((resolve,reject) => {
accessToken = JSON.stringify(accessToken) //将对象保存成JSON字符串
//将access保存成一个文件
writeFile("./accessToken.txt",accessToken,err => {
if(!err){
// console.log("文件保存成功")
resolve();
}else{
console.log("文件保存失败")
reject("getAccessToken方法出了问题"+err);
}
})
})
}
//读取accesstoken
readAcessToken(){
return new Promise((resolve,reject) => {
readFile("./accessToken.txt",(err,data) => {
if(!err){
// console.log("文件读取成功");
//将JSON字符串转化为js对象
data = JSON.parse(data);
resolve(data);
}else{
console.log("文件读取失败")
reject("readAccessToken方法出了问题"+err);
}
})
})
}
//检查accessToken是不是有效的
isValidAccesstoken(data){
//检查传入的参数是否有效
if(!data && !data.accessToken && !data.expire_in){
//代表access_token无效
return false;
}
//检查access_token是否在有效期内
// if(data.expire_in < Date.now()){
// //过期
// return false;
// }else{
// return true;
// }
return data.expire_in > Date.now();
}
//获取没有过期的AccessToken
fetchAccessToken() {
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
})
} //是fetchAccessToken函数的返回值
return this.readAcessToken()
.then(async res => {
//本地有文件
// 判断它是否过期(isValidAccessToken)
if (this.isValidAccesstoken(res)) {
//有效的
// resolve(res);
return Promise.resolve(res);
} else {
//过期了
//-发送请求获取access_ token(getAccessToken)
const res = await this.getAccessToken()
// 保存下来(本地文件) (saveAccessToken), 直接使用
await this.saveAcessToken(res)
//将请求回来的access_token返回出去
// resolve(res);
return Promise.resolve(res);
}
})
.catch(async err => {
//本地没有文件
//-发送请求获取access_ token(getAccessToken)
const res = await this.getAccessToken()
// 保存下来(本地文件) (saveAccessToken), 直接使用
await this.saveAcessToken(res)
//将请求回来的access_token返回出去
// resolve(res);
return Promise.resolve(res);
})
.then(res => {
//将access_token挂载到this上
this.access_token = res.access_token;
this.expires_in=res.expires_in;
//返回res包装了一层promise对象(此对象为成功的状态)
return Promise.resolve(res);
})
}
/**获取临时的票据(jsapi_tiket)**/
//获取Ticket
getTicket() {
return new Promise(async (resolve,reject) =>{//发送请求
//获取access_token
const data = await this.fetchAccessToken();
const url = `${api.ticket}&access_token=${data.access_token}`;
rp({method: 'GET', url:url, json: true})
.then(res => { //成功时的回调
//将Promise的状态改为成功的状态
resolve({ticket:res.ticket,
expires_in: res.expires_in = Date.now() + (res.expires_in - 300) * 1000
});
})
.catch(err => { //失败时的回调
console.log(err);
//将Promise的状态改为失败的状态
reject('getTiket请求出现了问题'+err);
})
})
}
//保存jsapi_tiket
saveTicket(ticket){
return new Promise((resolve,reject) => {
ticket = JSON.stringify(ticket); //将对象保存成JSON字符串
//将access保存成一个文件
writeFile("./ticket.txt",ticket,err => {
if(!err){
// console.log("文件保存成功");
resolve();
}else{
console.log("文件保存失败")
reject("saveTiket方法出了问题"+err);
}
})
})
}
//读取jsapi_tiket
readTicket(){
return new Promise((resolve,reject) => {
readFile("./ticket.txt",(err,data) => {
if(!err){
// console.log("文件读取成功");
//将JSON字符串转化为js对象
data = JSON.parse(data);
resolve(data);
}else{
console.log("文件读取失败")
reject("readTiket方法出了问题"+err);
}
})
})
}
//检查jsapi_tiket是不是有效的
isValidTicket(data){
//检查传入的参数是否有效
if(!data && !data.ticket && !data.expire_in){
//代表Tiket无效
return false;
}
return data.expire_in > Date.now();
}
//获取没有过期的AccessToken
fetchTicket() {
if(this.ticket && this.ticket_expires_in && this.isValidTicket(this)) {
//说明之前保存过access_token,并且他是有效的,直接使用
return Promise.resolve({
ticket:this.ticket,
expires_in:this.expires_in
})
}
return this.readTicket()
.then(async res => {
//本地有文件 // 判断它是否过期(isValidTicket)
if (this.isValidTicket(res)) {
//有效的
// resolve(res);
return Promise.resolve(res);
} else {
//过期了
//-发送请求获取access_ token(getAccessToken)
const res = await this.getTicket()
// 保存下来(本地文件) (saveTicket), 直接使用
await this.saveTicket(res)
//将请求回来的access_token返回出去
// resolve(res);
return Promise.resolve(res);
}
})
.catch(async err => {
//本地没有文件
//-发送请求获取access_ token( getTicket)
const res = await this.getTicket()
// 保存下来(本地文件) (saveAccessToken), 直接使用
await this.saveTicket(res)
//将请求回来的access_token返回出去
// resolve(res);
return Promise.resolve(res);
})
.then(res => {
//将ticket挂载到this上
this.ticket = res.ticket;
this.ticket_expires_in=res.ticket_expires_in;
//返回res包装了一层promise对象(此对象为成功的状态)
return Promise.resolve(res);
})
}
//创建自定义菜单 menu菜单的配置对象
createMenu(menu){
//异步的东西为了更好的执行下去包装一层promise对象
return new Promise(async (resolve,reject) =>{
try{
//获取access_token
const data = await this.fetchAccessToken()
//定义请求的地址
const url = ''+api.menu.create+'access_token='+data.access_token+'';
//发送请求
const result = await rp({method:'POST',url,json:true,body:menu}); //如果把菜单放在body里面是以请求体的形式发送过去
resolve(result);
}catch (e){
reject('createMenu方法出了问题'+e);
}
})
}
//删除自定义菜单
deleteMenu(){
return new Promise(async (resolve,reject) => {
try{
//获取access_token
const data = await this.fetchAccessToken();
//定义一个请求地址
const url = ''+api.menu.delete+'access_token='+data+'';
//发送删除请求
const result = await rp({method:'GET',url,json:true})
resolve(result);
}catch (e){
reject('deleteMenu方法出了问题'+e);
}
})
}
}
//因为是异步函数 所以我们写成立即执行函数
(async ()=>{
//模拟测试
const test = new Wechat();
//删除之前创建的菜单
let result = await test.deleteMenu();
console.log(result);
//创建新的菜单
let result1 = await test.createMenu(menu);
console.log(result1);
const data = await test.fetchTicket();
console.log(data);
})()
4.6.1 成功运行 控制台的打印
4.7在tool中引入fs模块将wechat中的获取和读取功能写进来,减少wechat中的代码量,将保存的accessToken和ticket的文件运用path保存到utils文件夹下
//工具包函数
const {parseString} = require('xml2js'); // 将xml数据转化为js库 2原本是to
//引入fs模块
const {writeFile,readFile}=require('fs');
//引入path模块
const {resolve} = require('path'); //resolve这个负责帮我解析我想要的绝度路径
module.exports={
getUserDataAsync(req){
return new Promise((resolve,reject) => {
let xmlData = '';
req.on('data',data => {
//当流式数据传递过来时,会将数据注入回调函数中
// console.log(data);
//读取的数据是buffer类型 需要转换成字符串
xmlData += data.toString();
})
.on('end',() => {
//数据接受完毕触发
resolve(xmlData);
})
})
},
parseXMLAsync(xmlData){
return new Promise((resolve,reject) => {
parseString (xmlData,{trim:true},(err,data) => {
if(!err){
resolve(data);
}else{
reject("parseXMLAsync方法出了问题"+err);
}
})
})
},
formatMessage(jsData){
let message = {};
//获取xml对象
jsData = jsData.xml;
//判断数据是不是一个对象
if(typeof jsData === 'object'){
for (let key in jsData){
//获取属性值
let value = jsData[key];
//过滤空的数据
// if(Array.isArray(value) && value > 0){ 官方的过滤方法
if(Array.isArray(value) && value!=' '){ //自己改写的
//将合法的数据复制到message对象上
message[key] = value[0];
}
}
}
return message;
},
//优化wechat模块
writeFileAsync(data,fileName){
const filePath = resolve(__dirname,fileName); //防止和下面的resolve变量名冲突
return new Promise((resolve,reject) => {
data = JSON.stringify(data) //将对象保存成JSON字符串
//将access保存成一个文件
writeFile(filePath,data,err => {
if(!err){
// console.log("文件保存成功")
resolve();
}else{
console.log("文件保存失败")
reject("writeFileAsync方法出了问题"+err);
}
})
})
},
readFileAsync(fileName){
const filePath = resolve(__dirname,fileName); //防止和下面的resolve变量名冲突
return new Promise((resolve,reject) => {
readFile(filePath,(err,data) => {
if(!err){
// console.log("文件读取成功");
//将JSON字符串转化为js对象
data = JSON.parse(data);
resolve(data);
}else{
console.log("文件读取失败")
reject("readFileAsync方法出了问题"+err);
}
})
})
},
}
4.7.1 wechat需要头部引入
4.7.2 文件保存成功
4.8 配置网页路由准备工作
4.8.1将wechat模块声明出来
4.8.2在config模块写入url服务器地址
4.8.3 参照官方文档
4.8.4 JS安全域名接口写入(不写前缀)
4.8.5 完善app.js模块
//引入express模块
const express = require("express");
//引入sha1模块
const sha1 = require('sha1');
//引入auth模块
const auth=require('./wechat/auth');
//引入wechat模块
const Wechat = require('./wechat/wechat');
//引入config模块
const {url} = require('./config/config');
//创建app应用对象
const app = express();
//配置模板资源目录
app.set('views','./views');
//配置模板引擎
app.set('view engine','ejs');
//创建wechat实例对象
const wechatApi = new Wechat();
//网页路由
app.get('/search',async (req,res) =>{
/*生成js-sdk使用的签名 */
//获取随机字符串
const st = Math.random();
const noncestr = JSON.stringify(st).split('.')[1];
//获取时间戳
const timestamp = Date.now();
//获取票据
const {ticket} = await wechatApi.fetchTicket(); //返回值是一个promise对象
// 1.组合参与签名的四个参数,jsapi_ticket(临时票据),noncestr(随机字符串),timestamp(时间戳),url(当前的服务器地址)
const arr = [
`jsapi_ticket=${ticket}`,
`noncestr=${noncestr}`,
`timestamp=${timestamp}`,
`url=${url}/search`
]
// 2.组合将其字典序排序,以‘&’拼接在一起
const str = arr.sort().join('&');
// console.log(str);
// 3.进行sha1加密,最终生成signature
const signature = sha1(str);
//渲染页面,将渲染好的页面给用户
res.render('search',{
signature,
noncestr,
timestamp,
});
})
//接受处理所有消息
app.use(auth());
//监听端口号
app.listen(3000,() => console.log('服务器启动成功'));
4.9正确配置search.ejs模块
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>search</title>
</head>
<body>
<h1>这是一个搜索页面</h1>
<script type="text/javascript" src="http://res.wx.qq.com/open/js/jweixin-1.6.0.js"></script>
<script type="text/javascript">
/*
1.绑定域名
-在接口测试号页面上填写JS安全域名接口
2.引入JS文件
3.通过 config 接口注入权限验证配置
*/
wx.config({
debug: true, // 开启调试模式,调用的所有 api 的返回值会在客户端 alert 出来,若要查看传入的参数,可以在 pc 端打开,参数信息会通过 log 打出,仅在 pc 端时才会打印。
appId: 'wxb4fd0fa596cad699', // 必填,公众号的唯一标识
timestamp: '<%= timestamp %>', // 必填,生成签名的时间戳 加入单引号让ejs模板去解析
nonceStr: '<%= noncestr %>', // 必填,生成签名的随机串
signature: '<%= signature%>',// 必填,签名
jsApiList: [
'onMenuShareQQ', //分享到qq
'onMenuShareQZone', //分享到qq空间
'startRecord', //开始录音
'stopRecord', //结束录音
'translateVoice', //语音识别
] // 必填,需要使用的 JS 接口列表
});
wx.ready(function(){
// config信息验证后会执行 ready 方法,所有接口调用都必须在 config 接口获得结果之后,config是一个客户端的异步操作,所以如果需要在页面加载时就调用相关接口,则须把相关接口放在 ready 函数中调用来确保正确执行。对于用户触发时才调用的接口,则可以直接调用,不需要放在 ready 函数中。
});
wx.error(function(res){
// config信息验证失败会执行 error 函数,如签名过期导致验证失败,具体错误信息可以打开 config 的debug模式查看,也可以在返回的 res 参数中查看,对于 SPA 可以在这里更新签名。
});
</script>
</body>
</html>
五,JS-SDK之语音接口和分享接口(概述 | 微信开放文档 (qq.com))
5.1在search.js模块中read方法判断当前客户端版本是否支持指定JS接口(有些东西你一上来就需要使用微信SDK的接口的话,就得把接口放在ready回调函数中)
wx.checkJsApi({
jsApiList: [
'onMenuShareQQ', //分享到qq
'onMenuShareQZone', //分享到qq空间
'startRecord', //开始录音
'stopRecord', //结束录音
'translateVoice', //语音识别
], // 需要检测的JS接口列表,所有JS接口列表见附录2,
success: function(res) {
// 以键值对的形式返回,可用的api值true,不可用为false
// 如:{"checkResult":{"chooseImage":true},"errMsg":"checkJsApi:ok"}
console.log(res);
},
fail:function (res){
}
});
5.1.2正确可用的接口
5.2将微信开发文档中开始录音和结束录音的接口代码和语音识别功能的接口代码写入read回调函数( startRecord(开始录音)stopRecord(停止录音)translateVoice(语音转换为文字))
5.2.1具体代码实现
//设置标志位,检测当前录音状态
var isRecord = false;
//语音识别功能
$(function(){
$('#yuyin').click(function (){
if(!isRecord){
wx.startRecord(); //开始录音官方的接口
isRecord = true;
}else{
wx.stopRecord({ //结束录音
success: function (res) {
//结束录音后,会自动上传到微信服务器中,微信服务器会返回一个id给开发者
var localId = res.localId;
//将录音转化为汉字
wx.translateVoice({
localId: `${localId}`, // 需要识别的音频的本地Id,由录音相关接口获得
isShowProgressTips: 1, // 默认为1,显示进度提示
success: function (res) {
alert(res.translateResult); // 语音识别的结果
}
});
isRecord=false;
}
});
}
})
})
5.2.2功能效果成功展示
startRecord(开始录音)
stopRecord(停止录音)
translateVoice(语音转换为文字)(只是模拟测试并没有实际效果)
分享接口(写在read函数中即可)
//分享接口
wx.updateAppMessageShareData({
title: 'title', // 分享标题
desc: '描述', // 分享描述
link: 'https://2d92-42-245-192-21.jp.ngrok.io/search', // 分享链接,该链接域名或路径必须与当前页面对应的公众号 JS 安全域名一致
imgUrl: 'https://img-s.msn.cn/tenant/amp/entityid/AAYXpDT.img', // 分享图标
success: function () {
// 设置成功
alert("分享成功")
},cancel:function (){
var p = confirm("真的要取消嘛");
console.log(p);
}
})