摘要:方案二通過微信公眾號平臺提供的接口定時獲取數據,然后插入到小程序數據庫中。和可在微信公眾平臺開發基本配置頁中獲得需要已經成為開發者,且帳號沒有異常狀態。
0、介紹
本文源碼:https://github.com/Jameswain/...
?
? ? 最近有一個需求:把5個公眾號的所有文章定時同步到小程序的數據庫里,10分鐘同步一次。實現這個需求當時我想了兩種方案
方案一:使用Puppeteer就所以的歷史文章爬下來,然后解析入庫。
方案二:通過微信公眾號平臺提供的接口定時獲取數據,然后插入到小程序數據庫中。這兩種方案中顯然是方案二最方便的,本文主要講解方案二實現過程。
? 技術棧:Node + MySQL + 微信公眾號接口
1、微信公眾平臺后臺配置? 首先需要登錄到你的微信公眾平臺,進行一些開發相關的配置。登錄微信公眾平臺后,在左側菜單中打開【開發】-【基本配置】
打開的頁面如下圖所示,下圖涉及到了一些敏感信息,所以我做了一些修改
? 在【基本配置】里,我們主要需要配置【開發者密碼(AppSecret)】和IP白名單,因為我們在調用微信公眾平臺的接口之前需要獲取access_token,在調用接口時access_token傳遞過去。
1.1 開發者密碼(AppSecret) 1.2 IP白名單配置? IP白名單:限制微信公眾平臺接口調用的IP;你要想調用微信開發者平臺的接口,你就必須把調用接口機器的公網IP配置到IP白名單里。
上圖我把47.50.55.11這臺機器配置到IP白名單里,這樣47.50.55.11這臺機器就可以調用微信公眾平臺的相關接口了。 到目前為止,公眾號開發的基本配置就配置好了。
2、獲取access_token? access_token是公眾號的全局唯一接口調用憑據,公眾號調用各接口時都需使用access_token。開發者需要進行妥善保存。access_token的存儲至少要保留512個字符空間。access_token的有效期目前為2個小時,需定時刷新,重復獲取將導致上次獲取的access_token失效。這是官網的詳細介紹:https://mp.weixin.qq.com/wiki...
? 公眾號可以使用AppID和AppSecret調用本接口來獲取access_token。AppID和AppSecret可在“微信公眾平臺-開發-基本配置”頁中獲得(需要已經成為開發者,且帳號沒有異常狀態)。調用接口時,請登錄“微信公眾平臺-開發-基本配置”提前將服務器IP地址添加到IP白名單中,點擊查看設置方法,否則將無法調用成功。
? 獲取access_token每日調用上限是2000次,具體情況可以在【開發】-【接口權限】中查看,在這里可以查看到所有接口
? 開始擼碼,新建一個文件夾,并通過npm初始化項目:
在MpWeixin.js文件中創建實現獲取access_token功能,具體流程如下圖所示:
MpWexin.js 實現代碼如下:
const path = require("path"); const fe = require("fs-extra"); const axios = require("axios"); class MpWeixin { /** * @param appID 開發者ID(AppID) * @param appSecret 開發者密碼(AppSecret) */ constructor(appID,appSecret) { this.appID = appID; this.appSecret = appSecret; } /** *讀取本地磁盤上access_token */ getAccessTokenForLocalDisk(){ let accessTokenFile = null; try { //讀取當前目錄下config/token文件中的token文件 accessTokenFile = fe.readJsonSync(path.resolve("config","token",`${this.appID}.json`)); } catch(e) { //如果文件不存在則創建一個空的access_token對象 accessTokenFile = { access_token : "", expires_time : 0 } } return accessTokenFile; } /** * 獲取access_token * access_token是公眾號的全局唯一接口調用憑據,access_token的有效期目前為2個小時,需定時刷新,重復獲取將導致上次獲取的access_token失效。 * 實現思路:每次獲取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的有效時間小于當前時間 表示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白名單中配置的機器上執行才能成功獲取access_token
3、獲取永久素材管理(公眾號文章)接口永久素材管理接口必須需要通過微信認證,微信認證必須要是要是企業訂閱號才可以進行微信認證,個人訂閱號無法進行微信認證,也就是說個人訂閱號是沒有調用這個接口權限的。接口官方說明: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 返回素材的數量,取值在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); });
在白名單中配置的機器上的運行結果如下:
4、創建存儲文件表獲取到的文章相關數據,需要將它存儲到MySQL數據庫中,所以首先需要創建一張文章表,創建SQL如下:
CREATE TABLE `article` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `title` varchar(255) NOT NULL COMMENT "標題", `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 "圖文消息的摘要,僅有單圖文消息才有摘要,多圖文此處為空。如果本字段為沒有填寫,則默認抓取正文前64個字。", `content` text COMMENT "圖文消息的具體內容,支持HTML標簽,必須少于2萬字符,小于1M,且此處會去除JS,涉及圖片url必須來源 "上傳圖文消息內的圖片獲取URL"接口獲取。外部圖片url將被過濾。", `url` varchar(255) DEFAULT NULL COMMENT "圖文頁的URL", `content_source_url` varchar(255) DEFAULT NULL COMMENT "圖文消息的原文地址,即點擊“閱讀原文”后的URL", `update_time` datetime DEFAULT NULL COMMENT "更新時間", `create_time` datetime DEFAULT NULL COMMENT "創建時間", PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8;
創建表操作也可以通過一些圖形化軟件進行操作,比如Navicat,操作界面如下:
5、實現文章存儲DAO層? DAO層主要是實現數據庫相關操作的,我使用的是MySQL數據庫,所以需要安裝一個【promise-mysql】模塊進行數據庫相關操作,并且對數據庫操作進行一個簡單的封裝,把數據庫的連接和斷開連接操作都封裝到一個db.js文件中,在需要進行數據庫操作的地方引入該模塊即可。
? db.js 數據連接 & 斷開操作封裝
const mysql = require("promise-mysql"); class DB { /** * 執行sql查詢數據庫 * @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", //數據庫地址 user: "root", //用戶名 password: "123456", //密碼 database: "mp", //數據庫 port:3306 //端口 } } //數據庫配置 DB.DB_CONFIG = config.test; module.exports = DB;
?接下來實現文章存儲到MySQL的DAO相關操作ArticleDao.js,具體實現代碼如下:
const db = require("./DB"); const sqlstring = require("sqlstring"); //sql占位符模塊 /** * 文章存儲dao操作 **/ class ArticleDao { /** * 保持文章到數據庫中 * @param article * @returns {Promise6、實現文章存儲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) { //數據庫中已存在 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 { //數據庫中不存在 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) } } /** * 根據 thumb_media_id 查詢數據庫中是否存在文章 * @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層實現獲取公眾號文章并存儲到數據庫中的相關業務代碼,ArticleService.js 具體實現代碼如下:
const moment = require("moment"); const ArticleDao = require("./ArticleDao"); const MpWeixin = require("./MpWeixin"); /** * 獲取公眾號文章,并保存到數據庫中 **/ class ArticleService { /** * @param appID 開發者ID(AppID) * @param appSecret 開發者密碼(AppSecret) */ constructor(appID, appSecret) { this.mpWeixin = new MpWeixin(appID, appSecret); this.articleDao = new ArticleDao(); } /** * 同步最新文章到數據庫中 */ 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); } } /** * 解析接口返回的文章列表,并同步到數據庫 * @param arrItems * @return {Array} */ async parseNewsItems(arrItems) { for (let i = 0; i < arrItems.length; i++) { const item = arrItems[i]; //創建日期 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]; //過濾未發布的文章,沒有封面表示沒有發布 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 }; //將文章同步到數據庫中 const result = await this.articleDao.saveArticle(article); console.log(result); } } } } module.exports = ArticleService;7、創建定時任務
? ??? ? 每10分鐘同步一次文章數據,因為獲取列表素材接口每天有調用次數的限制,所以我設置凌晨1點 ~ 早上7點不同步,因為這個點寫更新的公眾文章的可能性非常小。我在App.js實現定時同步文章操作,具體實現代碼如下:
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"); //執行間隔,單位:分鐘 const MINUTE = 10; setInterval(async () => { //設置凌晨1點 ~ 早上7點不同步 const hour = parseInt(moment().format("HH")); if(hour > 0 && hour < 8) return; //多個公眾號同時進行同步到一張表中 await Promise.all([ as1.syncLatestArticle(), as2.syncLatestArticle(), as3.syncLatestArticle(), as4.syncLatestArticle() ]); console.log(moment().format("YYYY-MM-DD HH:mm:ss"),"同步完畢"); },1000 * 60 * MINUTE);
? 寫到這里,整個定時同步微信公眾號文章到數據庫的操作就已經全部實現完成了,本人技術有限,歡迎各位大神交流指正。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/95835.html
摘要:一直都想搞一下微信公眾號網頁開發公司忙沒有時間自己也沒開發過所以也沒有頭緒前兩天通過自己的摸索以及自行查找的資料終于通過在本地成功的獲取到了微信的及簽名以及調用微信的接口因為筆者自己在做的時候費了挺長時間沒有找到一個相對完整詳細的一個項目借 一直都想搞一下微信公眾號網頁開發,公司忙沒有時間自己也沒開發過所以也沒有頭緒,前兩天通過自己的摸索以及自行查找的資料,終于通過nodejs在本地成...
前言 本篇文章主要是記錄本人在微信掃碼支付過程中所遇到的問題,給大家一個借鑒作用,希望對你們有幫助 開發環境 nodejs v8.1.0 egg v1.1.0 準備工作 微信公眾號-appid 微信商戶號-mch_id key值(簽名算法所需,其實就是一個32位的密碼,可以用md5生成一個)(key設置路徑:微信商戶平臺(pay.weixin.qq.com)-->賬戶設置-->API安全...
摘要:在函數中通過賦予變量,在函數中,指向定時器以及回調函數當不需要或者時,定時器沒有被,定時器的回調函數以及內部依賴的變量都不能被回收,造成內存泄漏。比如使用了定時器,需要在中做對應銷毀處理。 前言: 3月5日,從中山去往廣州,一大早7點多就做好準備了,在高鐵站了30分鐘,轉廣州地鐵又站了90分鐘,去到地鐵口,就有一輛cvte的大巴車過來接送,我選擇的面試時間是11:00-12:00,但前...
閱讀 1230·2021-11-11 16:54
閱讀 1744·2021-10-13 09:40
閱讀 940·2021-10-08 10:05
閱讀 3503·2021-09-22 15:50
閱讀 3706·2021-09-22 15:41
閱讀 1800·2021-09-22 15:08
閱讀 2345·2021-09-07 10:24
閱讀 3578·2019-08-30 12:52