微信小程序结合微信公众号进行消息发送

由于小程序的模板消息已经废弃了,官方让使用订阅消息功能。而订阅消息的使用限制比较大,用户必须得订阅、需要获取用户同意接收消息的权限、用户必须得和小程序有交互的时候才能发送且只能发送一次。而我们的需求有可能是不限时间不限“次数”的发送消息。那么就可以使用官方提供的统一服务消息

统一服务消息

该功能还是有一点的使用限制的。

第一:小程序和公众号的主体必须是同一个。
第二:用户必须关注公众号。
第三:我们得有消息模板,小程序消息模板和公众号消息模板都可以。

结合文档,使用Java对发送消息的功能进行简单的封装

第一:使用httpClient请求地址,获取access_token

GET https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET
根据官方接口的返回值,我们封装返回值对象
@Data
public class AccessToken {
    /** 接口调用凭证 */
    private String access_token;
    /** 过期时间 */
    private Long expires_in;
    /** 错误信息代码 */
    private String errcode;
    /** 错误信息 */
    private String errmsg;
}
使用httpClient发送请求
AccessToken accessToken = new AccessToken();

String url = WxMessageConstant.GET_XCX_ACCESS_TOKEN_URL + "&appid=" + WxMessageConstant.APP_ID + "&secret=" + WxMessageConstant.SECRET;
String result = HttpClientUtils.doGet(url);

accessToken = JSON.parseObject(result, AccessToken.class);

注意:获得的token结果可以放在redis里面,设置上过期时间,防止应用重复获取access_token!

第二:构造请求

构造请求url

根据官方提供的接口再加上我们的获取的access_token进行构造

POST https://api.weixin.qq.com/cgi-bin/message/wxopen/template/uniform_send?access_token=ACCESS_TOKEN
构造请求参数

根据官方文档,我们一步一步来构造请求参数对象,我们用的是公众号的消息模板,所以这里没有weapp_template_msg参数

@Data
@AllArgsConstructor
@NoArgsConstructor
public class ReqSendMessageData {
    /**
     * 消息接收者
     */
    private String touser;
    /**
     * 公众号模板消息信息
     */
    private MpTemplateMsg mp_template_msg;
}

构造mp_template_msg对象

@Data
@NoArgsConstructor
@AllArgsConstructor
public class MpTemplateMsg {
    /** 公众号openId */
    private String appid;
    /** 公众号模板id */
    private String template_id;
    /** 跳转到公众号的地址,文档上写的必填,我没填,没有报错 */
    private String url;
    /** 公众号绑定的小程序信息 */
    private Miniprogram miniprogram;
    /** 数据 */
    private Map<String,Object> data;
}

构造小程序信息

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Miniprogram {
    /** 小程序appid */
    private String appid;
    /** 要跳转到小程序的页面 */
    private String page;
}

贴出httpClient发送post请求的方式

public static String postSendJsonParam(String url, String param) {
        CloseableHttpClient httpClient = HttpClients.createDefault();
        String result = null;
        // 请求方式
        HttpPost httpPost = null;
        // 响应
        CloseableHttpResponse httpResponse = null;
        httpPost = new HttpPost(url);
        httpPost.setHeader("Connection", REQUEST_HEADER_CONNECTION);
        httpPost.setHeader("User-Agent", REQUEST_HEADER_USER_AGENT);
        httpPost.addHeader("content-type", "application/json;charset=utf-8");
        httpPost.addHeader("accept", "application/json");
        httpPost.setEntity(new StringEntity(param, "UTF-8"));

        try {
            
            httpResponse = httpClient.execute(httpPost);
            HttpEntity httpEntity = httpResponse.getEntity();
            result = EntityUtils.toString(httpEntity);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (httpClient != null) {
                try {
                    httpClient.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        return result;
    }

构造返回值对象,我们根据错误代码进行扩展操作

@Data
public class ResSendMessageDTO {
    /** 错误代码 */
    private String errcode;
    /** 错误信息 */
    private String errmsg;
}

最后我们可以把这些封装成一个service交给spring管理。

public interface MessageService {
    /**
     * @author: taoym
     * @date: 2020/7/2 9:08
     * @desc: 推送消息
     */
    ResSendMessageDTO sendMessage(ReqSendMessageData data);
}

具体实现不展示了。

封装消息发送者

@Component
public class MessageSender {

    @Autowired
    private MessageService messageService;

    /**
     * @param first      消息标题
     * @param keywordMap 消息内容
     * @param page       要跳转到小程序的页面
     * @param templateId 模板消息编号
     * @param touser     消息接收者openId
     * @author: taoym
     * @date: 2020/9/28 11:25
     * @desc: 消息发送者
     */
    public ResSendMessageDTO sendMessage(String first, Map<String, Keyword> keywordMap, String touser, String page, String templateId) {

        // 消息模板上的数据
        Map<String, Object> messageData = Maps.newHashMap();
        // first,可以理解为 标题
        messageData.put("first", new First(first));
        // keywordMap 可以理解为内容
        messageData.putAll(keywordMap);

        ReqSendMessageData reqSendMessageData = new ReqSendMessageData();
        reqSendMessageData.setTouser(PasswordUtils.decrypt(touser));
        MpTemplateMsg mpTemplateMsg = new MpTemplateMsg();
        Miniprogram miniprogram = new Miniprogram();
        miniprogram.setPage(page);
        mpTemplateMsg.setMiniprogram(miniprogram);
        mpTemplateMsg.setData(messageData);
        mpTemplateMsg.setTemplate_id(templateId);
        reqSendMessageData.setMp_template_msg(mpTemplateMsg);
        ResSendMessageDTO resSendMessageDTO = messageService.sendMessage(reqSendMessageData);
        // 0为成功的标志
        if (!resSendMessageDTO.getErrcode().equals("0")) {
            throw new ExceptionCore(resSendMessageDTO.getErrcode(), resSendMessageDTO.getErrmsg());
        }
        return resSendMessageDTO;
    }
}

最后我们只需要根据模板消息的内容构造合理的参数,然后调用该方法即可发送消息。