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

posted on 2020-02-10 12:29  Hi,王松柏  阅读(8955)  评论(0编辑  收藏  举报

导航