我的小程序之旅八:基於weixin-java-mp實現微信公眾號被動回復消息


在微信里有這樣一個公眾號【華為運動健康】,當點擊最新排行的時候,公眾號就會發送今天最新的運動步數給你。如下圖:

這里有兩種格式的消息

1、有頭像框,有聊天框——普通消息

2、消息有樣式、顏色等——模板消息

本篇文章主要介紹的就是如何讓微信公眾號自動回復消息

參考文檔鏈接:https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Passive_user_reply_message.html

 

開發之前,給大家介紹一個weixin-java-tools

簡單介紹一下:

1、微信各種平台的api它都集成了,直接調用就行,不用自己維護微信官方url、各種常量、accessToken等;

2、微信那艹蛋的xml返回數據格式可以直接使用對象進行構建,它已經做好了封裝;

3、WxMpMessageRouter非常好用,將微信的各種事件進行分發處理,示例如下:

maven引入:

<!-- weixin-java-mp SDK框架-->
<dependency>
    <groupId>com.github.binarywang</groupId>
    <artifactId>weixin-java-mp</artifactId>
    <version>3.6.0</version>
</dependency>

 

github地址如下:https://github.com/binarywang/weixin-java-mp-demo

wiki文檔如下:https://github.com/Wechat-Group/WxJava/wiki

SpringBoot-WeChat示例參考項目:https://github.com/Thinkingcao/SpringBootLearning/tree/master/springboot-wechat 

一、登錄微信公眾平台,啟用服務器配置

1、登錄https://mp.weixin.qq.com,選擇公眾號進入,在設置與開發—基本配置

2、記下開發者AppId和開發者秘鑰,IP白名單待會再說

3、點擊修改配置,配置服務器地址,輸入令牌生成加解密秘鑰,選擇安全模式點擊保存

4、上面配置的服務器地址,就是微信回調我們服務器的回調地址,由於微信的數據格式是xml,所以我們需要在接口上進行一些處理

代碼如下:

@RequestMapping(value = "handleWxEvent", method = RequestMethod.POST, produces = "application/xml; charset=UTF-8")
@ResponseBody
public String handleWxEvent(@RequestBody String requestBody,
                                @RequestParam("signature") String signature,
                                @RequestParam("timestamp") String timestamp,
                                @RequestParam("nonce") String nonce,
                                @RequestParam("openid") String openid,
                                @RequestParam(name = "encrypt_type", required = false) String encType,
                                @RequestParam(name = "msg_signature", required = false) String msgSignature){
  //微信加密簽名
        String signature = request.getParameter("signature");
        //時間戳
        String timestamp = request.getParameter("timestamp");
        //隨機數
        String nonce = request.getParameter("nonce");
        //隨機字符串
        String echostr = request.getParameter("echostr");
        //接入驗證
        if (checkSignature(signature, timestamp, nonce, gzhToken)) {
            log.info("微信公眾號校驗完成echostr:[{}]", echostr);
            try {
         //這樣寫可以防止服務器給返回的字符串加上雙引號,導致驗證失敗 response.getWriter().print(echostr); }
catch (IOException e) { log.error("輸出返回值異常", e); } return; } throw new DscException(ErrorCodeEnum.SYSTEM_EXCEPTION, "解析簽名發生異常"); } /** * 校驗簽名 * * @param signature 簽名 * @param timestamp 時間戳 * @param nonce 隨機數 * @return 布爾值 */ public static boolean checkSignature(String signature, String timestamp, String nonce, String token) { 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.toString().getBytes()); checkText = byteToStr(digest); } catch (Exception e) { log.error("解碼發生異常", e); } } //將加密后的字符串與signature進行對比 return checkText != null ? checkText.equals(signature.toUpperCase()) : false; }

二、配置公眾號信息,實例化WxMpService、WxMpMessageRouter

WxMpService功能很多如驗證消息的確來自微信服務器、獲取access_token、進行相應的公眾號切換...

WxMpMessageRouter:微信消息路由器,通過代碼化的配置,把來自微信的消息交給handler處理

說明:

   1. 配置路由規則時要按照從細到粗的原則,否則可能消息可能會被提前處理

   2. 默認情況下消息只會被處理一次,除非使用 WxMpMessageRouterRule.next()

   3. 規則的結束必須用WxMpMessageRouterRule.end()或者WxMpMessageRouterRule.next(),否則不會生效

1、在application.properties或者application.yml配置好公眾號的相關信息

 

2、wxmp包如下

WxMpConfig.class

import com.google.common.collect.Maps;
import me.chanjar.weixin.common.api.WxConsts;
import me.chanjar.weixin.mp.api.WxMpMessageRouter;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.api.impl.WxMpServiceImpl;
import me.chanjar.weixin.mp.config.WxMpConfigStorage;
import me.chanjar.weixin.mp.config.impl.WxMpDefaultConfigImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.Map;

