微信学习(一)——java代码发送服务通知

最近开发微信和小程序,需要后台实现推送,所以就动手实现一下小程序模版消息功能的推送。

我们先来看看官方的说明:

https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/uniform-message/uniformMessage.send.html

请求地址

POST https://api.weixin.qq.com/cgi-bin/message/wxopen/template/send?access_token=ACCESS_TOKEN

请求参数

参数 类型 默认值 必填 说明
access_token string   接口调用凭证
touser string   用户openid,可以是小程序的openid,也可以是mp_template_msg.appid对应的公众号的openid
weapp_template_msg Object   小程序模板消息相关的信息,可以参考小程序模板消息接口; 有此节点则优先发送小程序模板消息
mp_template_msg Object   公众号模板消息相关的信息,可以参考公众号模板消息接口;有此节点并且没有weapp_template_msg节点时,发送公众号模板消息

为了使第三方开发者能够为用户提供更多更有价值的个性化服务,微信公众平台 开放了许多接口,包括自定义菜单接口、客服接口、获取用户信息接口、用户分组接口、群发接口等,access_token接口调用凭证是一个重要的参数,但是这个代表的是什么意思呢?

access_token简介

开发者在调用这些接口时,都需要传入一个相同的参数 access_token,它是公众账号的全局唯一票据,它是接口访问凭证。

access_token是公众号的全局唯一票据,公众号调用各接口时都需使用access_token。开发者需要进行妥善保存。

access_token 的存储至少要保留 512 个字符空间;

access_token 的有效期目前为 2 个小时,需定时刷新,重复获取将导致上次获取的 access_token 失效;

建议开发者使用中控服务器统一获取和刷新 access_token,其他业务逻辑服务器所使用的 access_token 均来自于该中控服务器,不应该各自去刷新,否则容易造成冲突,导致 access_token 覆盖而影响业务;

access_token 的有效期通过返回的 expire_in 来传达,目前是7200秒之内的值,中控服务器需要根据这个有效时间提前去刷新。在刷新过程中,中控服务器可对外继续输出的老 access_token,此时公众平台后台会保证在5分钟内,新老 access_token 都可用,这保证了第三方业务的平滑过渡;

access_token 的有效时间可能会在未来有调整,所以中控服务器不仅需要内部定时主动刷新,还需要提供被动刷新 access_token 的接口,这样便于业务服务器在API调用获知 access_token 已超时的情况下,可以触发 access_token 的刷新流程。

公众号可以使用AppID和AppSecret调用本接口来获取access_token。AppID和AppSecret可在微信公众平台官网-开发者中心页中获得(需要已经成为开发者,且帐号没有异常状态)。

问题:如何通过获取access_token?

解决方案:(1)直接通过浏览器访问。(2)编写程序,模拟https连接,获得access_token。

解决详细步骤如下:

(1)浏览器中直接输入链接:https://mp.weixin.qq.com/debug/cgi-bin/apiinfo?t=index&type=%E5%9F%BA%E7%A1%80%E6%94%AF%E6%8C%81&form=%E8%8E%B7%E5%8F%96access_token%E6%8E%A5%E5%8F%A3%20/token&token=&lang=zh_CN

然后把APPID和APPSECRET替换成自己的appid和appsecret,在浏览器即可获得access_token。

(2)如何在程序中模拟发送https请求,并且获取到access_token呢

我们再看看官方的说明:

https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/access-token/auth.getAccessToken.html

https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140183

微信学习(一)——java代码发送服务通知_第1张图片

请求地址

GET https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET

请求参数

参数 类型 默认值 必填 说明
grant_type string   填写client_credential
appid string   小程序唯一凭证,即 AppID
secret string   小程序唯一凭证密钥,即 AppSecret,获取方式同 appid

返回值

Object

返回的 JSON 数据包

属性 类型 说明
access_token string 获取到的凭证
expires_in number 凭证有效时间,单位:秒。目前是7200秒之内的值
errcode number 错误码
errmsg string 错误信息

