java发送公众号/服务通知模板消息到指定用户(完整流程|亲测可用)

2年前 (2022) 程序员胖胖胖虎阿
382 0 0

1、java发送服务通知模板消息到指定用户

准备:

  1. 获取当前微信小程序appId(小程序appId)获取当前小程序的秘钥secret
    java发送公众号/服务通知模板消息到指定用户(完整流程|亲测可用)
  2. 新建模板消息
    java发送公众号/服务通知模板消息到指定用户(完整流程|亲测可用)
    选用后勾选需要的字段并提交
    一次订阅:
    指用户订阅一次,服务号可不限时间地下发一条对应的订阅通知;
    长期订阅:
    指用户订阅一次,服务号可长期多次下发通知,长期订阅通知仅向政务民生、医疗,交通,金融等公共服务领域开放,比较难申请;
    服务通知:
    微信默认开启服务通知功能,在用户聊天列表中会出现橙色的服务通知
    java发送公众号/服务通知模板消息到指定用户(完整流程|亲测可用)
    java发送公众号/服务通知模板消息到指定用户(完整流程|亲测可用)
    java发送公众号/服务通知模板消息到指定用户(完整流程|亲测可用)

后端代码实现:

微信发送服务通知工具类

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 拉起订阅按钮
java发送公众号/服务通知模板消息到指定用户(完整流程|亲测可用)
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接口不同

配置模板消息

java发送公众号/服务通知模板消息到指定用户(完整流程|亲测可用)
java发送公众号/服务通知模板消息到指定用户(完整流程|亲测可用)
java发送公众号/服务通知模板消息到指定用户(完整流程|亲测可用)
这里最好是公众号关联小程序,绑定后虽然每个小主体的用户openid不同但是用户的unionid都是相同的
java发送公众号/服务通知模板消息到指定用户(完整流程|亲测可用)

获取公众号需要用到的信息

获取公钥私钥,设置白名单ip(也就是你部署项目的服务器ip或者你本地的公网ip不设置获取不到access_token)
java发送公众号/服务通知模板消息到指定用户(完整流程|亲测可用)
java发送公众号/服务通知模板消息到指定用户(完整流程|亲测可用)

后端代码实现

微信发送公众号消息工具类

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=

相关文章

暂无评论

暂无评论...