学习微信公众号开发的第二天
一,創建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 = { //存放各種idtoUserName:message.FromUserName,fromUserName:message.ToUserName,createTime:Date.now(),msgType:'text',imageId:1, //圖片idvoiceId:1, //音頻idvideoId:1, //視頻idptmusicId:1, //普通音樂HQmusicId:1, //高清音樂music:1, //音樂idpicId: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_tokenconst 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_tokenconst 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_tokengetAccessToken() {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是提前五分鐘 單位是秒所以乘以1000res.expire_in = Date.now() + (res.expire_in - 300) * 1000;//將Promise的狀態改為成功的狀態resolve(res);}).catch(err => { //失敗時的回調console.log(err);//將Promise的狀態改為失敗的狀態reject('getAccessToken請求出現了問題'+err);})})}//保存accesstokensaveAcessToken(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);}})})}//讀取accesstokenreadAcessToken(){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();}//獲取沒有過期的AccessTokenfetchAccessToken() {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_tokenconst 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_tokenconst 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_tokengetAccessToken() {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是提前五分鐘 單位是秒所以乘以1000res.expire_in = Date.now() + (res.expire_in - 300) * 1000;//將Promise的狀態改為成功的狀態resolve(res);}).catch(err => { //失敗時的回調console.log(err);//將Promise的狀態改為失敗的狀態reject('getAccessToken請求出現了問題'+err);})})}//保存accesstokensaveAcessToken(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);}})})}//讀取accesstokenreadAcessToken(){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();}//獲取沒有過期的AccessTokenfetchAccessToken() {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)**///獲取TicketgetTicket() {return new Promise(async (resolve,reject) =>{//發送請求//獲取access_tokenconst 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_tiketsaveTicket(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_tiketreadTicket(){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();}//獲取沒有過期的AccessTokenfetchTicket() {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_tokenconst 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_tokenconst 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加密,最終生成signatureconst 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);}})總結
以上是生活随笔為你收集整理的学习微信公众号开发的第二天的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 不采用Matlab函数,自行设计基于双线
- 下一篇: Instagram 图谱 API