import static me.chanjar.weixin.common.api.WxConsts.EventType.SUBSCRIBE;
import static me.chanjar.weixin.common.api.WxConsts.EventType.UNSUBSCRIBE;
import static me.chanjar.weixin.common.api.WxConsts.XmlMsgType.EVENT;
import static me.chanjar.weixin.mp.constant.WxMpEventConstants.CustomerService.*;
import static me.chanjar.weixin.mp.constant.WxMpEventConstants.POI_CHECK_NOTIFY;

@Configuration public class WxMpConfig { /** * 設置微信公眾號的appid */ @Value("${gzh.appId}") private String appId; /** * 設置微信公眾號的app secret */ @Value("${gzh.appSecret}") private String secret; /** * 設置微信公眾號的token */ @Value("${gzh.token}") private String token; /** * 設置微信公眾號的EncodingAESKey */ @Value("${gzh.aesKey}") private String aesKey; /** * 日志處理 */ @Autowired private LogHandler logHandler; @Autowired private NullHandler nullHandler; @Autowired private KfSessionHandler kfSessionHandler; @Autowired private StoreCheckNotifyHandler storeCheckNotifyHandler; @Autowired private LocationHandler locationHandler; @Autowired private MenuHandler menuHandler; @Autowired private MsgHandler msgHandler; @Autowired private UnsubscribeHandler unsubscribeHandler; @Autowired private SubscribeHandler subscribeHandler; @Autowired private ScanHandler scanHandler; @Autowired private TextMsgHandler textMsgHandler; @Autowired private ImgHandler imgHandler; @Bean("wxMpService") public WxMpService wxMpService() { WxMpDefaultConfigImpl wxMpDefaultConfig = new WxMpDefaultConfigImpl(); wxMpDefaultConfig.setAppId(appId); wxMpDefaultConfig.setSecret(secret); wxMpDefaultConfig.setToken(token); wxMpDefaultConfig.setAesKey(aesKey); Map<String, WxMpConfigStorage> configMap = Maps.newHashMap(); configMap.put(appId,wxMpDefaultConfig); WxMpService service = new WxMpServiceImpl(); service.setMultiConfigStorages(configMap); return service; } @Bean public WxMpMessageRouter messageRouter(WxMpService wxMpService) { final WxMpMessageRouter newRouter = new WxMpMessageRouter(wxMpService); // 記錄所有事件的日志 (異步執行) newRouter.rule().handler(this.logHandler).next(); // 接收客服會話管理事件 newRouter.rule().async(false).msgType(EVENT).event(KF_CREATE_SESSION) .handler(this.kfSessionHandler).end(); newRouter.rule().async(false).msgType(EVENT).event(KF_CLOSE_SESSION) .handler(this.kfSessionHandler).end(); newRouter.rule().async(false).msgType(EVENT).event(KF_SWITCH_SESSION) .handler(this.kfSessionHandler).end(); // 門店審核事件 newRouter.rule().async(false).msgType(EVENT).event(POI_CHECK_NOTIFY).handler(this.storeCheckNotifyHandler).end(); // 自定義菜單事件 newRouter.rule().async(false).msgType(EVENT).event(WxConsts.EventType.CLICK).handler(this.menuHandler).end(); // 點擊菜單連接事件 newRouter.rule().async(false).msgType(EVENT).event(WxConsts.EventType.VIEW).handler(this.nullHandler).end(); // 關注事件 newRouter.rule().async(false).msgType(EVENT).event(SUBSCRIBE).handler(this.subscribeHandler).end(); // 取消關注事件 newRouter.rule().async(false).msgType(EVENT).event(UNSUBSCRIBE).handler(this.unsubscribeHandler).end(); // 上報地理位置事件 newRouter.rule().async(false).msgType(EVENT).event(WxConsts.EventType.LOCATION).handler(this.locationHandler).end(); // 接收地理位置消息 newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.LOCATION).handler(this.locationHandler).end(); // 掃碼事件 newRouter.rule().async(false).msgType(EVENT).event(WxConsts.EventType.SCAN).handler(this.scanHandler).end(); // 文本消息處理 newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.TEXT).handler(this.textMsgHandler).end(); // 圖片消息處理 newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.IMAGE).handler(this.imgHandler).end(); // 默認 newRouter.rule().async(false).handler(this.msgHandler).end(); return newRouter; } }

3、各個handler如下

需要注意的是:

(1)消息回復的類型有文本、圖片、圖文、語音、視頻、音樂等;

(2)圖片、視頻等消息都需要先上傳到素材庫並獲取該素材的mediaId;

(3)回復的消息還可以是小程序卡片,但需要保證當前小程序必須綁定在該公眾號下

(4)假如服務器無法保證在五秒內處理並回復,也必須回復,這樣微信服務器才不會對此作任何處理,並且不會發起重試(這種情況下,可以使用客服消息接口進行異步回復),否則,將出現嚴重的錯誤提示

三、服務器處理微信回調事件,並進行分發

import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.mp.api.WxMpMessageRouter;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage;
import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.bind.annotation.*;


@Slf4j
@AllArgsConstructor
@RestControllerpublic class WxPortalController {
    private final WxMpService wxService;
    private final WxMpMessageRouter messageRouter;

    @PostMapping(value="handleWxEvent",produces = "application/xml; charset=UTF-8")
    public String handleWxEvent(@PathVariable String appid,
                       @RequestBody String requestBody,
                       @RequestParam("signature") String signature,
                       @RequestParam("timestamp") String timestamp,
                       @RequestParam("nonce") String nonce,
                       @RequestParam("openid") String openid,
                       @RequestParam(name = "encrypt_type", required = false) String encType,
                       @RequestParam(name = "msg_signature", required = false) String msgSignature) {
        log.info("\n接收微信請求:[openid=[{}], [signature=[{}], encType=[{}], msgSignature=[{}],"
                + " timestamp=[{}], nonce=[{}], requestBody=[\n{}\n] ",
            openid, signature, encType, msgSignature, timestamp, nonce, requestBody);
if (!wxService.checkSignature(timestamp, nonce, signature)) {
            throw new IllegalArgumentException("非法請求,可能屬於偽造的請求!");
        }

        String out = null;
        if (encType == null) {
            // 明文傳輸的消息
            WxMpXmlMessage inMessage = WxMpXmlMessage.fromXml(requestBody);
            WxMpXmlOutMessage outMessage = this.route(inMessage);
            if (outMessage == null) {
                return "";
            }

            out = outMessage.toXml();
        } else if ("aes".equalsIgnoreCase(encType)) {
            // aes加密的消息
            WxMpXmlMessage inMessage = WxMpXmlMessage.fromEncryptedXml(requestBody, wxService.getWxMpConfigStorage(),
                timestamp, nonce, msgSignature);
            log.debug("\n消息解密后內容為:\n{} ", inMessage.toString());
            WxMpXmlOutMessage outMessage = this.route(inMessage);
            if (outMessage == null) {
                return "";
            }
            out = outMessage.toEncryptedXml(wxService.getWxMpConfigStorage());
        }

        log.debug("\n組裝回復信息:{}", out);
        return out;
    }

    private WxMpXmlOutMessage route(WxMpXmlMessage message) {
        try {
            return this.messageRouter.route(message);
        } catch (Exception e) {
            log.error("路由消息時出現異常!", e);
        }

        return null;
    }

}

四、構建回復消息

1、關注公眾號回復歡迎用戶

 這里我們可以看到,關注【鄉榮】公眾號后,不僅回復了信息,而且是兩條。實現思路:

(1)第一條消息,當用戶訂閱公眾號時會調用SubscribeHandler,該handler會返回一個WxMpXmlOutMessage,這個就是默認返回值

(2)第二條消息,我們可以使用【微信客服】發送,直接使用下面的方法進行發送

wxService.getKefuService().sendKefuMessage(WxMpKefuMessage
                    .TEXT()
                    .toUser(wxMessage.getFromUser())
                    .content("客服回復內容")
                    .build()); 

 2、回復小程序卡片消息 

 

 

回復小程序卡片這個功能在微信公眾平台是找不到的,這個功能只能使用代碼實現,這里就體現出代碼的強大了💪。

如上圖所示:當我們輸入“最新直播”時,小程序會返回當前最新直播的小程序卡片,點擊即可進入小程序。實現思路:

(1)用戶輸入的是文本消息,處理應該在TextMsgHandler中

(2)默認返回的WxMpXmlOutMessage是沒有小程序卡片這個類型的,所以這里還得借助客服消息

(3)小程序卡片需要一個封面,且這個封面圖必須上傳到微信素材庫后獲取mediaId,上傳代碼如下:

//獲取素材庫相關實現
WxMpMaterialService materialService = wpService.getMaterialService();
//上傳臨時素材
WxMediaUploadResult wxMediaUploadResult = materialService.mediaUpload(WxConsts.KefuMsgType.IMAGE, ".jpg", inputStream);
//獲取素材ID
String mediaId = wxMediaUploadResult.getMediaId();

(4)構建小程序卡片信息

 wpService.getKefuService().sendKefuMessage(WxMpKefuMessage
                    .MINIPROGRAMPAGE()
                    .title("小程序卡片標題")
                    .toUser(wxMessage.getFromUser())
                    .thumbMediaId(mediaId)
                    .appId("小程序ID")
                    .pagePath("卡片要跳轉的路徑").build());
   

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM