前段時間剁手了 PS4,在瀏覽商店時,發現官方商店真的不好用,主要是網路原因,次要是頁面設計。所以就想自己做一個遊戲查詢的小程式,可以關注某個遊戲,然後在這個遊戲打折的時候傳送通知給使用者。最後發現有個很大的問題是:小程式沒法直接給使用者推送訊息(當時還不知道模板訊息),服務號才能。然後就用郵箱實現了通知功能,但是郵箱的侷限實在是太大了(各大免費郵箱每天的發件數都很小,自己搭建的郵件伺服器雖然沒有發件限制,但是大概率會被放到垃圾箱)。
然後某天在微信小程式的管理後臺發現了模板訊息這個東西,查了會資料發現可以通過這個來實現訊息推送。要給使用者傳送模板訊息需要formId/prepay_id
這樣一個東西,這個東西是怎麼來的呢?
- formId:這個可以通過表單的提交來獲取,需要在元件中設定屬性
report-submit="true"
,這樣每次對這個表單的提交一次就會產生一個 formId. - prepay_id:這個是支付動作產生的,具體的我也不太清楚,畢竟個人小程式並不能支付.
下面具體來講講前後臺的實現,前臺使用 mpvue,後臺使用 java
前臺獲取 formId
既然可以通過 form 的 submit 操作來獲取到 formId,那我們稍微擴充一下,將我們的小程式頁面中所有使用者能點選的部分都用 form,button 來包裹一下,這樣使用者感知不到有表單提交操作,我們也能獲取大量的 formId。
注意不能用疊加的方式來一次點選獲取多個 formId,這樣方法已經不行了,獲取到的都會是一樣的。
獲取一個模板
要傳送模板訊息,首先要在小程式的管理後臺上新增模板,步驟如下:
1.在模板庫中選擇一個模板
2.選擇顯示引數
選擇要顯示在訊息中的引數,這裡選擇如下兩個引數:
這樣就有了一個模板可以用來發訊息了,在我的模板中可以看到模板 id,和欄位 id
獲取 formId
formId 是通過表單提交來獲取到了,為了獲取足夠多的 formId,可以將能夠點選的元件(比如按鈕,列表單元..)包裹在 form 中,這樣使用者在日常使用中就能夠收集到足夠多的 formId。下面以包裹一個有讚的按鈕為例。因為小程式的限制,設定form-type
屬性的按鈕必須為 form 元件的直接子節點,所以並不能夠在 form 中使用自定義元件,並將form-type='submit'
設定到自定義元件中。
template 部分如下:
<!-- 要獲取formId,需要給form設定report-submit="true"的屬性,然後在form-type="submit"的按鈕上產生點選動作,才會觸發表單提交的事件--formSubmit,進而獲取到formId -->
<form @submit="formSubmit" report-submit="true" class="bottom">
<button style="border:0;display:inline-block" plain="true" form-type="submit" @click="back">
<van-button round type="primary" size="small" @click="back">返回</van-button>
</button>
<button style="border:0;display:inline-block" plain="true" form-type="submit" @click="watchGame">
<van-button round type="danger" size="small">{{watchText}}</van-button>
</button>
<button style="border:0;display:inline-block" plain="true" form-type="submit" open-type="share">
<van-button round type="info" size="small">分享</van-button>
</button>
</form>
style 目的為了去除原生 button 的背景,邊框啥的,把 button 當一個 div 來使用,然後在 button 中設定 form-type 和 click 屬性,這樣既不影響 formId 的屬性,也對原有邏輯不產生任何影響。之所以將 click 放在 button 上是因為 button 內部的元件沒法獲取到點選事件。
formSubmit 程式碼如下:
formSubmit(e) {
let item = {
value: e.mp.detail.formId,
expireTime: Date.now() + 7 * 24 * 60 * 60 * 1000
};
this.globalData.formIdList.push(item);
},
該函式是在表單提交時觸發,用於獲取 formId,將 formId 和這個 formId 的過期時間一起存到 globalData 全域性資料中。然後找一個時機將這些 formId 傳送給伺服器儲存起來就行了。
這裡放上我的做法以供參考。
我是在每次傳送 http 請求前檢查是不是有 formId 需要傳送到伺服器,如果有就將這些資料 JSON 序列化後放到一個自定義 header 中,傳送出去,具體程式碼如下(http 請求工具為:flyio):
var Fly = require("flyio/dist/npm/wx");
var fly = new Fly();
//在請求預處理中
fly.interceptors.request.use(request => {
request.headers["jwt-token"] = wxUtil.getGlobalData("jwt-token");
//如果有formId就放到header裡送過去
let formIdList = getApp().globalData.formIdList;
if (formIdList.length > 0) {
request.headers["formIdList"] = JSON.stringify(formIdList);
getApp().globalData.formIdList = [];
}
if (request.method == "GET") {
request.params["_t"] = new Date().getTime();
}
return request;
});
下面將後臺的實現,基於 Spring Boot.
後臺處理
蒐集 formId
首先需要把 formId 收集起來存到資料庫,那麼就需要檢查每個請求,看 header 中有沒有攜帶 formId,如果有就存到資料庫中,注意要和使用者對應起來,某個使用者點選產生的 formId 只能用於給這個使用者推送訊息。
因為要將 formId 和使用者繫結起來,因此我是在身份認證過濾器中進行的 formId 處理,身份認證成功後,處理 formId。程式碼如下:
/**
* Description: 從請求頭中獲取formIdList,並插入資料庫
*
* @param request 請求頭
* @return void
* @author fanxb
* @date 2019/5/6 16:39
*/
private void checkFormId(HttpServletRequest request) {
String str = request.getHeader(Constant.HEADER_FORM_ID);
if (StringUtil.isEmpty(str)) {
return;
}
List<FormKey> formKeyList = JSON.parseArray(str, FormKey.class);
//UserContextHolder使用者將當前執行緒和使用者繫結起來,方便後面獲取使用者資訊
int userId = UserContextHolder.get().getUser().getUserId();
formKeyList.forEach(item -> item.setUserId(userId));
this.formKeyDao.insertMany(formKeyList);
}
傳送微信提醒
通過官方文件可以知道傳送訊息的流程如下:
1 獲取 accessToken,呼叫微信的大多數介面都需要這個東西,這個通過 appId 和 secret 來獲取。詳情參見:https://developers.weixin.qq.com/miniprogram/dev/api-backend/auth.getAccessToken.html
2 呼叫微信傳送服務通知的介面.這個介面文件在:點選跳轉
最終傳送的 http 請求是這樣的:
url: https://api.weixin.qq.com/cgi-bin/message/wxopen/template/send?access_token=上面獲取的accessToken
method: post
請求體格式為:application/json; charset=utf-8
body:{
"touser": "使用者的openId",
"template_id": "模板id",
"page": "點選跳轉的小程式url路徑",
"form_id": "收集到的formId",
"data": {
"keyword1": {
"value": "您有一個資訊的提示訊息"
},
"keyword2": {
"value": "這是訊息內容"
}
},
"emphasis_keyword": "keyword1.DATA"
}
data 中的資料的按照順序 keyword1,keyword2 對應於模板中欄位的順序。
結束
工程全部原始碼在這裡:github
PS
這就是那個小程式,歡迎批評指正。