errcode 的合法值

说明
-1 系统繁忙,此时请开发者稍候再试
0 请求成功
40001 AppSecret错误或者AppSecret不属于这个小程序,请开发者确认AppSecret的正确性
40002 请确保grant_type字段值为client_credential
40013 不合法的 AppID,请开发者检查 AppID 的正确性,避免异常字符,注意大小写

返回数据示例

正常情况下,微信会返回下述JSON数据包

{"access_token":"ACCESS_TOKEN","expires_in":7200}

错误时微信会返回错误码等信息,JSON数据包示例如下(该示例为AppSecret无效错误)

{"errcode":40001,"errmsg":"invalid AppSecret"}

完整代码如下:

package com.study.dto;

/**
 * @Auther: lds
 * @Date: 2019/7/25 16:03
 * @Description: 微信小程序发送模板消息(服务通知)的入参
 */
public class WxMessageDTO {

    private String touser; // 用户的openid
    private String template_id; // 所需下发的模板消息的id
    private String form_id; // 表单提交场景下,为submit事件带上的form_id;支付场景下,为本次支付的prepay_id
    private String activityname; // 活动名称
    private String lotterystate; // 用户是否中奖
    private String lotterytime; // 抽奖日期

    @Override
    public String toString() {
        return "WxMessageSendDTO{" +
                ", touser='" + touser + '\'' +
                ", template_id='" + template_id + '\'' +
                ", form_id='" + form_id + '\'' +
                ", activityname='" + activityname + '\'' +
                ", lotterystate='" + lotterystate + '\'' +
                ", lotterytime='" + lotterytime + '\'' +
                '}';
    }

    public String getTouser() {
        return touser;
    }

    public void setTouser(String touser) {
        this.touser = touser;
    }

    public String getTemplate_id() {
        return template_id;
    }

    public void setTemplate_id(String template_id) {
        this.template_id = template_id;
    }

    public String getForm_id() {
        return form_id;
    }

    public void setForm_id(String form_id) {
        this.form_id = form_id;
    }

    public String getActivityname() {
        return activityname;
    }

    public void setActivityname(String activityname) {
        this.activityname = activityname;
    }

    public String getLotterystate() {
        return lotterystate;
    }

    public void setLotterystate(String lotterystate) {
        this.lotterystate = lotterystate;
    }

    public String getLotterytime() {
        return lotterytime;
    }

    public void setLotterytime(String lotterytime) {
        this.lotterytime = lotterytime;
    }
}
package com.study.dto;

import com.alibaba.fastjson.JSONObject;

/**
 * @Auther: lds
 * @Date: 2019/7/25 17:20
 * @Description: 拼接模板消息相关的字段
 */
public class WeappTemplateMsgDTO {

    private String template_id; // 所需下发的模板消息的id
    private String page; // 点击模板卡片后跳转页面,仅限小程序内的页面
    private String form_id; // 表单提交场景下,为submit事件带上的form_id;支付场景下,为本次支付的prepay_id
    private JSONObject data; // 小程序模板消息发送的数据,不填则发送空模板

    @Override
    public String toString() {
        return "WeappTemplateMsg{" +
                "template_id='" + template_id + '\'' +
                ", page='" + page + '\'' +
                ", form_id='" + form_id + '\'' +
                ", data=" + data +
                '}';
    }

    public String getTemplate_id() {
        return template_id;
    }

    public void setTemplate_id(String template_id) {
        this.template_id = template_id;
    }

    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 JSONObject getData() {
        return data;
    }

    public void setData(JSONObject data) {
        this.data = data;
    }
}
package com.study.dto;

import com.alibaba.fastjson.JSONObject;

/**
 * @Auther: lds
 * @Date: 2019/7/25 16:03
 * @Description: 发送消息需要的入参
 */
public class WxMessageSendDTO {

    private String touser; // 用户的openid
    private WeappTemplateMsgDTO weapp_template_msg; // 小程序模板消息相关的信息

