微信學習(一)——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

請求地址

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;

    /**
     * 功能描述:微信小程序發送模板消息(服務通知)
     * <p>
     * 入參: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<String, String> 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進行了遮擋,大家可以根據自己的進行修改。

獲取到的access_token效果圖:

服務通知顯示效果:

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章