小程序统一服务消息实现
目录
小程序统一服务消息实现
总体思路:运营后台处理某个任务时,推送相关消息到绑定了小程序的微信账户的服务通知里。需要用到用户的openid和用户在小程序中有交互动作产生的formId。
后台表结构设计:
小程序中的代码(taro-ui):
<View className="orderInfo">
<View
className="infoItem"
onClick={this.navigateTo.bind(this, '/pages/order/unHandleOrder')}
>
<AtForm className="tb-submitForm"
onSubmit={this.onSubmit.bind(this)}
reportSubmit
>
<AtButton formType='submit' className="num tb-submitBtn">{orderCount}</AtButton>
</AtForm>
<View className="infoTip">处理订单</View>
</View>
<View className="vLine" />
<View className="infoItem" onClick={this.navigateTo.bind(this, '/pages/me/msgCenter')}>
<AtForm className="tb-submitForm"
onSubmit={this.onSubmit.bind(this)}
reportSubmit
>
<AtButton formType='submit' className="num tb-submitBtn">{msgCount}</AtButton>
</AtForm>
<View className="infoTip">未读消息</View>
</View>
</View>
//表单提交事件,用于采集用户form_id
onSubmit (event) {
this.props.dispatch(action('app/updateUserFormId', event.detail.formId));
}
app.js:
*updateUserFormId({ payload }, { select, call, put }) {
try {
const wechatId = yield select(state => state.app.openid);
const formId = payload;
console.log('updateUserFormId print formId : ' + formId);
console.log('updateUserFormId print wechatId : ' + wechatId);
const data = {
urlParms: '/sys/api/v1/user/updateUserFormId',
bodys: { wechatId, formId },
};
const res = yield call(request, data);
if (res.code == 0) {
// nothing to do
} else {
Taro.showToast({
title: res.message,
icon: 'none',
duration: 2000,
});
}
} catch (e) {
console.log(e);
}
},
后台代码:
1)保存formId
/**
* 更新用户的formId
* @author xxx
* @date 2019-03-05 17:31:57
* @param formId
* @param wechatId
* @return
*/
@RequestMapping("/updateUserFormId")
@ResponseBody
@HttpSecurityAnnotation(typeValue = MethodType.USER_SERVICE,openLog=true)
public String updateUserFormId(@NotBlank(message="formId不能为空") @Bodys(value="formId")String formId,
@NotBlank(message="wechatId不能为空") @Bodys(value="wechatId")String wechatId){
try{
userService.updateUserFormId(wechatId,formId);
}catch(Exception e){
log.error("更新用户的formId失败!",e);
return JSONUtil.wrapResponse(JSONUtil.ResponseCode.FAILURE,e.getMessage());
}
return JSONUtil.wrapResponse(JSONUtil.ResponseCode.SUCCESS,"更新用户的formId成功");
}
1)发送模板消息:
if(addOperation){ // 新增对账单,发送消息到买方微信(拥有对账权限的买方账号)。
String open = env.getProperty("wechat.uniformMessage.open","off");
if("off".equals(open)){ //发送统一服务消息开关已关闭
return;
}
//小程序跳转页面
String page = env.getProperty("wechat.duizhangdan.page","pages/me/index");
//7天 = 7 * 24 = 168小时,这里去掉1小时。
//当前时间往前推167个小时,如果用户的的formId最后更新时间大于这个时间,则说明formId有效。
//formId目前有效期微信给出的是7天。
Date newDate = DateUtils.addHours(new Date(),-167);
List<PersonVO> personList = this.userService.getMbrCellphones(ECAuthConstant.BLANACE_ACC, blanace.getBuyerId());
if(!personList.isEmpty()){
JSONObject data = new JSONObject();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Map<String,Object> keyMap1 = new HashMap<String,Object>();
keyMap1.put("value",blanace.getBuyerName());
//添加客户名称
data.put("keyword1",keyMap1);
Map<String,Object> keyMap2 = new HashMap<String,Object>();
keyMap2.put("value",blanace.getSellerName());
//添加卖方名称
data.put("keyword2",keyMap2);
Map<String,Object> keyMap3 = new HashMap<String,Object>();
keyMap3.put("value",blanace.getMonth());
//添加对账月份
data.put("keyword3",keyMap3);
Map<String,Object> keyMap4 = new HashMap<String,Object>();
keyMap4.put("value",sdf.format(blanace.getCreateTime()));
//添加创建时间
data.put("keyword4",keyMap4);
for(PersonVO personVO : personList){
if(StringUtils.isEmpty(personVO.getWechatFormId())){
logger.warn("用户的微信formId为空,不发送消息!用户名[" + personVO.getPersonName() + "]");
continue;
}
if(personVO.getWechatFormIdModifyTime().before(newDate)){
//formId认为无效了,则不用发送了。
logger.warn("用户的微信formId时间已失效,不发送消息!用户名[" + personVO.getPersonName() + "]");
continue;
}
final UniformMsg uniformMsg = new UniformMsg();
uniformMsg.setTouser(personVO.getWechatId());
//uniformMsg.setTouser("oZaHr4iznKdHEjGremZZfvPS3X3k");
uniformMsg.setForm_id(personVO.getWechatFormId());
//uniformMsg.setForm_id("7821c2ceb9e848f1a95a2af8ce5f5a1c");
uniformMsg.setTemplate_id(env.getProperty("wechat.duizhangdan.template_id"));
uniformMsg.setPage(page); //小程序跳转页面
uniformMsg.setData(data);
this.taskExecutor.execute(new Runnable() {
public void run() {
wechatService.sendUniformMessage(uniformMsg);
}
});
}
}
}
IWechatService.java:
@Autowired
private Environment env;
@Autowired
private RedisTemplate redisTemplate;
private volatile Boolean init = false;
private String appid;
private String secret;
//获取微信accessToken接口地址
private String accessTokenIp;
//获取微信发送统一模板消息接口地址
private String uniformMessageIp;
private void init(){
init = true;
appid = env.getProperty("wechat.appid");
secret = env.getProperty("wechat.secret");
accessTokenIp = env.getProperty("wechat.accesstoken.ip");
uniformMessageIp = env.getProperty("wechat.uniformMessage.ip");
}
@Override
public String getAccessToken() throws Exception{
if(!init){
init();
}
NameValuePair[] nameValuePairs = new NameValuePair[3];
NameValuePair nameValuePair = new NameValuePair();
nameValuePair.setName("appid");
nameValuePair.setValue(this.appid);
NameValuePair nameValuePair2 = new NameValuePair();
nameValuePair2.setName("secret");
nameValuePair2.setValue(this.secret);
NameValuePair nameValuePair3 = new NameValuePair();
nameValuePair3.setName("grant_type");
nameValuePair3.setValue("client_credential");
nameValuePairs[0] = nameValuePair;
nameValuePairs[1] = nameValuePair2;
nameValuePairs[2] = nameValuePair3;
HttpClient client = new HttpClient();
GetMethod method = new GetMethod(this.accessTokenIp);
method.addRequestHeader("Content-Type", "application/x-www-form-urlencoded");
method.setQueryString(nameValuePairs);
client.executeMethod(method);
String retValue = method.getResponseBodyAsString();
method.releaseConnection();
JSONObject jsonObject = JSONObject.parseObject(retValue);
if(jsonObject.get("errcode") != null){
logger.warn("微信小程序接口,获取accessToken失败,原因:{}",jsonObject.get("errmsg"));
return null;
}
logger.info("打印accessToken jsonObject:{}",jsonObject.toJSONString());
//将微信accessToken放入redis中
redisTemplate.opsForValue().set("wechatAccessToken",jsonObject.get("access_token"));
return (String)jsonObject.get("access_token");
}
@Override
public void sendUniformMessage(UniformMsg uniformMsg) {
if(StringUtils.isEmpty(uniformMessageIp)){
init();
}
Double randomNum = Math.random();
String redisAccesssToken = (String) redisTemplate.opsForValue().get("wechatAccessToken");
if(StringUtils.isEmpty(redisAccesssToken)){
try {
this.getAccessToken();
} catch (Exception e) {
logger.error("发送统一模板消息,获取accessToken异常!",e);
throw new RuntimeException("发送统一模板消息,获取accessToken异常!");
}
}
redisAccesssToken = (String) redisTemplate.opsForValue().get("wechatAccessToken");
uniformMsg.getWeapp_template_msg().put("template_id",uniformMsg.getTemplate_id());
uniformMsg.getWeapp_template_msg().put("page",uniformMsg.getPage());
uniformMsg.getWeapp_template_msg().put("form_id",uniformMsg.getForm_id());
uniformMsg.getWeapp_template_msg().put("data",uniformMsg.getData());
uniformMsg.getWeapp_template_msg().put("emphasis_keyword","");
Map<String,Object> submitMap = Maps.newHashMap();
submitMap.put("touser",uniformMsg.getTouser());
submitMap.put("weapp_template_msg",uniformMsg.getWeapp_template_msg());
//请求url
String postUrl = this.uniformMessageIp + "?access_token=" + redisAccesssToken;
//执行请求发送
logger.info("[" + randomNum + "]submitMap:{}", JSON.toJSONString(submitMap));
String retValue = "";
try{
retValue = this.httpsRequest(postUrl,"POST",JSON.toJSONString(submitMap));
}catch(Exception e){
logger.error(e.getMessage(),e);
return;
}
logger.info("[" + randomNum + "]打印retValue:{}",retValue);
logger.info("[" + randomNum + "]打印accessToken:{}",redisAccesssToken);
//得到返回结果
JSONObject jsonObject = JSONObject.parseObject(retValue);
if(jsonObject.get("errcode") != null && (int)jsonObject.get("errcode") != 0){
logger.warn("[" + randomNum + "]微信小程序接口,发送统一模板消息失败,原因:{}",jsonObject.get("errmsg"));
if(DJECConstants.WechatUniformMsgErrcodeEnum.ACCESS_TOKEN_INVALID.getCode() == (int)jsonObject.get("errcode")){
logger.info("[" + randomNum + "]accessToken失效,重新获取token,再调一次。");
try {
redisAccesssToken = this.getAccessToken();
} catch (Exception e) {
logger.error(e.getMessage(),e);
return;
}
postUrl = this.uniformMessageIp + "?access_token=" + redisAccesssToken;
try{
//retValue = this.doPost(postUrl,JSON.toJSONString(submitMap));
retValue = this.httpsRequest(postUrl,"POST",JSON.toJSONString(submitMap));
}catch(Exception e){
logger.error(e.getMessage(),e);
return;
}
logger.info("[" + randomNum + "]打印retValue(第二次):{}",retValue);
//得到返回结果
jsonObject = JSONObject.parseObject(retValue);
if(jsonObject.get("errcode") != null && (int)jsonObject.get("errcode") !=0) {
logger.warn("[" + randomNum + "]微信小程序接口,发送统一模板消息(第二次)失败,原因:{}", jsonObject.get("errmsg"));
return;
}
logger.info("[" + randomNum + "]发送服务消息成功(第二次)。");
}
return;
}
logger.info("[" + randomNum + "]发送服务消息成功。");
}
//通用方法,执行请求连接。
public String httpsRequest(String requestUrl, String requestMethod, String outputStr){
try {
URL url = new URL(requestUrl);
HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
conn.setDoOutput(true);
conn.setDoInput(true);
conn.setUseCaches(false);
// 设置请求方式(GET/POST)
conn.setRequestMethod(requestMethod);
conn.setRequestProperty("content-type", "application/x-www-form-urlencoded");
// 当outputStr不为null时向输出流写数据
if (null != outputStr) {
OutputStream outputStream = conn.getOutputStream();
// 注意编码格式
outputStream.write(outputStr.getBytes("UTF-8"));
outputStream.close();
}
// 从输入流读取返回内容
InputStream inputStream = conn.getInputStream();
InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "utf-8");
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
String str = null;
StringBuffer buffer = new StringBuffer();
while ((str = bufferedReader.readLine()) != null) {
buffer.append(str);
}
// 释放资源
bufferedReader.close();
inputStreamReader.close();
inputStream.close();
conn.disconnect();
return buffer.toString();
} catch (ConnectException ce) {
logger.error("连接超时:{}",ce);
} catch (Exception e) {
logger.error("https请求异常:{}",e);
}
return null;
}
方法httpsRequest参考了:
UniformMsg.java:
import com.alibaba.fastjson.JSONObject;
/**
* 微信小程序统一发送模板消息对象
* @author XXXX
* @date 2019年3月1日 18:14:15
*/
public class UniformMsg {
/**
* 微信小程序模板ID
*/
private String template_id;
/**
* 微信用户绑定的小程序openid
*/
private String touser;
/**
* 跳转至小程序的页面路径
*/
private String page;
/**
* 表单提交场景下,为 submit 事件带上的 formId
*/
private String form_id;
/**
* 模板需要放大的关键词,不填则默认无放大
*/
private String emphasis_keyword;
private JSONObject weapp_template_msg = new JSONObject();
private JSONObject data = new JSONObject();
public String getTemplate_id() {
return template_id;
}
public void setTemplate_id(String template_id) {
this.template_id = template_id;
}
public String getTouser() {
return touser;
}
public void setTouser(String touser) {
this.touser = touser;
}
public String getPage() {
return page;
}
public void setPage(String page) {
this.page = page;
}
public String getForm_id() {
return form_id;
}
public void setForm_id(String form_id) {
this.form_id = form_id;
}
public String getEmphasis_keyword() {
return emphasis_keyword;
}
public void setEmphasis_keyword(String emphasis_keyword) {
this.emphasis_keyword = emphasis_keyword;
}
public JSONObject getData() {
return data;
}
public void setData(JSONObject data) {
this.data = data;
}
public JSONObject getWeapp_template_msg() {
return weapp_template_msg;
}
public void setWeapp_template_msg(JSONObject weapp_template_msg) {
this.weapp_template_msg = weapp_template_msg;
}
}
restful工具测试:
request body json结构:
{
"touser" : "oZaHr4iznKdHEjGremZZfvPS3X3k",
"weapp_template_msg" : {
"data":{
"keyword1":{
"value":"娄底建材工程有限公司"
},
"keyword2":{
"value":"2019-02"
}
},
"template_id":"mfFyTYNfYJhEOKxbW5hPSYOvlhcztM3qoclq6SHJ04s",
"form_id" : "70aff62c35a64ece84a281797cad095f",
"page" : "pages/order/statementAccount"
}
}
微信接收效果: