1、java发送服务通知模板消息到指定用户
准备:
- 获取当前微信小程序appId(小程序appId)获取当前小程序的秘钥secret
- 新建模板消息
选用后勾选需要的字段并提交
一次订阅:
指用户订阅一次,服务号可不限时间地下发一条对应的订阅通知;
长期订阅:
指用户订阅一次,服务号可长期多次下发通知,长期订阅通知仅向政务民生、医疗,交通,金融等公共服务领域开放,比较难申请;
服务通知:
微信默认开启服务通知功能,在用户聊天列表中会出现橙色的服务通知
后端代码实现:
微信发送服务通知工具类
import com.alibaba.fastjson.JSONObject;
import com.shop.cereshop.commons.utils.third.IdWorker;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
import java.io.*;
import java.net.URL;
import java.net.URLConnection;
import java.text.SimpleDateFormat;
import java.util.*;
/**
* @ClassName WechatUtil
* @Version 1.0
*/
@Slf4j(topic = "WxchatSendUtil")
@Component
public class WxchatSendUtil {
@Value("${bussiness.appId}")
private static String appId;
@Value("${bussiness.secret}")
private static String secret;
@Value("${bussiness.orderPlacementNoticeTemplateId}")
private static String orderPlacementNoticeTemplateId;
/**
* 获取小程序token
*
* @return
*/
public static String getAccessToken() {
String url = "https://api.weixin.qq.com/cgi-bin/token?" +
"appid=" + appId + "&secret=" + secret + "&grant_type=client_credential";
PrintWriter out = null;
BufferedReader in = null;
String line;
StringBuffer stringBuffer = new StringBuffer();
try {
URL realUrl = new URL(url);
// 打开和URL之间的连接
URLConnection conn = realUrl.openConnection();
// 设置通用的请求属性 设置请求格式
//设置返回类型
conn.setRequestProperty("contentType", "text/plain");
//设置请求类型
conn.setRequestProperty("content-type", "application/x-www-form-urlencoded");
//设置超时时间
conn.setConnectTimeout(1000);
conn.setReadTimeout(1000);
conn.setDoOutput(true);
conn.connect();
// 获取URLConnection对象对应的输出流
out = new PrintWriter(conn.getOutputStream());
// flush输出流的缓冲
out.flush();
// 定义BufferedReader输入流来读取URL的响应 设置接收格式
in = new BufferedReader(
new InputStreamReader(conn.getInputStream(), "UTF-8"));
while ((line = in.readLine()) != null) {
stringBuffer.append(line);
}
JSONObject jsonObject = JSONObject.parseObject(stringBuffer.toString());
return jsonObject.getString("access_token");
} catch (Exception e) {
e.printStackTrace();
}
//使用finally块来关闭输出流、输入流
finally {
try {
if (out != null) {
out.close();
}
if (in != null) {
in.close();
}
} catch (IOException ex) {
ex.printStackTrace();
}
}
return null;
}
public static final String SEND_INFO_URL = "https://api.weixin.qq.com/cgi-bin/message/subscribe/send?access_token=";
public static void main(String[] args) throws IOException {
// 1、获取 接口调用凭证
RestTemplate restTemplate = new RestTemplate();
String url = SEND_INFO_URL + WxchatSendUtil.getAccessToken();
//拼接推送的模版
WxMssVO wxMssVo = new WxMssVO();
//用户的openId
wxMssVo.setTouser("oa4u44ukI8wbYO3it-ysAm_yNJSo");
//订阅消息模板id
wxMssVo.setTemplate_id(orderPlacementNoticeTemplateId);
// wxMssVo.setPage("pages/appointment/line_up?"+"shopId="+info.getShopId());
Map<String, TemplateData> m = new HashMap<>(4);
m.put("character_string36", new TemplateData(IdWorker.generSequeId()));
m.put("thing2", new TemplateData("用户下单通知啊"));
m.put("phrase28", new TemplateData("待付款"));
SimpleDateFormat simpleDateFormat=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
m.put("time10", new TemplateData(simpleDateFormat.format(new Date())));
wxMssVo.setData(m);
ResponseEntity<String> responseEntity =
restTemplate.postForEntity(url, wxMssVo, String.class);
System.out.println(responseEntity.getBody());
}
}
WxMssVO
import lombok.Data;
import java.util.Map;
/**
* @author ys
*/
@Data
public class WxMssVO {
/**
* 用户openid
*/
private String touser;
/**
* 订阅消息模版id
*/
private String template_id;
/**
* 默认跳到小程序首页
*/
private String page;
/**
* 推送文字
*/
private Map<String, TemplateData> data;
}
TemplateData
import lombok.AllArgsConstructor;
import lombok.Data;
/**
* @author ys
*/
@AllArgsConstructor
@Data
public class TemplateData {
private String value;
}
前端代码实现:
用户在在开发者提供的 H5 页面中,通过 JSSDK 拉起订阅按钮
wx.requestSubscribeMessage
<button bindtap="getAuthority" type='primary'>获取订阅消息授权</button>
//获取授权的点击事件
getAuthority() {
wx.requestSubscribeMessage({
//这里填入我们生成的模板id
tmplIds: ['CFeSWarQL*************8V8bFLkBzTU'],
success(res) {
console.log('授权成功', res)
}, fail(res) {
console.log('授权失败', res) }
}
)
}
2、java发送微信公众号模板消息到指定用户
这里和发送服务通知的不同
1> 要多一步配置并验证token(token需要验签)
2> 需要配置ip白名单
3> 发送消息的https接口不同
配置模板消息
这里最好是公众号关联小程序,绑定后虽然每个小主体的用户openid不同但是用户的unionid都是相同的
获取公众号需要用到的信息
获取公钥私钥,设置白名单ip(也就是你部署项目的服务器ip或者你本地的公网ip不设置获取不到access_token)
后端代码实现
微信发送公众号消息工具类
import com.alibaba.fastjson.JSONObject;
import com.shop.cereshop.app.param.shop.WxSendParam;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
import java.io.*;
import java.net.URL;
import java.net.URLConnection;
import java.util.HashMap;
import java.util.Map;
/**
* @author ys
* @ClassName WechatUtil
* @Version 1.0
*/
@Slf4j(topic = "WxChatSendUtil")
@Component
public class WxChatSendUtil {
// @Value("${bussiness.appId}")
// private static String appId;
// @Value("${bussiness.secret}")
// private static String secret;
// @Value("${bussiness.orderPlacementNoticeTemplateId}")
// private static String orderPlacementNoticeTemplateId;
private static final String appId="微信公众号appID";
private static final String secret="微信公众号secret密钥";
private static final String orderPlacementNoticeTemplateId="微信公众号模板消息id";
/**
* 获取小程序token
* @return
*/
public static String getAccessToken() {
String url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" + appId + "&secret=" + secret + "&grant_type=client_credential";
PrintWriter out = null;
BufferedReader in = null;
String line;
StringBuffer stringBuffer = new StringBuffer();
try {
URL realUrl = new URL(url);
// 打开和URL之间的连接
URLConnection conn = realUrl.openConnection();
// 设置通用的请求属性 设置请求格式
//设置返回类型
conn.setRequestProperty("contentType", "text/plain");
//设置请求类型
conn.setRequestProperty("content-type", "application/x-www-form-urlencoded");
//设置超时时间
conn.setConnectTimeout(1000);
conn.setReadTimeout(1000);
conn.setDoOutput(true);
conn.connect();
// 获取URLConnection对象对应的输出流
out = new PrintWriter(conn.getOutputStream());
// flush输出流的缓冲
out.flush();
// 定义BufferedReader输入流来读取URL的响应 设置接收格式
in = new BufferedReader(
new InputStreamReader(conn.getInputStream(), "UTF-8"));
while ((line = in.readLine()) != null) {
stringBuffer.append(line);
}
JSONObject jsonObject = JSONObject.parseObject(stringBuffer.toString());
return jsonObject.getString("access_token");
} catch (Exception e) {
e.printStackTrace();
}
//使用finally块来关闭输出流、输入流
finally {
try {
if (out != null) {
out.close();
}
if (in != null) {
in.close();
}
} catch (IOException ex) {
ex.printStackTrace();
}
}
return null;
}
/**
* 发送微信小程序订阅消息
*
* @param wxSendParam
* @return
*/
public static String WxChatSendOrderNotice(WxSendParam wxSendParam) {
// 1、获取 接口调用凭证
RestTemplate restTemplate = new RestTemplate();
String url = "https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=" + WxChatSendUtil.getAccessToken();
//拼接推送的模版
WxMssVO wxMssVo = new WxMssVO();
//用户的openId
wxMssVo.setTouser(wxSendParam.getBusinessOpenId());
//订阅消息模板id
wxMssVo.setTemplate_id(orderPlacementNoticeTemplateId);
wxMssVo.setPage("pages/order/order");
wxMssVo.setData(wxSendParam.getOrderMap());
ResponseEntity<String> responseEntity = restTemplate.postForEntity(url, wxMssVo, String.class);
return responseEntity.getBody();
}
public static void main(String[] args) {
WxSendParam wxSendParam = new WxSendParam();
Map<String, TemplateData> orderSendWxSend = new HashMap<>(5);
orderSendWxSend.put("first", new TemplateData("下单成功通知"));
orderSendWxSend.put("keyword1", new TemplateData("113e23432432"));
orderSendWxSend.put("keyword2", new TemplateData("已付款"));
orderSendWxSend.put("keyword3", new TemplateData("320.18元"));
orderSendWxSend.put("keyword4", new TemplateData("2021-08-19"));
wxSendParam.setOrderMap(orderSendWxSend);
wxSendParam.setBusinessOpenId("xxxxx");
WxChatSendUtil.WxChatSendOrderNotice(wxSendParam);
}
}
通过前端授权公众号获取的code解析用户在公众号中的openid
import com.alibaba.fastjson.JSONObject;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletResponse;
/**
* 通过前端授权公众号获取的code解析用户在公众号中的openid
* @param code
* @param response
* @return
*/
public static String getopenid(String code, HttpServletResponse response){
String appid= "微信公众号appID";
String secret = "微信公众号secret密钥";
response.setHeader("Access-Control-Allow-Origin", "*");
/*星号表示所有的域都可以接受,*/
response.setHeader("Access-Control-Allow-Methods", "GET,POST");
String wxLoginUrl = "https://api.weixin.qq.com/sns/oauth2/access_token";
String param = "appid="+appid+"&secret="+secret+"&code="+code+"&grant_type=authorization_code";
String jsonString = GetPostUntil.sendGet(wxLoginUrl, param);
JSONObject json = JSONObject.parseObject(jsonString);
String openid = json.getString("openid");
log.info("###############"+openid);
return openid;
}
授权公众号controller
/**
* 授权公众号
* @param param
* @return
*/
@PostMapping(value = "authorizedOfficialAccount")
@ApiOperation(value = "授权公众号")
public Result authorizedOfficialAccount(@RequestBody AuthorizedOfficialAccountParam param, HttpServletResponse response) throws CoBusinessException {
String openid = WxChatSendUtil.getopenid(param.getCode(),response);
if(StringUtils.isNotBlank(openid)){
log.info("==========================================:"+openid);
//todo 对公众号openid做数据库操作
}
return new Result(CoReturnFormat.SUCCESS);
}
Token验证
import com.shop.cereshop.app.utils.wxSendUtils.SignUtil;
import io.swagger.annotations.Api;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
/**
* 订单模块
*/
@RestController
@RequestMapping("/wxSend")
@Slf4j(topic = "wxSendController")
@Api(value = "服务号发送消息", tags = "服务号发送消息")
public class wxSendController {
/**
* 这个token要与公众平台服务器配置填写的token一致
*/
private final static String TOKEN = "xxxxxx";
@GetMapping("/checkSignature")
public String checkSignature(HttpServletRequest request) {
String signature = request.getParameter("signature");
String timestamp = request.getParameter("timestamp");
String nonce = request.getParameter("nonce");
String echostr = request.getParameter("echostr");
log.info("接收来自微信服务器的认证信息。signature={},timestamp={},nonce={},echostr={}",
signature, timestamp, nonce, echostr);
if(StringUtils.isAnyBlank(signature,timestamp,nonce,echostr)){
log.info("请求参数非法");
return null;
}
//加密后的mySignature与微信公众平台的signature一致
boolean check = SignUtil.checkSignature(TOKEN, signature, timestamp, nonce);
if (check) {
return echostr;
}
return null;
}
}
验签工具SignUtil类
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
public class SignUtil {
/**
* 校验签名
*
* @param token 服务器配置里写的TOKEN
* @param signature 签名
* @param timestamp 时间戳
* @param nonce 随机数
* @return true 成功,false 失败
*/
public static boolean checkSignature(String token, String signature, String timestamp, String nonce) {
String checkText = null;
if (null != signature) {
//对Token,timestamp nonce 按字典排序
String[] paramArr = new String[]{token, timestamp, nonce};
Arrays.sort(paramArr);
//将排序后的结果拼成一个字符串
String content = paramArr[0].concat(paramArr[1]).concat(paramArr[2]);
try {
MessageDigest md = MessageDigest.getInstance("SHA-1");
//对接后的字符串进行sha1加密
byte[] digest = md.digest(content.getBytes());
checkText = byteToStr(digest);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
}
//将加密后的字符串与signature进行对比
return checkText != null && checkText.equals(signature.toUpperCase());
}
/**
* 将字节数组转化为16进制字符串
*
* @param byteArrays 字符数组
* @return 字符串
*/
private static String byteToStr(byte[] byteArrays) {
StringBuilder str = new StringBuilder();
for (byte byteArray : byteArrays) {
str.append(byteToHexStr(byteArray));
}
return str.toString();
}
/**
* 将字节转化为十六进制字符串
*
* @param myByte 字节
* @return 字符串
*/
private static String byteToHexStr(byte myByte) {
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[(myByte >>> 4) & 0X0F];
tempArr[1] = digit[myByte & 0x0F];
return new String(tempArr);
}
}
WxMssVO
import lombok.Data;
import java.util.Map;
/**
* @author ys
*/
@Data
public class WxMssVO {
/**
* 用户openid
*/
private String touser;
/**
* 订阅消息模版id
*/
private String template_id;
/**
* 默认跳到小程序首页
*/
private String page;
/**
* 推送文字
*/
private Map<String, TemplateData> data;
}
TemplateData
import lombok.AllArgsConstructor;
import lombok.Data;
/**
* @author ys
*/
@AllArgsConstructor
@Data
public class TemplateData {
private String value;
}
注:前端需要授权公众号
补充:
获取公众号所有的关注用户openid
https://api.weixin.qq.com/cgi-bin/user/get?access_token=
“openid”: [
“oNg5r5w54H4ZVfIWvOstPkY1WPDw”,
“oNg5r55r_nxPP8J0iY3gapn__rkw”,
“oNg5r5xCxn_dmErxcHzBCujnhhko”,
“oNg5r55Slu3Qt8-jlUxhdeQHrwTA”,
“oNg5r57SwfV-I5w1ccWjypTfyC_Q”,
“oNg5r53lQSBGeoJNdTiqGqQThhSk”,
“oNg5r5_vTiEXP5IArZHmAf1G0a64”,
“oNg5r59mPCOnnEzkNkTXpbRZ3zFw”,
“oNg5r5xYuHAPuZMLVjFlmely78Ig”,
“oNg5r565AHcksPhcYUwfgnSMAo24”,
“oNg5r5z6gKpe1mPqDkReb067IMgI”
]
获取公众号所有的标签信息
https://api.weixin.qq.com/cgi-bin/tags/get?access_token=
{
“id”: 2,
“name”: “星标组”,
“count”: 0
},
{
“id”: 100,
“name”: “11”,
“count”: 1
},
{
“id”: 101,
“name”: “22”,
“count”: 1
},
{
“id”: 102,
“name”: “33”,
“count”: 1
}
通过openid获取用户分组等信息
https://api.weixin.qq.com/cgi-bin/user/info?access_token=&openid=