Java后台+微信小程序實現推送 “服務通知”


微信小程序+java后台實現,小程序推送“服務通知”給用戶
成功步驟:

1.注冊微信小程序app,然后登陸微信公眾平台開通“消息推送”,配置url、token等參數
2.平台——模板消息——我的模板 申請模板,獲取模板id、配置的字段名
3.在外網80/443的接口中放一個可以驗證簽名的接口(詳情見下文)。
4.Java后台寫發送消息的工具類
5.小程序寫一個可以獲取formId、openid的頁面
6.用真機調試中獲取到的參數在java工具類中main方法測試發送通知。成功!

0、登錄微信公眾平台,開啟小程序“消息推送”功能
https://mp.weixin.qq.com/cgi-bin/loginpage?t=wxm2-login&lang=zh_CN

開發——開發設置——消息推送

注意:URL填寫的是:要在外網能訪問的接口(需要80或者443端口才行),該接口需要包括能夠驗證簽名的方法,具體方法如下:

只需要把下面接口中的WECHAT_TOKEN :改成 消息推送的"Token(令牌)"值 一樣即可。


import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

/**
* 微信授權
*/
@Controller
@Slf4j
@RequestMapping("api/wx")
public class APIWxController{

// 與接口配置信息中的Token要一致
private static String WECHAT_TOKEN = "WECHAT_TOKEN";

@RequestMapping("/checkToken")
public void get(HttpServletRequest request, HttpServletResponse response) throws Exception {

log.info("========checkToken Controller========= ");

boolean isGet = request.getMethod().toLowerCase().equals("get");
PrintWriter print;
if (isGet) {
// 微信加密簽名
String signature = request.getParameter("signature");
// 時間戳
String timestamp = request.getParameter("timestamp");
// 隨機數
String nonce = request.getParameter("nonce");
// 隨機字符串
String echostr = request.getParameter("echostr");
// 通過檢驗signature對請求進行校驗,若校驗成功則原樣返回echostr,表示接入成功,否則接入失敗
if (signature != null && checkSignature(signature, timestamp, nonce)) {
try {
print = response.getWriter();
print.write(echostr);
print.flush();
log.info("========checkToken success ========= ");
} catch (IOException e) {
e.printStackTrace();
}
}else{
log.error("========checkToken failed========= ");
}
}else {
log.error("========checkToken failed:Only support Get Method =========");
}
}

/**
* 驗證簽名
*
* @param signature
* @param timestamp
* @param nonce
* @return
*/
public static boolean checkSignature(String signature, String timestamp, String nonce) {
String[] arr = new String[] { WECHAT_TOKEN, timestamp, nonce };
// 將token、timestamp、nonce三個參數進行字典序排序
// Arrays.sort(arr);
sort(arr);
StringBuilder content = new StringBuilder();
for (int i = 0; i < arr.length; i++) {
content.append(arr[i]);
}
MessageDigest md = null;
String tmpStr = null;

try {
md = MessageDigest.getInstance("SHA-1");
// 將三個參數字符串拼接成一個字符串進行sha1加密
byte[] digest = md.digest(content.toString().getBytes());
tmpStr = byteToStr(digest);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
content = null;
// 將sha1加密后的字符串可與signature對比,標識該請求來源於微信
return tmpStr != null ? tmpStr.equals(signature.toUpperCase()) : false;
}

/**
* 將字節數組轉換為十六進制字符串
*
* @param byteArray
* @return
*/
private static String byteToStr(byte[] byteArray) {
String strDigest = "";
for (int i = 0; i < byteArray.length; i++) {
strDigest += byteToHexStr(byteArray[i]);
}
return strDigest;
}

/**
* 將字節轉換為十六進制字符串
*
* @param mByte
* @return
*/
private static String byteToHexStr(byte mByte) {
char[] Digit = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
char[] tempArr = new char[2];
tempArr[0] = Digit[(mByte >>> 4) & 0X0F];
tempArr[1] = Digit[mByte & 0X0F];
String s = new String(tempArr);
return s;
}
public static void sort(String a[]) {
for (int i = 0; i < a.length - 1; i++) {
for (int j = i + 1; j < a.length; j++) {
if (a[j].compareTo(a[i]) < 0) {
String temp = a[i];
a[i] = a[j];
a[j] = temp;
}
}
}
}

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
在模板消息——我的模板中創建一個模板消息,獲取模板ID


1、java后台創建小程序 Vo類,用於封裝傳送的參數。
TemplateDataVo .java

import lombok.Data;

/*
* 設置推送的文字和顏色
* */
@Data
public class TemplateDataVo {
//字段值例如:keyword1:訂單類型,keyword2:下單金額,keyword3:配送地址,keyword4:取件地址,keyword5備注
private String value;//依次排下去
// private String color;//字段顏色(微信官方已廢棄,設置沒有效果)
}
1
2
3
4
5
6
7
8
9
10
11
WxMssVo .java

import lombok.Data;
import java.util.Map;

/*
* 小程序推送所需數據
* */
@Data
public class WxMssVo {
private String touser;//用戶openid
private String template_id;//模版id
private String page = "pages/index/index";//默認跳到小程序首頁
private String form_id;//收集到的用戶formid
// private String emphasis_keyword = "keyword1.DATA";//放大那個推送字段
private Map<String, TemplateDataVo> data;//推送文字
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2、java后台創建發送消息推送類
WeChatService.java
該類包括以下主要方法:
1、推送通知的主要方法類:
/**
*
* @param access_token app的token
* @param openid 用戶openid
* @param formId 表單ID
* @param templateId 模板ID
* @param keywords {與模板字段一一對應}
* @return
/
public String pushOneUser(String access_token,String openid, String formId,String templateId,String[] keywords)
2、獲取app的access_token 方法
/
* 獲取access_token
* appid和appsecret到小程序后台獲取,當然也可以讓小程序開發人員給你傳過來
* */
public String getAccess_token()


import com.alibaba.fastjson.JSONObject;
import com.weixin.demo.entity.TemplateDataVo;
import com.weixin.demo.entity.WxMssVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestTemplate;

import java.util.HashMap;
import java.util.Map;

@Slf4j
@Service
public class WeChatService {

@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}

@Autowired
private RestTemplate restTemplate;

 

/**
*
* @param access_token app的token
* @param openid 用戶openid
* @param formId 表單ID
* @param templateId 模板ID
* @param keywords {與模板字段一一對應}
* @return
*/
public String pushOneUser(String access_token,String openid, String formId,String templateId,String[] keywords) {

//如果access_token為空則從新獲取
if(StringUtils.isEmpty(access_token)){
access_token = getAccess_token();
}

String url = "https://api.weixin.qq.com/cgi-bin/message/wxopen/template/send" +
"?access_token=" + access_token;

//拼接推送的模版
WxMssVo wxMssVo = new WxMssVo();
wxMssVo.setTouser(openid);//用戶openid
wxMssVo.setForm_id(formId);//formId
wxMssVo.setTemplate_id(templateId);//模版id
Map<String, TemplateDataVo> m = new HashMap<>();

//封裝數據
if(keywords.length>0){
for(int i=1;i<=keywords.length;i++){
TemplateDataVo keyword = new TemplateDataVo();
keyword.setValue(keywords[i-1]);
m.put("keyword"+i, keyword);
}
wxMssVo.setData(m);
}else{
log.error("keywords長度為空");
return null;
}

if(restTemplate==null){
restTemplate = new RestTemplate();
}

ResponseEntity<String> responseEntity =
restTemplate.postForEntity(url, wxMssVo, String.class);
log.error("小程序推送結果={}", responseEntity.getBody());
return responseEntity.getBody();
}

/*
* 獲取access_token
* appid和appsecret到小程序后台獲取,當然也可以讓小程序開發人員給你傳過來
* */
public String getAccess_token() {
//獲取access_token
String appid = "wxb1abfc1724c5ee8b";
String appsecret = "c88a5dd3c3af8228a389d778fce3e32b";
String url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential" +
"&appid=" + appid + "&secret=" + appsecret;
if(restTemplate==null){
restTemplate = new RestTemplate();
}
String json = restTemplate.getForObject(url, String.class);
JSONObject myJson = JSONObject.parseObject(json);
return myJson.get("access_token").toString();
}

public static void main(String[] args) {
System.out.println(new WeChatService().getAccess_token());

WeChatService weChatUtil = new WeChatService();
String values[] ={"Jack方","2019-5-8 10:10:10","xxx有限公司","JAVA開發","xx區xx廣場xx號","請帶好入職材料"};
weChatUtil.pushOneUser(weChatUtil.getAccess_token()
,"o_fh25E0IufW7NIpezUReODfVH68","ec76b8b81cd04cf6b464bb0adf309d3b","zv0IsYDpJxgKWLHGUy8FEv0ajtJqkfhWTsFWiM7zzSU"
,values);
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
3、微信小程序(小程序申請什么的就自己去操作吧)
我們在本地的小程序開發工具構建一個這樣的頁面來測試下:
為什么是這樣的頁面?主要是為了方便獲取openid、formId


index.wxml
只有 元素中 加上report-submit=“true” 才能獲取得到formId,formId有效期為7天,一個formId只能發送一次通知,發完就不能再用了,formId是發送推送信息的必要條件。

<view class="container">


<form bind:submit='getOpenIdTap' report-submit="true">
<button formType='submit'>獲取用戶唯一標識openid</button>
<view class='widget'>
<text class='column'>openid:{{openid}}</text>
</view>
<view class='widget'>
<text class='column'>session_key:{{session_key}}</text>
</view>
<view class='widget'>
<text class='column'>formId:{{formId}}</text>
</view>

</form>

<form bind:submit="testSubmit" report-submit="true">
<button formType="submit">發送模板消息</button>
<view class='widget'>
<text class='column'>errcode:{{errcode}}</text>
</view>
<view class='widget'>
<text class='column'>errmsg:{{errmsg}}</text>
</view>
</form>

<view class='moto-container' bindtap='bindViewTap'>
<text class='moto'>獲取列表</text>
</view>
</view>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
index.js
將index.js中的下面幾個參數換成你的即可
const APP_ID = 'APP_ID ';//輸入小程序appid
const APP_SECRET = 'APP_SECRET ';//輸入小程序app_secret

//index.js
//獲取應用實例
const app = getApp()
const APP_ID = 'APP_ID ';//輸入小程序appid
const APP_SECRET = 'APP_SECRET ';//輸入小程序app_secret
var OPEN_ID = ''//儲存獲取到openid
var SESSION_KEY = ''//儲存獲取到session_k
var FORM_ID = ''//儲存獲取到的formId

Page({
data: {
motto: 'Hello World',
userInfo: {},
hasUserInfo: false,
canIUse: wx.canIUse('button.open-type.getUserInfo')
},
//事件處理函數
bindViewTap: function () {
wx.navigateTo({
url: '../list/list'
})
},
onLoad: function () {
if (app.globalData.userInfo) {
this.setData({
userInfo: app.globalData.userInfo,
hasUserInfo: true
})
} else if (this.data.canIUse) {
// 由於 getUserInfo 是網絡請求,可能會在 Page.onLoad 之后才返回
// 所以此處加入 callback 以防止這種情況
app.userInfoReadyCallback = res => {
this.setData({
userInfo: res.userInfo,
hasUserInfo: true
})
}
} else {
// 在沒有 open-type=getUserInfo 版本的兼容處理
wx.getUserInfo({
success: res => {
app.globalData.userInfo = res.userInfo
this.setData({
userInfo: res.userInfo,
hasUserInfo: true
})
}
})
}
},
getUserInfo: function (e) {
console.log(e)
app.globalData.userInfo = e.detail.userInfo
this.setData({
userInfo: e.detail.userInfo,
hasUserInfo: true
})
},
getOpenIdTap: function (e) {
var that = this;
FORM_ID = e.detail.formId;//獲取到formId
console.log("formId1:"+FORM_ID)
that.setData({
formId: FORM_ID
})
wx.login({
success: function (res) {
wx.request({
//獲取openid接口
url: 'https://api.weixin.qq.com/sns/jscode2session',
data: {
appid: APP_ID,
secret: APP_SECRET,
js_code: res.code,
grant_type: 'authorization_code'
},
method: 'GET',
success: function (res) {
OPEN_ID = res.data.openid;//獲取到的openid
SESSION_KEY = res.data.session_key;//獲取到session_key
console.log("openid:" + OPEN_ID)
console.log("session_key:" + SESSION_KEY)
that.setData({
openid: OPEN_ID,
session_key: SESSION_KEY
})
}
})
}
})
},
testSubmit: function (e) {
var that = this;
wx.request({
url: 'http://127.0.0.1:80/pushMsg',
method: 'POST',
data: {
access_token:null,
openid:OPEN_ID,
formid:FORM_ID
},
success: function (res) {
that.setData({
errcode: res.data.errcode,
errmsg: res.data.errmsg
})
console.log(res)
},
fail: function (err) {
console.log('request fail ', err);
},
complete: function (res) {
console.log("request completed!");
}

})
}
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
運行小程序,點擊獲取openid、formId
如果出現如下圖所示:formId is a mock one。
說明需要我們用真機測試才能獲取formId

點擊“真機調試”,用手機掃描二維碼

拿到我們需要的openId、formId


回到java后台WeChatService.java
用main方法測試一下。推送成功!

看下手機端展示:

 

為什么不直接在 小程序中直接發送“模板消息”?
答:主要是因為真機測試時,手機不能調用本地的接口,所以不好測試,我們用真機測試獲取formId,在放在java后台,模擬調用接口測試

————————————————
版權聲明:本文為CSDN博主「Jack方」的原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/fangchao2011/article/details/90074356


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM