这里工具类使用了静态内部类,且禁止外部使用new创建对象。
ps:URL这里用final static修饰没问题,但切记不要把微信所需的令牌参数(access_token)也拼接上(之前项目的微信消息推送每次重启项目后一段时间就失效),问题所在就不过多细说了;
package com.icex.frame.utils.wechat;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.icex.bus.sys.entity.WxTempMsgModel;
import com.icex.common.Const;
import com.icex.frame.utils.DateUtil;
import com.icex.frame.utils.HttpClientUtil;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 微信推送模板消息方法
* 微信工具类
* @author Jjcc
* @createTime 2019年07月24日 21:03:00
*/
public class WeChatToolUtil {
private static final String URL = "https://api.weixin.qq.com/cgi-bin/message/wxopen/template/send?access_token=";
private static Logger logger = LoggerFactory.getLogger(WeChatToolUtil.class.getName());
/**
* 构造方法私有化
* @title WeChatToolUtil
* @description
* @author Jjcc
* @return
* @createTime
* @throws
*/
private WeChatToolUtil() {
if (null != WeChatUtilInnerClass.WE_CHAT_TOOL) {
logger.error("实例对象已创建!");
throw new RuntimeException("实例对象已创建!");
}
}
private static class WeChatUtilInnerClass {
private final static WeChatToolUtil WE_CHAT_TOOL = new WeChatToolUtil();
}
public static WeChatToolUtil getInstance() {
return WeChatUtilInnerClass.WE_CHAT_TOOL;
}
/**
* @title pushMessage
* @description
* @author Jjcc
* @param openId 微信用户openId
* @param templateId 模板消息Id
* @param formId form_id 表单提交场景下,为 submit 事件带上的 formId
* @param page 点击模板消息后进入的页面
* @return String
* @createTime 2019/7/24 21:57
* @throws
*/
public String pushMessage(String openId, String templateId, String formId, String page, JSONObject jsonObject) {
System.out.println("推送微信消息-----token---------> " + Const.WX_ACCESS_TOKEN);
WxTempMsgModel model = new WxTempMsgModel();
model.setAccess_token(Const.WX_ACCESS_TOKEN);
model.setTouser(openId);
model.setTemplate_id(templateId);
model.setForm_id(formId);
model.setPage(page);
model.setData(jsonObject);
String jsonStr = JSON.toJSONString(model);
String ret;
try {
ret = HttpClientUtil.doPost2(URL + Const.WX_ACCESS_TOKEN, jsonStr);
System.out.println("微信推送模板消息结果ret----------> " + ret);
JSONObject retObj = JSONObject.parseObject(ret);
/*
* 返回状态码
* 0:正常;
* 40037:template_id不正确
* 41028:form_id不正确,或者过期
* 41029:form_id已被使用
* 41030:page不正确
*/
String errCode = retObj.getString("errcode");
//返回状态消息
String errMsg = retObj.getString("errmsg");
logger.info("weChatReturnJson:" + retObj.toJSONString() + ";msg:" + errMsg);
if (StringUtils.isNotBlank(errCode) && "0".equals(errCode)) {
// 推送成功
logger.info("推送成功!");
return errCode;
} else {
logger.error("微信模板消息推送失败!第二次尝试推送");
//40001 获取access_token时AppSecret错误,或者access_token无效。请开发者认真比对AppSecret的正确性,或查看是否正在为恰当的公众号调用接口
//42001 access_token超时,请检查access_token的有效期,请参考基础支持-获取access_token中,对access_token的详细机制说明
if ("40001".equals(errCode) || "42001".equals(errCode)) {
//getAccessTokenAndPush(model);
}
}
} catch (Exception e) {
logger.error("微信模板消息推送异常!");
e.printStackTrace();
}
return "-1";
}
}
本人这里并没有直接使用JSONObject组装json
package com.icex.bus.sys.entity;
import java.util.Date;
/**
* 微信模板消息Bean
* @author Jjcc
* @version 1.0.0
* @Description
* @ClassName WeChatMessTempPara.java
* @createTime 2019年07月25日 12:52:00
*/
public class WeChatMessTempPara {
/**
* 受理时间
*/
private Date acceptTime;
/**
* 受理人
*/
private String dealAccountName;
/**
* 联系电话
*/
private String dealAccountPhone;
/**
* 工单内容
*/
private String flowContent;
/**
* 工单状态
*/
private String serverStatusName;
/**
* 进度说明
*/
private String progressExplain;
/**
* 处理状态
*/
private String dealStatus;
/**
* 当前进度
*/
private String presentProgress;
/**
* 工作内容
*/
private String dealContent;
public Date getAcceptTime() {
return acceptTime;
}
public void setAcceptTime(Date acceptTime) {
this.acceptTime = acceptTime;
}
public String getDealAccountName() {
return dealAccountName;
}
public void setDealAccountName(String dealAccountName) {
this.dealAccountName = dealAccountName;
}
public String getDealAccountPhone() {
return dealAccountPhone;
}
public void setDealAccountPhone(String dealAccountPhone) {
this.dealAccountPhone = dealAccountPhone;
}
public String getFlowContent() {
return flowContent;
}
public void setFlowContent(String flowContent) {
this.flowContent = flowContent;
}
public String getServerStatusName() {
return serverStatusName;
}
public void setServerStatusName(String serverStatusName) {
this.serverStatusName = serverStatusName;
}
public String getProgressExplain() {
return progressExplain;
}
public void setProgressExplain(String progressExplain) {
this.progressExplain = progressExplain;
}
public String getDealStatus() {
return dealStatus;
}
public void setDealStatus(String dealStatus) {
this.dealStatus = dealStatus;
}
public String getPresentProgress() {
return presentProgress;
}
public void setPresentProgress(String presentProgress) {
this.presentProgress = presentProgress;
}
public String getDealContent() {
return dealContent;
}
public void setDealContent(String dealContent) {
this.dealContent = dealContent;
}
@Override
public String toString() {
return "WeChatMessTempPara{" +
"acceptTime=" + acceptTime +
", dealAccountName='" + dealAccountName + '\'' +
", dealAccountPhone='" + dealAccountPhone + '\'' +
", flowContent='" + flowContent + '\'' +
", serverStatusName='" + serverStatusName + '\'' +
", progressExplain='" + progressExplain + '\'' +
", dealStatus='" + dealStatus + '\'' +
", presentProgress='" + presentProgress + '\'' +
", dealContent='" + dealContent + '\'' +
'}';
}
}
package com.icex.frame.utils.wechat.factory;
import com.alibaba.fastjson.JSONObject;
import com.icex.bus.sys.entity.SysFlow;
import com.icex.bus.sys.entity.WeChatMessTempPara;
/**
* 模板接口
* @author Jjcc
* @version 1.0.0
* @Description
* @ClassName ITempParam.java
* @createTime 2019年07月24日 23:57:00
*/
public interface ITempParam {
/**
* 组合消息模板方法
* @title createTempParam
* @description
* @author Jjcc
* @param tempPara
* @return com.alibaba.fastjson.JSONObject
* @createTime 2019/9/18 15:45
* @throws
*/
JSONObject createTempParam(WeChatMessTempPara tempPara);
}
具体的模板类实现了克隆;
这里使用了设计模式的思想;面向接口开发,而不是具体的实现类。对扩展开发,对修改关闭;
package com.icex.frame.utils.wechat.factory.impl;
import com.alibaba.fastjson.JSONObject;
import com.icex.bus.sys.entity.TempParamModel;
import com.icex.bus.sys.entity.WeChatMessTempPara;
import com.icex.frame.utils.wechat.factory.ITempParam;
/**
* 工单处理中消息模板参数
* @author Jjcc
* @version 1.0.0
* @Description
* @ClassName BeingDisposeTempParamImpl.java
* @createTime 2019年07月25日 00:05:00
*/
public class BeingDisposeTempParamImpl implements ITempParam,Cloneable {
@Override
public JSONObject createTempParam(WeChatMessTempPara tempPara) {
JSONObject json = new JSONObject();
json.put("keyword1", new TempParamModel(tempPara.getProgressExplain()));
json.put("keyword2", new TempParamModel(tempPara.getDealStatus()));
json.put("keyword3", new TempParamModel(tempPara.getPresentProgress()));
json.put("keyword4", new TempParamModel(tempPara.getDealContent()));
return json;
}
@Override
public BeingDisposeTempParamImpl clone() {
BeingDisposeTempParamImpl tempParam = null;
try {
tempParam = (BeingDisposeTempParamImpl) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return tempParam;
}
}
package com.icex.frame.utils.wechat.factory.impl;
import com.alibaba.fastjson.JSONObject;
import com.icex.bus.sys.entity.TempParamModel;
import com.icex.bus.sys.entity.WeChatMessTempPara;
import com.icex.frame.utils.wechat.factory.ITempParam;
/**
* 工单处理完成-消息模板参数
* @author Jjcc
* @version 1.0.0
* @Description
* @ClassName DisposeFinishTempParaImpl.java
* @createTime 2019年07月25日 00:09:00
*/
public class DisposeFinishTempParaImpl implements ITempParam,Cloneable {
@Override
public JSONObject createTempParam(WeChatMessTempPara tempPara) {
JSONObject json = new JSONObject();
json.put("keyword1", new TempParamModel(tempPara.getDealStatus()));
System.out.println("tempPara.getServerStatusName(): " + tempPara.getServerStatusName());
json.put("keyword2", new TempParamModel(tempPara.getDealAccountPhone()));
json.put("keyword3", new TempParamModel(tempPara.getDealAccountName()));
return json;
}
@Override
public DisposeFinishTempParaImpl clone() {
DisposeFinishTempParaImpl tempParam = null;
try {
tempParam = (DisposeFinishTempParaImpl) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return tempParam;
}
}
该工厂使用的是静态工厂模式,通过枚举类型来决定创建具体的消息模板对象,同时也使用了克隆模式来优化频繁的创建对象;
package com.icex.frame.utils.wechat.factory;
import com.icex.frame.utils.wechat.factory.impl.BeingDisposeTempParamImpl;
import com.icex.frame.utils.wechat.factory.impl.DisposeFinishTempParaImpl;
import com.icex.frame.utils.wechat.factory.impl.OrderReceivingTempParamImpl;
import javax.validation.constraints.NotNull;
/**
* 微信模板消息工厂
* @author Jjcc
* @version 1.0.0
* @Description
* @ClassName WeChatTempMsgFactory.java
* @createTime 2019年07月26日 10:50:00
*/
public class WeChatTempMsgFactory {
private static OrderReceivingTempParamImpl orderReceivingTempParam;
private static BeingDisposeTempParamImpl beingDisposeTempParam;
private static DisposeFinishTempParaImpl disposeFinishTempPara;
/**
* 静态工厂方法,通过参数返回指定的类实例对象
* @title getTempMsgIns
* @description
* @author Jjcc
* @param type
* @return com.icex.frame.utils.wechat.factory.ITempParam
* @createTime 2019/7/26 14:36
* @throws
*/
public static ITempParam getTempMsgIns(@NotNull WeChatTempMsgType type) {
ITempParam tempParam = null;
switch (type) {
case ORDER_RECEIVING:
if (null == orderReceivingTempParam) {
orderReceivingTempParam = new OrderReceivingTempParamImpl();
tempParam = orderReceivingTempParam;
} else {
tempParam = orderReceivingTempParam.clone();
}
break;
case BEING_DISPOSE:
if (null == beingDisposeTempParam) {
beingDisposeTempParam = new BeingDisposeTempParamImpl();
tempParam = beingDisposeTempParam;
} else {
tempParam = beingDisposeTempParam.clone();
}
break;
case DISPOSE_FINISH:
if (null == disposeFinishTempPara) {
disposeFinishTempPara = new DisposeFinishTempParaImpl();
tempParam = disposeFinishTempPara;
} else {
tempParam = disposeFinishTempPara.clone();
}
break;
default:
}
return tempParam;
}
}
用于限制工厂方法创建对象所传入的参数类型;
package com.icex.frame.utils.wechat.factory;
/**
* 选择微信模板消息参数
* @author Jjcc
* @version 1.0.0
* @Description
* @ClassName WeChatTempMsgType.java
* @createTime 2019年07月26日 14:45:00
*/
public enum WeChatTempMsgType {
/**
* 工单受理-模板消息参数
*/
ORDER_RECEIVING,
/**
* 工单处理中消息模板参数
*/
BEING_DISPOSE,
/**
* 工单处理完成-消息模板参数
*/
DISPOSE_FINISH,
}
微信的accept_token存活时间只有7200S,超过7200S该令牌就会失效,必须在令牌有效期内访问Wechat的API重新刷新accpet_token;我的解决思路是使用Spring中的定时任务组件import org.quartz.Job来定时请求WeChat提供的API来刷新令牌存活时间;
关于quartz这里不细说,配置quartz时,设置为项目启动成功后便执行定时任务;
package com.icex.bus.job;
import org.apache.commons.lang3.StringUtils;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import com.alibaba.fastjson.JSONObject;
import com.icex.common.Const;
import com.icex.frame.utils.DateUtil;
import com.icex.frame.utils.HttpClientUtil;
/**
* 刷新微信accessToken;
* accessToken的存活时间:7200Second;
* 定时任务3200Second执行一次刷新accessToken存活时间;
* 服务器重启后自动执行;
* @description
* @author Jjcc
* @createTime 2019/7/24 20:55
*/
public class WxAccessTokenJob extends BaseJob implements Job {
private static final String ACCESS_TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" + Const.WX_APP_ID + "&secret=" + Const.WX_APP_SECRET;
@Override
public void execute(JobExecutionContext arg0) {
System.out.println("------------获取微信token--------------");
try {
int i = 1;
while(i < 11) {
i++;
String ret = HttpClientUtil.doGet(ACCESS_TOKEN_URL, null);
JSONObject json = JSONObject.parseObject(ret);
String errcode = json.getString("errcode");
if ("0".equals(errcode) || StringUtils.isBlank(errcode)) {
//请求成功
Const.WX_ACCESS_TOKEN = json.getString("access_token");
System.out.println("时间:" + DateUtil.getCurrentTime() + "刷新token成功;token为---> " + Const.WX_ACCESS_TOKEN);
break;
}
}
System.out.println("本次刷新token使用次数为:---> " + (i-1));
} catch (Exception e) {
e.printStackTrace();
}
}
}
小程序中,生成的formId只能使用一次,且每个formeId的生命期只有7day,创建时间超过7day的formeId无法成功使用;
这里本人写了一个定时任务,每天的凌晨12点会删除存储formId表中超过7day的formId;具体的代码就不贴了,定时任务的配置同令牌刷新一起贴出了;
该方法通过openId找到对应formId,并执行工具类中封装的方法,这里不管推送是否成功,都会删除拿到的formId;
/**
* 微信模板消息推送
* @title WeChatMsgPush
* @description
* @author Jjcc
* @param openId
* @param informTempId 微信消息模板Id
* @param page 跳转的页面
* @param weChatMessTempPara 模板消息参数对象
* @param msgTempParaType 模板类型
* @return void
* @createTime 2019/7/26 12:01
* @throws
*/
public void weChatMsgPush(String openId, String informTempId, String page, WeChatMessTempPara weChatMessTempPara, WeChatTempMsgType msgTempParaType, ISysFormidService formidService) {
try {
SysFormid sysFormid = new SysFormid();
sysFormid.setOpenId(openId);
List sysFormidList = formidService.select(sysFormid);
if (0 != sysFormidList.size()) {
String formId = sysFormidList.get(0).getFormId();
if (StringUtils.isNotBlank(openId) && StringUtils.isNotBlank(formId)) {
WeChatToolUtil toolUtil = WeChatToolUtil.getInstance();
ITempParam tempMsgIns = WeChatTempMsgFactory.getTempMsgIns(msgTempParaType);
JSONObject jsonObject = tempMsgIns.createTempParam(weChatMessTempPara);
String resultStr = toolUtil.pushMessage(openId, informTempId, formId, page, jsonObject);
//删除formId
Example example = new Example(SysFormid.class);
example.createCriteria().andEqualTo("formId", formId);
int i = formidService.deleteByExample(example);
logger.error("微信模板消息推送结果:" + resultStr);
logger.error("删除formId结果:" + i);
} else {
logger.error("微信模板消息推送失败------->formId或openId参数为空!");
}
} else {
logger.error("当前openId无可用的formId");
}
} catch (Exception e) {
e.printStackTrace();
}
}
使用方式
模板参数实体bean
WeChatMessTempPara weChatMessTempPara = new WeChatMessTempPara();
weChatMessTempPara.setAcceptTime(now);
weChatMessTempPara.setServerStatusName("已受理");
weChatMessTempPara.setFlowContent(obj.getFlowContent());
/**
* openId:微信账号唯一标识符;
* ACCEPT_INFORM:微信的消息模板Id;
* PAGES:点击推送的消息后跳转至小程序中的页面;
* weChatMessTempPara:模板参数实体bean
* WeChatTempMsgType.ORDER_RECEIVING:选择何种模板
* sysFormidService:用于获取openId拥有的formId的对象
*/
weChatMsgPush(openId, ACCEPT_INFORM, PAGES + id, weChatMessTempPara, WeChatTempMsgType.ORDER_RECEIVING, sysFormidService);
end!!!