【最全最详细】微信第三方平台开发 —— 接收公众号事件/消息

1. 整体流程

请添加图片描述

2. 成为服务商

2.1. 首先打开微信开放平台,注册并登录你的账号

微信开放平台地址:https://open.weixin.qq.com/
在这里插入图片描述

2.2. 进行开发者资质认证

需要花300开这个认证。
在这里插入图片描述

2.3. 创建第三方平台并提交审核

(1)创建第三方平台

在这里插入图片描述
(2)按照要求填写相关信息
我这里用自己的服务器,开发模式选择传统模式

在这里插入图片描述
(3)提交审核
回到 管理中心 - 第三方平台,可以看到你创建的第三方平台处于未审核状态
点击详情,检查下信息有没有问题,直接提审就行

在这里插入图片描述

在这里插入图片描述

3. 搭建第三方平台后端服务

3.1. 管理员设置 和 权限集设置

可以在提审期间,设置一下平台管理员和权限集

首先设置一下平台管理员:记得实名认证一下
在这里插入图片描述

设置一下权限集:你可以全选所有权限,或者用到什么功能选什么功能
在这里插入图片描述
在这里插入图片描述

3.2. 开发资料设置

这里有几点要注意:

  • 授权事件接收配置、消息与事件接收配置、授权发起页面域名,这三项的 一级域名 必须相同
  • 消息与事件接收配置的路径中必须有$APPID$
  • 授权发起页面域名不写http/https(除此外这里如果你测试的时候可以配置局域网ip,暂时按下不表,后文细说)
  • 白名单IP无所谓,随便写写就行,没有影响
  • 消息加密token 和 消息加解密key 按照长度要求自己生成就行
    在这里插入图片描述
    在这里插入图片描述
    然后点击生成一下AppSecret:
    在这里插入图片描述

3.3. 进行商家授权操作

总体的流程即:

  1. 首先接收到第三方平台发给你服务器的ticket(授权事件接收配置的接口)
  2. 拿着ticket发送请求去获取第三方平台调度凭证token
  3. 拿着token去获取预授权码
  4. 拿着预授权码、token、授权回调接口url去生成一条授权链
    • 这里的授权回调接口对接收事件/消息没有多少影响,只是你可以通过整个接口来获取到授权的公众号的相关信息
  5. 商家点击嵌在 授权发起页域名 下的网页中的授权链,跳转到扫码授权页
  6. 商家扫码授权

3.3.0. 加密解密操作

这是获取的前置条件,ticket的获取,事件/消息的获取,接收到的都是加密的数据,你需要将其进行解密操作。
官方文档:https://developers.weixin.qq.com/doc/oplatform/Third-party_Platforms/2.0/api/Before_Develop/Technical_Plan.html

首先要下载相关的加密解密工具类:
在这里插入图片描述
解压后,你可以先看看他文件中引用的jar包,去mvn仓库找依赖引入,然后把他的所有工具都复制粘贴到你的目录中:

在这里插入图片描述
加密解密的方法如下:其中需要的参数,除了你平台上固定的配置外,就是平台发送请求中获取到的参数,不用着急,下文中有使用讲解:

	// 加密操作
    public String encryptMsg(String encodingAesKey, String token, String timestamp, String nonce, String appId, String replyMsg) {
        log.info("加密前:{}", replyMsg);
        try {
            WXBizMsgCrypt pc = new WXBizMsgCrypt(token, encodingAesKey, appId);
            String ciphertext = pc.encryptMsg(replyMsg, timestamp, nonce);
            log.info("加密后:{}", ciphertext);
            return ciphertext;
        } catch (AesException e) {
            throw new RuntimeException(e);
        }
    }

    //解密操作 
    public String decryptMsg(String timestamp, String nonce, String signature, String encrypt) {
        log.info("进行解密操作:{} {} {} {}", timestamp, nonce, signature, encrypt);
        try {
            log.info("token:{} encodingAesKey:{} componentAppId:{}", token, encodingAesKey, componentAppId);
            WXBizMsgCrypt pc = new WXBizMsgCrypt(token, encodingAesKey, componentAppId);
            String format = "<xml><ToUserName><![CDATA[toUser]]></ToUserName><Encrypt><![CDATA[%1$s]]></Encrypt></xml>";
            String fromXML = String.format(format, encrypt);
            log.info("fromXML:{}", fromXML);
            // 第三方收到公众号平台发送的消息
            String plaintext = pc.decryptMsg(signature, timestamp, nonce, fromXML);
            log.info("解密后明文: {}", plaintext);
            return plaintext;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

还有就是一个常量类:


public interface VXConstants {

    //---------------- Redis Key ------------------------

    // 票据ticket
    String COMPONENT_VERIFY_TICKET = "vx:component:ticket";
    // 第三方token令牌
    String COMPONENT_ACCESS_TOKEN = "vx:component:token";
    // 第三方pre_auth_code
    String COMPONENT_PRE_AUTH_CODE = "vx:component:preAuthCode";
    // 公众号授权信息
    String GZH_AUTH_MSG = "gzh:msg:";


    //---------------- URL ------------------------

    // 获取第三方平台接口的调用凭据(令牌)url
    String API_COMPONENT_TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/component/api_component_token";

    // 获取预授权码url
    String API_CREATE_PRE_AUTH_CODE_URL = "https://api.weixin.qq.com/cgi-bin/component/api_create_preauthcode?component_access_token=";

    // 使用授权码获取授权信息url
    String API_QUERY_AUTH_URL = "https://api.weixin.qq.com/cgi-bin/component/api_query_auth?component_access_token=";

    // 获取授权账号信息url
    String API_GET_AUTHORIZER_INFO_URL = "https://api.weixin.qq.com/cgi-bin/component/api_get_authorizer_info?component_access_token=";

    // 获取/刷新接口调用令牌url
    String API_AUTHORIZER_TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/component/api_authorizer_token?component_access_token=";

    // 授权回调url
    String AUTH_CALLBACK_URL = "http://192.168.20.217:29999/admin/vx/auth/callback";
}

3.3.1. 获取第三方平台发送至服务器的ticket

官方文档链接:https://developers.weixin.qq.com/doc/oplatform/Third-party_Platforms/2.0/api/Before_Develop/component_verify_ticket.html

就是你在下图整个位置配置的接口:
在这里插入图片描述
在第三方平台创建审核通过后,微信服务器会向其 ”授权事件接收URL” 每隔 10 分钟POST 的方式推送 component_verify_ticket有效期12小时
要注意一点,这个请求的参数是 加!密!的! 所以就需要加密解密操作。

详情请看代码和注释

代码如下:
controller层

    @PostMapping("/auth/event")
    public String componentVerifyTicket(HttpServletRequest req, HttpServletResponse resp) {
        log.info("接收到验证票据请求");
        return vxService.getComponentVerifyTicket(req, resp);
    }

service层

	// 存储开放平台appid对应的ticket
    public static final Map<String, String> COMPONENT_VETIFY_TICKET_MAP = new HashMap<>();
    @Resource
	private StringRedisTemplate redisTemplate;

	@Override
    public String getComponentVerifyTicket(HttpServletRequest request, HttpServletResponse response) {
        log.info("接收微信服务器发送的Ticket");
        try {
            request.setCharacterEncoding("UTF-8");
            response.setCharacterEncoding("UTF-8");
            // 微信加密签名
            String signature = request.getParameter("msg_signature");
            // 时间戳
            String timestamp = request.getParameter("timestamp");
            // 随机数
            String nonce = request.getParameter("nonce");
            // 从请求中读取整个post数据 这个工具类在下文写了
            Map<String, Object> xmlMap = MessageUtil.xmlToMap(request);
            log.info("加密签名msg_signature:{} 时间戳timestamp:{} 随机数nonce:{}", signature, timestamp, nonce);
            log.info("从request中获取xml信息:{}", xmlMap);
            //解密处理
            String encrypt = (String) xmlMap.get("Encrypt");
            log.info("Encrypt:{}", encrypt);
            String msg = decryptMsg(timestamp, nonce, signature, encrypt);
            log.info("msg:{}", msg);
            //将XML格式字符串转为Map类型 使用的是hutool工具包,记得引入一下
            Map<String, Object> msgMap = XmlUtil.xmlToMap(msg);
            String infoType = msgMap.get("InfoType").toString();
            log.info("类型:{}", infoType);
            switch (infoType) {
                //验证票据
                case "component_verify_ticket":
                    //查询库中的第三方信息,并且准备存储ticket
                    String componentVerifyTicket = msgMap.get("ComponentVerifyTicket").toString();
                    log.info("用户授权component_verify_ticket:{}", componentVerifyTicket);
                    COMPONENT_VETIFY_TICKET_MAP.put(componentAppId, componentVerifyTicket);
                    //使用StringRedisTemplate将票据值写入Redis缓存中 存不存,怎么存看你自己
                    redisTemplate.opsForValue().set(VXConstants.COMPONENT_VERIFY_TICKET, componentVerifyTicket);
                    redisTemplate.expire(VXConstants.COMPONENT_VERIFY_TICKET, 2, TimeUnit.HOURS);
                    break;
                case "unauthorized"://用户取消授权
                    log.info("用户取消授权");
                    break;
            }
        } catch (Exception e) {
            log.error("获取Ticket失败:", e);
        }
        return "success";
    }

util工具类,你可以只把用到的方法复制过去:

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class MessageUtil {
    // + MsgType 	消息类型,文本为 text
    public static final String MESSAGE_TEXT = "text";
    // + MsgType 	消息类型,图片为 image
    public static final String MESSAGE_IMAGE = "image";
    // + MsgType 	语音为 voice
    public static final String MESSAGE_VOICE = "voice";
    // + MsgType 	视频为 video
    public static final String MESSAGE_VIDEO = "video";
    // + MsgType 	小视频为 shortvideo
    public static final String MESSAGE_SHORTVIDEO = "shortvideo";
    // + MsgType 	消息类型,地理位置为 location
    public static final String MESSAGE_LOCATION = "location";
    // + MsgType 	消息类型,链接为 link
    public static final String MESSAGE_LINK = "link";
    // + MsgType 	消息类型,event
    public static final String MESSAGE_EVENT = "event";
    // > Event 	事件类型,subscribe(订阅)、unsubscribe(取消订阅)
    //Event 	事件类型,SCAN
    //Event 	事件类型,LOCATION
    //Event 	事件类型,CLICK
    //Event 	事件类型,VIEW
    public static final String MESSAGE_SUBSCRIBE_EVENT = "subscribe";
    public static final String MESSAGE_UNSUBSCRIBE_EVENT = "unsubscribe";
    public static final String MESSAGE_SCAN_EVENT = "SCAN";
    public static final String MESSAGE_LOCATION_EVENT = "LOCATION";
    public static final String MESSAGE_CLICK_EVENT = "CLICK";
    public static final String MESSAGE_VIEW_EVENT = "VIEW";

    /**
     * xml 转集合。
     *
     * @param request
     * @return
     * @throws IOException
     * @throws DocumentException
     */
    public static Map<String, Object> xmlToMap(HttpServletRequest request) throws IOException, DocumentException {
        Map<String, Object> map = new HashMap<>();
        SAXReader saxReader = new SAXReader();

        // 从 request 中获取输入流。
        ServletInputStream inputStream = request.getInputStream();
        Document document = saxReader.read(inputStream);

        // 获取 xml 根元素。
        Element rootElement = document.getRootElement();
        // 每一元素放入 list 中。
        List<Element> list = rootElement.elements();
        for (Element element : list) {
            map.put(element.getName(), element.getText());
        }

        inputStream.close();
        return map;
    }
}

3.3.2. 获取第三方平台的token令牌

这一步只是一个获取token令牌的java方法,获取后用于其他操作

官方文档:https://developers.weixin.qq.com/doc/oplatform/Third-party_Platforms/2.0/api/ThirdParty/token/component_access_token.html

下文中用到的componentAppId, componentAppSecret,即你在第三方平台配置的数据:
在这里插入图片描述

	private static final String API_COMPONENT_TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/component/api_component_token";
	/**
     * 获取第三方平台接口的调度凭据 - token令牌
     *
     * @return
     */
    public String getApiComponentToken() {
        try {
            //从redis中拿到token令牌
            String componentToken = redisTemplate.opsForValue().get(VXConstants.COMPONENT_ACCESS_TOKEN);
            if (StringUtils.isNotBlank(componentToken)) {//如果拿到直接返回
                COMPONENT_ACCESS_TOKEN = componentToken;
                return componentToken;
            }
            //重新生成token令牌
            if (StringUtils.isAnyBlank(componentAppId, componentAppSecret)) {
                log.error("获取token失败:{} {}", componentAppId, componentAppSecret);
                return null;
            }
            //尝试从redis中拿到ticket
            String componentVerifyTicket = redisTemplate.opsForValue().get(VXConstants.COMPONENT_VERIFY_TICKET);
            if (StrUtil.isBlank(componentVerifyTicket)) {//如果拿不到尝试从内存中拿
                componentVerifyTicket = COMPONENT_VETIFY_TICKET_MAP.get(componentAppId);
            }
            if (StringUtils.isBlank(componentVerifyTicket)) {
                return null;
            }
            //构建请求对象
            HashMap<String, Object> paramMap = new HashMap<>();
            paramMap.put("component_appid", componentAppId);
            paramMap.put("component_appsecret", componentAppSecret);
            paramMap.put("component_verify_ticket", componentVerifyTicket);
            //发送请求 使用的hutool中的工具,路径可以看开头的API_COMPONENT_TOKEN_URL,我放在常量类中了
            String post = HttpUtil.post(VXConstants.API_COMPONENT_TOKEN_URL, gson.toJson(paramMap));
            log.info("获取令牌:{}", post);
            //{"expires_in":7200}
            Map<String, Object> tokenMap = gson.fromJson(post, new TypeToken<Map<String, Object>>() {
            }.getType());
            //token
            String componentAccessToken = String.valueOf(tokenMap.get("component_access_token"));
            //有效期
            Double expiresIn = (Double) tokenMap.get("expires_in");
            if (StringUtils.isNotBlank(componentAccessToken)) {//如果能拿到token,则存入redis和内存中
                COMPONENT_ACCESS_TOKEN = componentAccessToken;
                redisTemplate.opsForValue().set(VXConstants.COMPONENT_ACCESS_TOKEN, componentAccessToken);
                redisTemplate.expire(VXConstants.COMPONENT_ACCESS_TOKEN, expiresIn.intValue(), TimeUnit.SECONDS);
            }
            return componentAccessToken;
        } catch (Exception e) {
            log.error("获取第三方平台token失败:", e);
        }
        return null;
    }

3.3.3. 获取预授权码

这一步同样只是一个获取预授权码的java方法,获取后用于其他操作,这一步需要用到3.3.2. 生成的token作为参数传入

官方文档:https://developers.weixin.qq.com/doc/oplatform/Third-party_Platforms/2.0/api/ThirdParty/token/pre_auth_code.html

	// 第三方平台的预授权码
    public static String COMPONENT_PRE_AUTH_CODE;
     // 获取预授权码url
    private static final String API_CREATE_PRE_AUTH_CODE_URL = "https://api.weixin.qq.com/cgi-bin/component/api_create_preauthcode?component_access_token=";
	//获取预授权码
    public String getApiCreatePreAuthCode(String componentAccessToken) {
        try {
            // 先查预授权码
            String preAuthCode = redisTemplate.opsForValue().get(VXConstants.COMPONENT_PRE_AUTH_CODE);
            if (StringUtils.isNotBlank(preAuthCode)) {
                COMPONENT_PRE_AUTH_CODE = preAuthCode;
                return preAuthCode;
            }
            if (StringUtils.isAnyBlank(componentAccessToken, componentAppId)) {
                log.info("参数为空:{} {}", componentAccessToken, componentAppId);
                return null;
            }
            //这个路径我也写在常量中了了,你可以去上面直接找这个路径
            String url = VXConstants.API_CREATE_PRE_AUTH_CODE_URL + componentAccessToken;
            HashMap<String, Object> paramMap = new HashMap<>();
            paramMap.put("component_appid", componentAppId);
            String post = HttpUtil.post(url, gson.toJson(paramMap));
            log.info("获取预授权码:{}", post);
            Map<String, Object> tokenMap = gson.fromJson(post, new TypeToken<Map<String, Object>>() {
            }.getType());
            //token
            preAuthCode = String.valueOf(tokenMap.get("pre_auth_code"));
            //有效期
            Double expiresIn = (Double) tokenMap.get("expires_in");
            if (StringUtils.isNotBlank(preAuthCode)) {//存入redis
                COMPONENT_PRE_AUTH_CODE = preAuthCode;
                redisTemplate.opsForValue().set(VXConstants.COMPONENT_PRE_AUTH_CODE, preAuthCode);
                redisTemplate.expire(VXConstants.COMPONENT_PRE_AUTH_CODE, expiresIn.intValue(), TimeUnit.SECONDS);
                return preAuthCode;
            }
        } catch (Exception e) {
            log.error("获取预授权码失败:", e);
        }
        return null;
    }

3.3.4. 生成授权链

这一步是生成的,在你授权发起页域名下的网站中嵌入,可以嵌入到一个<a hrel="授权链" \>标签中,然后点击跳转到一个有授权二维码的页面,交给公众号管理员扫码授权

官方文档:https://developers.weixin.qq.com/doc/oplatform/Third-party_Platforms/2.0/api/Before_Develop/Authorization_Process_Technical_Description.html

注:授权发起页域名你可以临时改成你本地测试/局域网测试的ip或域名,如192.168.20.212来进行唤起测试,当然,只是测试,改的时候,建议 参数授权回调链接授权发起页域名授权事件接收配置消息接收与发送配置都改成相同的。

controller层

	//实际上这个是你自己的网站中的前端调用的,随便写,只要能拿到授权链就行
	@PostMapping("/auth/buildChainUrl")
    public R<String> buildAuthChainUrl() {
        log.info("获取授权链");
        return R.ok(vxService.buildAuthChainUrl());
    }

这一步用到了3.3.2生成的token 和3.3.3生成的预授权码
service层

	@Override
    public String buildAuthChainUrl() {
        // token
        String apiComponentToken = getApiComponentToken();
        log.info("apiComponentToken:{}", apiComponentToken);
        // 预授权码
        String apiCreatePreAuthCode = getApiCreatePreAuthCode(apiComponentToken);
        log.info("apiCreatePreAuthCode:{}", apiCreatePreAuthCode);
        //VXConstants.AUTH_CALLBACK_URL 即你自定义的授权回调地址,见下文
        String url = "https://mp.weixin.qq.com/cgi-bin/componentloginpage?component_appid=" + componentAppId
                + "&pre_auth_code=" + apiCreatePreAuthCode + "&redirect_uri=" + VXConstants.AUTH_CALLBACK_URL
                + "&auth_type=3";
        return url;
    }
3.3.4.0. 下面的3.3.4.* 的子项,对业务影响不大,只是你可能会用到,但是我只获取事件没怎么用到
3.3.4.1. 授权回调

如果商家扫码授权,那么就会给这个回调发送请求。如果到了这一步,那么就意味着授权操作已经结束了,这是授权结束后的操作。
如果你需要进行其他的操作,可以通过这个回调来获取到商家公众号的相关信息,如公众号token、公众号refresh_token、公众号appid等
但是对我们来说,如果只是需要获取关注取关事件、获取到消息,那么接收到的数据也可以不存

controller层

	@GetMapping("/auth/callback")
    public void authCallBack(HttpServletRequest request, HttpServletResponse response) {
        log.info("接收到授权回调请求");
        vxService.authCallBack(request, response);
    }

service层

 	// 授权回调
    @Override
    public void authCallBack(HttpServletRequest request, HttpServletResponse response) {
        //授权回调
        try {
            //授权码
            String authorizationCode = request.getParameter("auth_code");
            Integer expiresIn = Integer.valueOf(request.getParameter("expires_in"));
            //三方AccessToken 见3.3.2
            String apiComponentToken = getApiComponentToken();
            //根据授权码获取授权信息 这个方法见下文3.3.4.2
            Map<String, Object> authorizationInformation = getAuthorizationInformation(apiComponentToken, authorizationCode);

        } catch (Exception e) {
            log.error("授权回调出现错误:", e);
        }
    }
3.3.4.2. 通过授权码获取授权信息

当然你可以通过 3.3.4.1. 回调中拿到的auth_code授权码,来获取公众号的授权信息的操作
官方文档:https://developers.weixin.qq.com/doc/oplatform/Third-party_Platforms/2.0/api/ThirdParty/token/authorization_info.html

service层

	//根据授权码获取授权信息
	private static final String API_QUERY_AUTH_URL = "https://api.weixin.qq.com/cgi-bin/component/api_query_auth?component_access_token=";
	// 授权信息
    public Map<String, Object> getAuthorizationInformation(String componentAccessToken, String authorizationCode) {
        log.info("根据授权码获取授权信息");
        try {
            if (StringUtils.isAnyBlank(componentAccessToken, componentAppId, authorizationCode)) {
                return null;
            }
            //我存入常量中了,路径看上文API_QUERY_AUTH_URL 
            String url = VXConstants.API_QUERY_AUTH_URL + componentAccessToken;
            HashMap<String, Object> paramMap = new HashMap<>();
            paramMap.put("component_access_token", componentAccessToken);
            paramMap.put("component_appid", componentAppId);
            paramMap.put("authorization_code", authorizationCode);
            String post = HttpUtil.post(url, gson.toJson(paramMap));
            log.info("授权信息:{}", post);
            Map<String, Object> informationMap = gson.fromJson(post, new TypeToken<Map<String, Object>>() {
            }.getType());

            Map<String, Object> authorizationInfo = (Map<String, Object>) informationMap.get("authorization_info");
            if (authorizationInfo == null) {
                return null;
            }
            String authorizerAppid = (String) authorizationInfo.get("authorizer_appid");
            String authorizerAccessToken = (String) authorizationInfo.get("authorizer_access_token");
            Double expiresIn = (Double) authorizationInfo.get("expires_in");
            String authorizerRefreshToken = (String) authorizationInfo.get("authorizer_refresh_token");

            //将授权信息存入redis
            String gzhAuthMsgKey = VXConstants.GZH_AUTH_MSG + authorizerAppid;
            redisTemplate.opsForValue().set(gzhAuthMsgKey, post);
            redisTemplate.expire(gzhAuthMsgKey, expiresIn.intValue(), TimeUnit.SECONDS);

            log.info("authorizationInfo:{}", authorizationInfo);
            return authorizationInfo;
        } catch (Exception e) {
            log.error("根据授权码获取授权信息失败", e);
        }
        return null;
    }
3.3.4.3. 通过授权公众号的access_token获取授权信息

也可以通过拿到的公众号的access_token来获取授权信息

官方文档:https://developers.weixin.qq.com/doc/oplatform/openApi/OpenApiDoc/authorization-management/getAuthorizerInfo.html

	// 获取授权账号信息url
    private static final String API_GET_AUTHORIZER_INFO_URL = "https://api.weixin.qq.com/cgi-bin/component/api_get_authorizer_info?component_access_token=";


public Map<String, Object> getAuthorizerInfo(String componentAccessToken, String authorizerAppid) {
        log.info("获取授权账号详情");
        if (StringUtils.isAnyBlank(componentAccessToken, componentAppId, authorizerAppid)) {
            return null;
        }
        //我存入常量中了,路径看上文API_GET_AUTHORIZER_INFO_URL 
        String url = VXConstants.API_GET_AUTHORIZER_INFO_URL + componentAccessToken;
        HashMap<String, Object> paramMap = new HashMap<>();
        paramMap.put("component_appid", componentAppId);
        paramMap.put("authorizer_appid", authorizerAppid);
        String post = HttpUtil.post(url, gson.toJson(paramMap));
        log.info("授权账号信息:{}", post);
        Map<String, Object> authorizerInfoMap = gson.fromJson(post, new TypeToken<Map<String, Object>>() {
        }.getType());
        //授权信息
        Map<String, Object> authorizationInfo = (Map<String, Object>) authorizerInfoMap.get("authorization_info");
        //公众号信息
        Map<String, Object> authorizerInfo = (Map<String, Object>) authorizerInfoMap.get("authorizer_info");
        return authorizerInfoMap;
    }
3.3.4.4. 去维护刷新这个公众号的access_token ,每两个小时或者一个半小时刷新一次

怎么存怎么刷新怎么维护都是你自己去想的了
官方文档:https://developers.weixin.qq.com/doc/oplatform/Third-party_Platforms/2.0/api/ThirdParty/token/api_authorizer_token.html

	// 获取/刷新接口调用令牌url
   	private static  String API_AUTHORIZER_TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/component/api_authorizer_token?component_access_token=";

	//获取/刷新token
    public void getAndRefreshAuthorizerToken(String componentAccessToken,
                                             String authorizerAppid, String authorizerRefreshToken) {
        log.info("执行刷新token逻辑");
        if (StringUtils.isAnyBlank(componentAccessToken, componentAppId, authorizerAppid, authorizerRefreshToken)) {
            return;
        }
        String url = VXConstants.API_AUTHORIZER_TOKEN_URL + componentAccessToken;
        HashMap<String, Object> paramMap = new HashMap<>();
        paramMap.put("component_appid", componentAppId);
        paramMap.put("authorizer_appid", authorizerAppid);
        paramMap.put("authorizer_refresh_token", authorizerRefreshToken);
        String post = HttpUtil.post(url, gson.toJson(paramMap));
        log.info("刷新token结果:{}", post);
        Map<String, Object> resultMap = gson.fromJson(post, new TypeToken<Map<String, Object>>() {
        }.getType());
        String accessToken = (String) resultMap.get("authorizer_access_token");
        Double expiresIn = (Double) resultMap.get("expires_in");
        String refreshToken = (String) resultMap.get("authorizer_refresh_token");
        //todo 存在哪里,怎么存怎么刷新
    }

3.3.5. 客户扫码授权

首先你有一个授权链了,如:https://mp.weixin.qq.com/cgi-bin/componentloginpage?component_appid=wxc6b9ff60a342422352992&pre_auth_code=preauthcode@@@x8QAAAAAIvQt8AA18S42IFNJf7A_7iIbAXbrglC8Ov8f2rUaaAaAAaaaaw&redirect_uri=http://192.168.20.222:9999/admin/vx/auth/callback&auth_type=3
有一个取巧的方法,你打开http://192.168.20.222这个网站,然后随便找一个a标签,然后F12打开调试,去将这个a标签的属性改一下,即<a href="https://mp.weixin.qq.com/cgi-bin/componentloginpage?component_appid=wxc6b9ff60a342422352992&pre_auth_code=preauthcode@@@x8QAAAAAIvQt8AA18S42IFNJf7A_7iIbAXbrglC8Ov8f2rUaaAaAAaaaaw&redirect_uri=http://192.168.20.222:9999/admin/vx/auth/callback&auth_type=3">,回车确定后,去点这个a标签,即可打开授权页

如:在这里插入图片描述
扫码之后:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3.4. 服务器接收公众号事件/消息

接收路径配置即途中的授权后实现业务
在这里插入图片描述
注意:

  1. 路径中一定要有$APPID$作为参数来接收公众号的appId
  2. 在写这个接口的时候,这个接口一定要能接收get和post两种请求,或者你分开写,因为他会先发get请求验证你接口是否存在可行,然后再发带着真正事件的post请求,并且get请求要给他一个响应
  3. 接收post请求之后,解密后的数据就跟公众号事件接收一致了

controller层

    /**
     * 第三方平台接收公众号事件
     */
    @RequestMapping("/{APPID}/callback")
    @Inner(value = false)
    @SaasNoCheck
    public void eventCallBack(@PathVariable("APPID") String appId, HttpServletRequest req, HttpServletResponse resp) {
        vxService.eventCallBack(appId, req, resp);
    }

service层

	// 接收事件
    @Override
    public void eventCallBack(String appId, HttpServletRequest req, HttpServletResponse resp) {
        PrintWriter out = null;
        try {
            req.setCharacterEncoding("utf-8");
            resp.setCharacterEncoding("utf-8");
            String signature = req.getParameter("msg_signature");
            log.info("事件回调 appId:{} signature:{}" , appId, signature);

            if (!StringUtils.isNotBlank(signature)) {
                log.error("signature中无消息");
                return;//微信推送给第三方开放平台的消息一定是加过密的,无消息加密无法解密消息
            }
            //时间戳
            String timestamp = req.getParameter("timestamp");
            // 随机数
            String nonce = req.getParameter("nonce");

            Map<String, Object> xmlMap = null;
            out = resp.getWriter();
            xmlMap = MessageUtil.xmlToMap(req);
            String resultMsg = null;
            if (StringUtils.isEmpty(signature)) {
                //不加密处理
                log.info("接收的微信推送消息为:{}", xmlMap.toString());
            } else {
                // 从请求中读取整个post数据
                log.info("加密签名msg_signature:{} 时间戳timestamp:{} 随机数nonce:{}", signature, timestamp, nonce);
                log.info("从request中获取xml信息:{}", xmlMap);
                //解密处理
                String encrypt = (String) xmlMap.get("Encrypt");
                resultMsg = decryptMsg(timestamp, nonce, signature, encrypt);
                log.info("接收的微信推送消息为:{}", resultMsg);
            }
            //处理接收到的信息
            out.println(handleEventMsg(appId, resultMsg));
        } catch (Exception e) {
            log.error(e.getLocalizedMessage(), e);
            if (out != null) {
                out.println("success");
            }
        } finally {
            if (out != null) {
                out.close();
            }
            out = null;
        }
    }
public String handleEventMsg(String appId, String resultMsg){
        String message = "success";
        try {
        	//hutool工具包
            Map<String, Object> map = XmlUtil.xmlToMap(resultMsg);
            String toUserName = (String) map.get("ToUserName");
            String fromUserName = (String) map.get("FromUserName");
            String createTime = (String) map.get("CreateTime");
            String msgType = (String) map.get("MsgType");
            String content = (String) map.get("Content");
            String msgId = (String) map.get("MsgId");
            String eventType = (String) map.get("Event");

            log.info("toUserName:{} fromUserName:{} createTime:{} msgType:{} content:{} msgId:{}  event:{}",
                    toUserName, fromUserName, createTime, msgType, content, msgId, eventType);
            // 普通消息:文本消息。
            if (MessageUtil.MESSAGE_TEXT.equals(msgType)) {
                //message = MessageUtil.initText(toUserName, fromUserName, "你发送的消息是:" + content);
//                log.info("普通消息:{}", message);
                message = "success";
            } else if (MessageUtil.MESSAGE_EVENT.equals(msgType)) {
                // 关注
                // 下面是我存入数据库的操作
                LambdaQueryWrapper<SysGzhFens> fenWrapper = new LambdaQueryWrapper<SysGzhFens>().eq(SysGzhFens::getGzhOpenId, fromUserName)
                        .in(SysGzhFens::getDelFlag, Arrays.asList(0, 1)).orderByDesc(SysGzhFens::getCreateTime).last(" limit 1");
                SysGzhFens fens = gzhFensService.getOne(fenWrapper);
                if (MessageUtil.MESSAGE_SUBSCRIBE_EVENT.equals(eventType)) {
                    log.info("关注");
                   // message = MessageUtil.initText(toUserName, fromUserName, "感谢关注");
                    if (fens != null) {
                        fens.setDelFlag(0);
                        gzhFensService.updateById(fens);
                    } else {
                        SysGzhFensAddDTO sysGzhFensAddDTO = new SysGzhFensAddDTO();
                        sysGzhFensAddDTO.setGzhOpenId(fromUserName);
                        sysGzhFensAddDTO.setTenantId(SecurityUtils.getTenantId());
                        gzhFensService.save(sysGzhFensAddDTO);
                    }
                    message = "success";
                } else if (MessageUtil.MESSAGE_UNSUBSCRIBE_EVENT.equals(eventType)) {
                    log.info("取关");
                   // message = MessageUtil.initText(toUserName, fromUserName, "取关成功");
                    if (fens != null) {
                        fens.setDelFlag(1);
                        boolean remove = gzhFensService.updateById(fens);
                        log.info("取关情况:{}", remove);
                    }
                    message = "success";

                }
            }
            log.info("消息:{}", message);
        } catch (Exception e) {
            log.error("处理微信公众号事件失败:", e);
        }
        return message;
    }
  • 16
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

浅梦曾倾

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值