    @Override
    public String toString() {
        return "WxMessageSendDTO{" +
                "touser='" + touser + '\'' +
                ", weapp_template_msg=" + weapp_template_msg +
                '}';
    }

    public String getTouser() {
        return touser;
    }

    public void setTouser(String touser) {
        this.touser = touser;
    }

    public WeappTemplateMsgDTO getWeapp_template_msg() {
        return weapp_template_msg;
    }

    public void setWeapp_template_msg(WeappTemplateMsgDTO weapp_template_msg) {
        this.weapp_template_msg = weapp_template_msg;
    }
}
package com.study.controller;

import com.alibaba.fastjson.JSONObject;
import com.study.dto.WxMessageDTO;
import com.study.service.WxMessageSendService;
import com.study.utils.ReturnInfo;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

/**
 * @Auther: lds
 * @Date: 2019/7/25 16:03
 * @Description: 微信小程序发送服务通知
 */
@RestController
@RequestMapping(value = "/wxMessage")
public class WxMessageSendController {

    private static final Logger logger = LoggerFactory.getLogger(WxMessageSendController.class);

    @Autowired
    private WxMessageSendService wxMessageSendService;

    /**
     * 功能描述:微信小程序发送模板消息(服务通知)
     * 

* 入参:WxMessageSendDTO * 逻辑:根据微信官方API,调用微信官方接口先获取access_token接口调用凭证, * 在根据消息模板拼接的需要发送的内容,调用微信官方接口发送消息给用户 * code: * 入参有空,code:2001 * 系统异常,code:2002 */ @RequestMapping(value = "/send", method = RequestMethod.POST) public String sendWxMessage(@RequestBody WxMessageDTO wxMessageDTO) { logger.info("WxMessageSendController Method sendWxMessage 传递参数 WxMessageDTO: " + JSONObject.toJSONString(wxMessageDTO)); ReturnInfo tReturnInfo = new ReturnInfo(); // 判断入参 if (wxMessageDTO == null) { tReturnInfo.setCode("2001"); tReturnInfo.setFlag("false"); tReturnInfo.setMes("param is null"); tReturnInfo.setData(null); return JSONObject.toJSONString(tReturnInfo); } ReturnInfo temp = checkNull(wxMessageDTO); if ("false".equals(temp.getFlag())) { tReturnInfo.setCode("2001"); tReturnInfo.setFlag("false"); tReturnInfo.setMes(temp.getMes()); tReturnInfo.setData(null); return JSONObject.toJSONString(tReturnInfo); } // 拼接微信推送的模板 String errcode = wxMessageSendService.wxMessageSend(wxMessageDTO); if ("0".equals(errcode)) { tReturnInfo.setCode("0"); tReturnInfo.setFlag("true"); tReturnInfo.setMes("微信小程序发送微信消息成功"); } else if ("40037".equals(errcode)) { tReturnInfo.setCode("40037"); tReturnInfo.setFlag("false"); tReturnInfo.setMes("模板ID不正确"); } else if ("41028".equals(errcode)) { tReturnInfo.setCode("41028"); tReturnInfo.setFlag("false"); tReturnInfo.setMes("form_id过期或者不正确"); } else if ("41029".equals(errcode)) { tReturnInfo.setCode("41029"); tReturnInfo.setFlag("false"); tReturnInfo.setMes("form_id已被使用"); } else if ("41030".equals(errcode)) { tReturnInfo.setCode("41030"); tReturnInfo.setFlag("false"); tReturnInfo.setMes("page不正确"); } else if ("45009".equals(errcode)) { tReturnInfo.setCode("45009"); tReturnInfo.setFlag("false"); tReturnInfo.setMes("接口调用超过限额"); } else if ("40003".equals(errcode)) { tReturnInfo.setCode("40003"); tReturnInfo.setFlag("false"); tReturnInfo.setMes("openid不正确"); } else if ("40013".equals(errcode)) { tReturnInfo.setCode("40013"); tReturnInfo.setMes("不合法的APPID"); } return JSONObject.toJSONString(tReturnInfo); } private ReturnInfo checkNull(WxMessageDTO wxMessageDTO) { ReturnInfo tReturnInfo = new ReturnInfo(); String mes = ""; if (StringUtils.isBlank(wxMessageDTO.getTouser())) { mes += "touser不能为空 "; } if (StringUtils.isBlank(wxMessageDTO.getForm_id())) { mes += "form_id不能为空 "; } if (!"".equals(mes)) { tReturnInfo.setFlag("false"); tReturnInfo.setMes(mes); } else { tReturnInfo.setFlag("true"); } return tReturnInfo; } }

package com.study.service;


import com.study.dto.WxMessageDTO;

/**
 * @Auther: lds
 * @Date: 2019/7/25 16:03
 * @Description:
 */
public interface WxMessageSendService {

    public String wxMessageSend(WxMessageDTO wxMessageDTO);
}
package com.study.service.impl;

import com.alibaba.fastjson.JSONException;
import com.alibaba.fastjson.JSONObject;
import com.study.dto.WeappTemplateMsgDTO;
import com.study.dto.WxMessageDTO;
import com.study.dto.WxMessageSendDTO;
import com.study.service.WxMessageSendService;
import com.study.utils.ReturnInfo;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import java.util.concurrent.TimeUnit;

/**
 * @Auther: lds
 * @Date: 2019/7/25 16:03
 * @Description:
 */
@Service
public class WxMessageSendServiceImpl implements WxMessageSendService {

    private static final Logger logger = LoggerFactory.getLogger(WxMessageSendServiceImpl.class);

    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private RestTemplate restTemplate;

    @Value("${wxmini.appid}")
    private String appid; // 小程序唯一凭证
    @Value("${wxmini.appsecret}")
    private String appsecret; // 小程序唯一凭证密钥
    @Value("${wxmini.templateid}")
    private String templateid; // 模板消息ID
    @Value("${wxmini.page}")
    private String page; // 跳转的页面

    // redis中保存的access_token
    public static final String WXNIMI_ACCESS_TOKEN = "WXNIMI_ACCESS_TOKEN";
    // access_token接口调用凭证
    public static final String ACCESS_TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential" +
            "&appid=APPID&secret=APPSECRET";
    // 微信消息发送
    public static final String REQUEST_URL = "https://api.weixin.qq.com/cgi-bin/message/wxopen/template/send?" +
            "access_token=ACCESS_TOKEN";

    @Override
    public String wxMessageSend(WxMessageDTO wxMessageDTO) {
        logger.info("WxMessageSendServiceImpl Method wxMessageSend 传递参数 wxMessageDTO: " + JSONObject.toJSONString(wxMessageDTO));
        JSONObject postResultJson = null;
        String access_token = null;
        String errcode = null;
        try {
            String touser = wxMessageDTO.getTouser();
            // 获取access_token接口调用凭证
            access_token = this.redisTemplate.opsForValue().get(WXNIMI_ACCESS_TOKEN);
            if (StringUtils.isBlank(access_token)) {
                ReturnInfo tReturnInfo = getAccess_token(appid, appsecret);
                access_token = tReturnInfo.getData();
                this.redisTemplate.opsForValue().set(WXNIMI_ACCESS_TOKEN, access_token, 2, TimeUnit.HOURS);
            }
            logger.info("#### 获取的access_token: " + access_token);
            // 将URL中的ACCESS_TOKEN替换掉
            String requestUrl = REQUEST_URL.replace("ACCESS_TOKEN", access_token);
            // 拼接微信推送的模板
            WxMessageSendDTO wxMessage = new WxMessageSendDTO();
            wxMessage.setTouser(touser);
            WeappTemplateMsgDTO weappTemplateMsgDTO = new WeappTemplateMsgDTO();
            weappTemplateMsgDTO.setTemplate_id(wxMessageDTO.getTemplate_id());
            weappTemplateMsgDTO.setForm_id(wxMessageDTO.getForm_id());
            weappTemplateMsgDTO.setPage(page);
            JSONObject keyword1 = new JSONObject();
            keyword1.put("value", wxMessageDTO.getActivityname());
            JSONObject keyword2 = new JSONObject();
            keyword2.put("value", "已开奖,点击查看中奖名单");
            JSONObject keyword3 = new JSONObject();
            keyword3.put("value", wxMessageDTO.getLotterytime());
            JSONObject keyword4 = new JSONObject();
            String lotterystate = wxMessageDTO.getLotterystate();
            keyword4.put("value", "恭喜!本次活动您获得" + lotterystate);
            if ("N".equals(lotterystate)) {
                keyword4.put("value", "很遗憾,本次活动您未中奖");
            }
            JSONObject data = new JSONObject();
            data.put("keyword1", keyword1);
            data.put("keyword2", keyword2);
            data.put("keyword3", keyword3);
            data.put("keyword4", keyword4);
            weappTemplateMsgDTO.setData(data);
            wxMessage.setWeapp_template_msg(weappTemplateMsgDTO);
            // 调用微信官方接发送模板消息
            String postForObject = this.restTemplate.postForObject(requestUrl, JSONObject.toJSONString(wxMessage), String.class);
            postResultJson = JSONObject.parseObject(postForObject);
            errcode = postResultJson.getString("errcode");
        } catch (Exception e) {
            logger.info("#### 微信小程序调用官方微信发模板消息异常", e);
        }
        return errcode;
    }

    /**
     * @param appid
     * @param appsecret
     * @return 使用appid、appsecret获取access_token
     */
    private ReturnInfo getAccess_token(String appid, String appsecret) {
        ReturnInfo tReturnInfo = new ReturnInfo();
        try {
            // 将URL中的两个参数替换掉
            String requestUrl = ACCESS_TOKEN_URL.replace("APPID", appid).replace("APPSECRET", appsecret);
            // 调用微信官方接口获取access_token
            JSONObject jsonObject = this.restTemplate.getForObject(requestUrl, JSONObject.class);
            // 取出access_token
            String access_token = jsonObject.getString("access_token");
            if (StringUtils.isBlank(access_token)) {
                String errcode = jsonObject.getString("errcode");
                if ("-1".equals(errcode)) {
                    tReturnInfo.setCode("-1");
                    tReturnInfo.setMes("系统繁忙");
                } else if ("40001".equals(errcode)) {
                    tReturnInfo.setCode("40001");
                    tReturnInfo.setMes("APPSECRET错误,请确认APPSECRET是否正确");
                } else if ("40002".equals(errcode)) {
                    tReturnInfo.setCode("40002");
                    tReturnInfo.setMes("请确保grant_type字段值为client_credential");
                } else if ("40013".equals(errcode)) {
                    tReturnInfo.setCode("40013");
                    tReturnInfo.setMes("不合法的APPID");
                }
                logger.info("#### 微信小程序获取access_token的errcode: " + errcode);
            } else {
                tReturnInfo.setCode("2000");
                tReturnInfo.setData(access_token);
            }
        } catch (JSONException e) {
            logger.info("#### 微信小程序调用官方微信获取access_token接口异常", e);
            return tReturnInfo;
        }
        return tReturnInfo;
    }
}

在实际项目中应该将APPID和APPSECRET写在配置文件中,读取一下,绝对不可以通过前端传入!这个动作是非常危险的,会导致数据的泄露!

考虑到安全问题,我对APPID和APPSECRET进行了遮挡,大家可以根据自己的进行修改。

微信学习(一)——java代码发送服务通知_第2张图片

获取到的access_token效果图:

微信学习(一)——java代码发送服务通知_第3张图片

服务通知显示效果:

微信学习(一)——java代码发送服务通知_第4张图片

 

你可能感兴趣的:(微信)