摘要:方案二通過微信公眾號平臺提供的接口定時獲取數(shù)據(jù),然后插入到小程序數(shù)據(jù)庫中。和可在微信公眾平臺開發(fā)基本配置頁中獲得需要已經(jīng)成為開發(fā)者,且?guī)ぬ枦]有異常狀態(tài)。
0、介紹
本文源碼:https://github.com/Jameswain/...
?
? ? 最近有一個需求:把5個公眾號的所有文章定時同步到小程序的數(shù)據(jù)庫里,10分鐘同步一次。實(shí)現(xiàn)這個需求當(dāng)時我想了兩種方案
方案一:使用Puppeteer就所以的歷史文章爬下來,然后解析入庫。
方案二:通過微信公眾號平臺提供的接口定時獲取數(shù)據(jù),然后插入到小程序數(shù)據(jù)庫中。這兩種方案中顯然是方案二最方便的,本文主要講解方案二實(shí)現(xiàn)過程。
? 技術(shù)棧:Node + MySQL + 微信公眾號接口
1、微信公眾平臺后臺配置? 首先需要登錄到你的微信公眾平臺,進(jìn)行一些開發(fā)相關(guān)的配置。登錄微信公眾平臺后,在左側(cè)菜單中打開【開發(fā)】-【基本配置】
打開的頁面如下圖所示,下圖涉及到了一些敏感信息,所以我做了一些修改
? 在【基本配置】里,我們主要需要配置【開發(fā)者密碼(AppSecret)】和IP白名單,因為我們在調(diào)用微信公眾平臺的接口之前需要獲取access_token,在調(diào)用接口時access_token傳遞過去。
1.1 開發(fā)者密碼(AppSecret) 1.2 IP白名單配置? IP白名單:限制微信公眾平臺接口調(diào)用的IP;你要想調(diào)用微信開發(fā)者平臺的接口,你就必須把調(diào)用接口機(jī)器的公網(wǎng)IP配置到IP白名單里。
上圖我把47.50.55.11這臺機(jī)器配置到IP白名單里,這樣47.50.55.11這臺機(jī)器就可以調(diào)用微信公眾平臺的相關(guān)接口了。 到目前為止,公眾號開發(fā)的基本配置就配置好了。
2、獲取access_token? access_token是公眾號的全局唯一接口調(diào)用憑據(jù),公眾號調(diào)用各接口時都需使用access_token。開發(fā)者需要進(jìn)行妥善保存。access_token的存儲至少要保留512個字符空間。access_token的有效期目前為2個小時,需定時刷新,重復(fù)獲取將導(dǎo)致上次獲取的access_token失效。這是官網(wǎng)的詳細(xì)介紹:https://mp.weixin.qq.com/wiki...
? 公眾號可以使用AppID和AppSecret調(diào)用本接口來獲取access_token。AppID和AppSecret可在“微信公眾平臺-開發(fā)-基本配置”頁中獲得(需要已經(jīng)成為開發(fā)者,且?guī)ぬ枦]有異常狀態(tài))。調(diào)用接口時,請登錄“微信公眾平臺-開發(fā)-基本配置”提前將服務(wù)器IP地址添加到IP白名單中,點(diǎn)擊查看設(shè)置方法,否則將無法調(diào)用成功。
? 獲取access_token每日調(diào)用上限是2000次,具體情況可以在【開發(fā)】-【接口權(quán)限】中查看,在這里可以查看到所有接口
? 開始擼碼,新建一個文件夾,并通過npm初始化項目:
在MpWeixin.js文件中創(chuàng)建實(shí)現(xiàn)獲取access_token功能,具體流程如下圖所示:
MpWexin.js 實(shí)現(xiàn)代碼如下:
const path = require("path"); const fe = require("fs-extra"); const axios = require("axios"); class MpWeixin { /** * @param appID 開發(fā)者ID(AppID) * @param appSecret 開發(fā)者密碼(AppSecret) */ constructor(appID,appSecret) { this.appID = appID; this.appSecret = appSecret; } /** *讀取本地磁盤上access_token */ getAccessTokenForLocalDisk(){ let accessTokenFile = null; try { //讀取當(dāng)前目錄下config/token文件中的token文件 accessTokenFile = fe.readJsonSync(path.resolve("config","token",`${this.appID}.json`)); } catch(e) { //如果文件不存在則創(chuàng)建一個空的access_token對象 accessTokenFile = { access_token : "", expires_time : 0 } } return accessTokenFile; } /** * 獲取access_token * access_token是公眾號的全局唯一接口調(diào)用憑據(jù),access_token的有效期目前為2個小時,需定時刷新,重復(fù)獲取將導(dǎo)致上次獲取的access_token失效。 * 實(shí)現(xiàn)思路:每次獲取access_token之前檢查本地文件是否存在access_token并且沒有過期,如果本地沒有access_token或者已過期則重新獲取access_token并保存到本地文件中 */ async getAccessToken() { const href = `https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${this.appID}&secret=${this.appSecret}`; //獲取本地存儲的access_token const accessTokenFile = this.getAccessTokenForLocalDisk(); const currentTime = Date.now(); //如果本地文件中的access_token為空 或者 access_token的有效時間小于當(dāng)前時間 表示access_token已過期 if(accessTokenFile.access_token === "" || accessTokenFile.expires_time < currentTime) { try { //訪問微信公眾平臺接口獲取acccess_token const {status,data} = await axios.get(href); console.log("getAccessToken status : ",status); console.log("getAccessToken data : ",data); if(data.access_token && data.expires_in) { //將access_token保存到本地文件中 accessTokenFile.access_token = data.access_token; accessTokenFile.expires_time = Date.now() + (parseInt(data.expires_in) - 180) * 1000; //access_token 有效期1小時57分鐘 //將access_token寫到本地文件中 const file = path.resolve("config","token",`${this.appID}.json`); fe.ensureFileSync(file); fe.outputJsonSync(file,accessTokenFile); return data.access_token; } else { throw new Error(JSON.stringify(data)); } } catch (e) { console.error("請求獲取access_token出錯:",e); } } //access_token 沒有過期,則直接返回本地存儲的token else { return accessTokenFile.access_token; } } } module.exports = MpWeixin;
然后新建一個App.js文件,在這個文件中測試一下獲取access_token這個方法,具體代碼如下:
const MpWeixin = require("./src/MpWeixin"); new MpWeixin("替換成你訂閱號的appID","替換成你訂閱號的appSecret").getAccessToken().then(access_token => { console.log("access_token:",access_token); });
注意:以上代碼必須要放在IP白名單中配置的機(jī)器上執(zhí)行才能成功獲取access_token
3、獲取永久素材管理(公眾號文章)接口永久素材管理接口必須需要通過微信認(rèn)證,微信認(rèn)證必須要是要是企業(yè)訂閱號才可以進(jìn)行微信認(rèn)證,個人訂閱號無法進(jìn)行微信認(rèn)證,也就是說個人訂閱號是沒有調(diào)用這個接口權(quán)限的。接口官方說明:https://mp.weixin.qq.com/wiki... ;
? 在MpWeixin.js 添加【獲取素材列表】代碼:
/** * https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1444738729 * 獲取素材列表 * @param type 素材的類型,圖片(image)、視頻(video)、語音 (voice)、圖文(news) * @param offset 從全部素材的該偏移位置開始返回,0表示從第一個素材 返回 * @param count 返回素材的數(shù)量,取值在1到20之間 * @returns {Promise} */ async getBatchGetMaterial(type,offset,count) { try { const access_token = await this.getAccessToken(); const href = `https://api.weixin.qq.com/cgi-bin/material/batchget_material?access_token=${access_token}`; const {status,data} = await axios.post(href,{type,offset,count}); console.log("status => ",status); return data; } catch(e) { console.error("獲取素材列表getBatchGetMaterial出錯:",e); } }
? 修改App.js文件,測試獲取素材列表接口:
const MpWeixin = require("./src/MpWeixin"); const mp_weixin = new MpWeixin("替換成你訂閱號的appID","替換成你訂閱號的appSecret").; //獲取圖文素材前20片文章 mp_weixin.getBatchGetMaterial("news",0,20).then(data => { console.log("獲取圖文素材:",data); }).catch(e => { console.error("獲取圖片素材出錯:",e); });
在白名單中配置的機(jī)器上的運(yùn)行結(jié)果如下:
4、創(chuàng)建存儲文件表獲取到的文章相關(guān)數(shù)據(jù),需要將它存儲到MySQL數(shù)據(jù)庫中,所以首先需要創(chuàng)建一張文章表,創(chuàng)建SQL如下:
CREATE TABLE `article` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `title` varchar(255) NOT NULL COMMENT "標(biāo)題", `thumb_url` varchar(255) NOT NULL COMMENT "文章封面", `thumb_media_id` varchar(255) DEFAULT NULL COMMENT "圖文消息的封面圖片素材id(必須是永久mediaID)", `show_cover_pic` tinyint(10) DEFAULT NULL COMMENT "是否顯示封面,0為false,即不顯示,1為true,即顯示", `author` varchar(100) DEFAULT NULL COMMENT "作者", `digest` varchar(255) DEFAULT NULL COMMENT "圖文消息的摘要,僅有單圖文消息才有摘要,多圖文此處為空。如果本字段為沒有填寫,則默認(rèn)抓取正文前64個字。", `content` text COMMENT "圖文消息的具體內(nèi)容,支持HTML標(biāo)簽,必須少于2萬字符,小于1M,且此處會去除JS,涉及圖片url必須來源 "上傳圖文消息內(nèi)的圖片獲取URL"接口獲取。外部圖片url將被過濾。", `url` varchar(255) DEFAULT NULL COMMENT "圖文頁的URL", `content_source_url` varchar(255) DEFAULT NULL COMMENT "圖文消息的原文地址,即點(diǎn)擊“閱讀原文”后的URL", `update_time` datetime DEFAULT NULL COMMENT "更新時間", `create_time` datetime DEFAULT NULL COMMENT "創(chuàng)建時間", PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8;
創(chuàng)建表操作也可以通過一些圖形化軟件進(jìn)行操作,比如Navicat,操作界面如下:
5、實(shí)現(xiàn)文章存儲DAO層? DAO層主要是實(shí)現(xiàn)數(shù)據(jù)庫相關(guān)操作的,我使用的是MySQL數(shù)據(jù)庫,所以需要安裝一個【promise-mysql】模塊進(jìn)行數(shù)據(jù)庫相關(guān)操作,并且對數(shù)據(jù)庫操作進(jìn)行一個簡單的封裝,把數(shù)據(jù)庫的連接和斷開連接操作都封裝到一個db.js文件中,在需要進(jìn)行數(shù)據(jù)庫操作的地方引入該模塊即可。
? db.js 數(shù)據(jù)連接 & 斷開操作封裝
const mysql = require("promise-mysql"); class DB { /** * 執(zhí)行sql查詢數(shù)據(jù)庫 * @param {String} sql * @param {String} isSql */ static async query(sql,isSql) { let connection,dataresult; try { connection = await mysql.createConnection(DB.DB_CONFIG); dataresult = await connection.query(sql); isSql && console.log(sql); connection.end(); return dataresult; } catch(e) { console.log(e); if (connection && connection.end) connection.end(); console.debug(e); throw e; } } static escape (value) { return mysql.escape(value); } } const config = { /** * 測試庫 */ test:{ host: "47.50.55.11", //數(shù)據(jù)庫地址 user: "root", //用戶名 password: "123456", //密碼 database: "mp", //數(shù)據(jù)庫 port:3306 //端口 } } //數(shù)據(jù)庫配置 DB.DB_CONFIG = config.test; module.exports = DB;
?接下來實(shí)現(xiàn)文章存儲到MySQL的DAO相關(guān)操作ArticleDao.js,具體實(shí)現(xiàn)代碼如下:
const db = require("./DB"); const sqlstring = require("sqlstring"); //sql占位符模塊 /** * 文章存儲dao操作 **/ class ArticleDao { /** * 保持文章到數(shù)據(jù)庫中 * @param article * @returns {Promise6、實(shí)現(xiàn)文章存儲Service層} */ async saveArticle(article) { try { const {thumb_media_id} = article; const isExits = await this.isExitsArticle(thumb_media_id); let keys = Object.keys(article); let vals = Object.values(article); if(isExits) { //數(shù)據(jù)庫中已存在 UPDATE const index = keys.indexOf("thumb_media_id"); keys.splice(index,1); vals.splice(index,1); const sign = keys.map(key => `${key}=?`).join(","); vals.push(thumb_media_id); const sql = sqlstring.format(`UPDATE article SET ${sign} WHERE thumb_media_id = ?`,vals); console.log("sql => ",sql) const result = await db.query(sql,true); return result; } else { //數(shù)據(jù)庫中不存在 INSERT const sign = new Array(keys.length).fill("?").join(","); const sql = sqlstring.format(`INSERT INTO article(${keys.join(",")}) VALUES(${sign})`,vals); console.log("sql => ",sql); const result = await db.query(sql,true); return result; } } catch (e) { console.error("saveArticle出錯:",e) } } /** * 根據(jù) thumb_media_id 查詢數(shù)據(jù)庫中是否存在文章 * @param thumb_media_id * @returns {Promise } */ async isExitsArticle(thumb_media_id) { try { const sql = sqlstring.format(`SELECT * FROM article WHERE thumb_media_id = ?`,[thumb_media_id]); const result = await db.query(sql,true); return result && result.length > 0; } catch (e) { throw e; } } } module.exports = ArticleDao;
在Service層實(shí)現(xiàn)獲取公眾號文章并存儲到數(shù)據(jù)庫中的相關(guān)業(yè)務(wù)代碼,ArticleService.js 具體實(shí)現(xiàn)代碼如下:
const moment = require("moment"); const ArticleDao = require("./ArticleDao"); const MpWeixin = require("./MpWeixin"); /** * 獲取公眾號文章,并保存到數(shù)據(jù)庫中 **/ class ArticleService { /** * @param appID 開發(fā)者ID(AppID) * @param appSecret 開發(fā)者密碼(AppSecret) */ constructor(appID, appSecret) { this.mpWeixin = new MpWeixin(appID, appSecret); this.articleDao = new ArticleDao(); } /** * 同步最新文章到數(shù)據(jù)庫中 */ async syncLatestArticle() { try { //獲取最新的前20篇圖文文章 const result = await this.mpWeixin.getBatchGetMaterial("news", 0, 20); console.log("result =>",result); const {item} = result; console.log("item =>",item); await this.parseNewsItems(item); } catch (e) { console.error("syncLatestArticle error => ", e); } } /** * 解析接口返回的文章列表,并同步到數(shù)據(jù)庫 * @param arrItems * @return {Array} */ async parseNewsItems(arrItems) { for (let i = 0; i < arrItems.length; i++) { const item = arrItems[i]; //創(chuàng)建日期 let create_time = parseInt(item.content.create_time) * 1000; //更新日期 let update_time = parseInt(item.content.update_time) * 1000; for (let j = 0; j < item.content.news_item.length; j++) { //獲取圖文消息 const {title,thumb_media_id,show_cover_pic,author,digest,content,url,content_source_url,thumb_url} = item.content.news_item[j]; //過濾未發(fā)布的文章,沒有封面表示沒有發(fā)布 if (thumb_url === "" || thumb_url === null || thumb_url.trim().length === 0) continue; //格式化時間 create_time = moment(create_time).format("YYYY-MM-DD HH:mm:ss"); update_time = moment(update_time).format("YYYY-MM-DD HH:mm:ss"); const article = { title, thumb_media_id, show_cover_pic, author, digest, content, url, content_source_url, thumb_url, create_time, update_time }; //將文章同步到數(shù)據(jù)庫中 const result = await this.articleDao.saveArticle(article); console.log(result); } } } } module.exports = ArticleService;7、創(chuàng)建定時任務(wù)
? ??? ? 每10分鐘同步一次文章數(shù)據(jù),因為獲取列表素材接口每天有調(diào)用次數(shù)的限制,所以我設(shè)置凌晨1點(diǎn) ~ 早上7點(diǎn)不同步,因為這個點(diǎn)寫更新的公眾文章的可能性非常小。我在App.js實(shí)現(xiàn)定時同步文章操作,具體實(shí)現(xiàn)代碼如下:
const moment = require("moment"); const ArticleService = require("./src/ArticleService"); //公眾號1 const as1 = new ArticleService("替換成你訂閱號的appID","替換成你訂閱號的appSecret"); //公眾號2 const as2 = new ArticleService("替換成你訂閱號的appID","替換成你訂閱號的appSecret"); //公眾號3 const as3 = new ArticleService("替換成你訂閱號的appID","替換成你訂閱號的appSecret"); //公眾號4 const as4 = new ArticleService("替換成你訂閱號的appID","替換成你訂閱號的appSecret"); //執(zhí)行間隔,單位:分鐘 const MINUTE = 10; setInterval(async () => { //設(shè)置凌晨1點(diǎn) ~ 早上7點(diǎn)不同步 const hour = parseInt(moment().format("HH")); if(hour > 0 && hour < 8) return; //多個公眾號同時進(jìn)行同步到一張表中 await Promise.all([ as1.syncLatestArticle(), as2.syncLatestArticle(), as3.syncLatestArticle(), as4.syncLatestArticle() ]); console.log(moment().format("YYYY-MM-DD HH:mm:ss"),"同步完畢"); },1000 * 60 * MINUTE);
? 寫到這里,整個定時同步微信公眾號文章到數(shù)據(jù)庫的操作就已經(jīng)全部實(shí)現(xiàn)完成了,本人技術(shù)有限,歡迎各位大神交流指正。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/95835.html
摘要:一直都想搞一下微信公眾號網(wǎng)頁開發(fā)公司忙沒有時間自己也沒開發(fā)過所以也沒有頭緒前兩天通過自己的摸索以及自行查找的資料終于通過在本地成功的獲取到了微信的及簽名以及調(diào)用微信的接口因為筆者自己在做的時候費(fèi)了挺長時間沒有找到一個相對完整詳細(xì)的一個項目借 一直都想搞一下微信公眾號網(wǎng)頁開發(fā),公司忙沒有時間自己也沒開發(fā)過所以也沒有頭緒,前兩天通過自己的摸索以及自行查找的資料,終于通過nodejs在本地成...
前言 本篇文章主要是記錄本人在微信掃碼支付過程中所遇到的問題,給大家一個借鑒作用,希望對你們有幫助 開發(fā)環(huán)境 nodejs v8.1.0 egg v1.1.0 準(zhǔn)備工作 微信公眾號-appid 微信商戶號-mch_id key值(簽名算法所需,其實(shí)就是一個32位的密碼,可以用md5生成一個)(key設(shè)置路徑:微信商戶平臺(pay.weixin.qq.com)-->賬戶設(shè)置-->API安全...
摘要:在函數(shù)中通過賦予變量,在函數(shù)中,指向定時器以及回調(diào)函數(shù)當(dāng)不需要或者時,定時器沒有被,定時器的回調(diào)函數(shù)以及內(nèi)部依賴的變量都不能被回收,造成內(nèi)存泄漏。比如使用了定時器,需要在中做對應(yīng)銷毀處理。 前言: 3月5日,從中山去往廣州,一大早7點(diǎn)多就做好準(zhǔn)備了,在高鐵站了30分鐘,轉(zhuǎn)廣州地鐵又站了90分鐘,去到地鐵口,就有一輛cvte的大巴車過來接送,我選擇的面試時間是11:00-12:00,但前...
閱讀 1236·2021-11-11 16:54
閱讀 1749·2021-10-13 09:40
閱讀 946·2021-10-08 10:05
閱讀 3511·2021-09-22 15:50
閱讀 3714·2021-09-22 15:41
閱讀 1812·2021-09-22 15:08
閱讀 2352·2021-09-07 10:24
閱讀 3582·2019-08-30 12:52