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

文章目录

  • 1. 整体流程
  • 2. 成为服务商
    • 2.1. 首先打开微信开放平台,注册并登录你的账号
    • 2.2. 进行开发者资质认证
    • 2.3. 创建第三方平台并提交审核
  • 3. 搭建第三方平台后端服务
    • 3.1. 管理员设置 和 权限集设置
    • 3.2. 开发资料设置
    • 3.3. 进行商家授权操作
      • 3.3.0. 加密解密操作
      • 3.3.1. 获取第三方平台发送至服务器的ticket
      • 3.3.2. 获取第三方平台的token令牌
      • 3.3.3. 获取预授权码
      • 3.3.4. 生成授权链
        • 3.3.4.0. 下面的3.3.4.* 的子项,对业务影响不大,只是你可能会用到,但是我只获取事件没怎么用到
        • 3.3.4.1. 授权回调
        • 3.3.4.2. 通过授权码获取授权信息
        • 3.3.4.3. 通过授权公众号的access_token获取授权信息
        • 3.3.4.4. 去维护刷新这个公众号的access_token ,每两个小时或者一个半小时刷新一次
      • 3.3.5. 客户扫码授权
    • 3.4. 服务器接收公众号事件/消息

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 ------------------------// 票据ticketString COMPONENT_VERIFY_TICKET = "vx:component:ticket";// 第三方token令牌String COMPONENT_ACCESS_TOKEN = "vx:component:token";// 第三方pre_auth_codeString COMPONENT_PRE_AUTH_CODE = "vx:component:preAuthCode";// 公众号授权信息String GZH_AUTH_MSG = "gzh:msg:";//---------------- URL ------------------------// 获取第三方平台接口的调用凭据(令牌)urlString API_COMPONENT_TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/component/api_component_token";// 获取预授权码urlString API_CREATE_PRE_AUTH_CODE_URL = "https://api.weixin.qq.com/cgi-bin/component/api_create_preauthcode?component_access_token=";// 使用授权码获取授权信息urlString API_QUERY_AUTH_URL = "https://api.weixin.qq.com/cgi-bin/component/api_query_auth?component_access_token=";// 获取授权账号信息urlString API_GET_AUTHORIZER_INFO_URL = "https://api.weixin.qq.com/cgi-bin/component/api_get_authorizer_info?component_access_token=";// 获取/刷新接口调用令牌urlString API_AUTHORIZER_TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/component/api_authorizer_token?component_access_token=";// 授权回调urlString 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对应的ticketpublic static final Map<String, String> COMPONENT_VETIFY_TICKET_MAP = new HashMap<>();@Resourceprivate StringRedisTemplate redisTemplate;@Overridepublic 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"://查询库中的第三方信息,并且准备存储ticketString 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 	消息类型,文本为 textpublic static final String MESSAGE_TEXT = "text";// + MsgType 	消息类型,图片为 imagepublic static final String MESSAGE_IMAGE = "image";// + MsgType 	语音为 voicepublic static final String MESSAGE_VOICE = "voice";// + MsgType 	视频为 videopublic static final String MESSAGE_VIDEO = "video";// + MsgType 	小视频为 shortvideopublic static final String MESSAGE_SHORTVIDEO = "shortvideo";// + MsgType 	消息类型,地理位置为 locationpublic static final String MESSAGE_LOCATION = "location";// + MsgType 	消息类型,链接为 linkpublic static final String MESSAGE_LINK = "link";// + MsgType 	消息类型,eventpublic static final String MESSAGE_EVENT = "event";// > Event 	事件类型,subscribe(订阅)、unsubscribe(取消订阅)//Event 	事件类型,SCAN//Event 	事件类型,LOCATION//Event 	事件类型,CLICK//Event 	事件类型,VIEWpublic 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中拿到ticketString 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());//tokenString 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;// 获取预授权码urlprivate 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());//tokenpreAuthCode = String.valueOf(tokenMap.get("pre_auth_code"));//有效期Double expiresIn = (Double) tokenMap.get("expires_in");if (StringUtils.isNotBlank(preAuthCode)) {//存入redisCOMPONENT_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层

	@Overridepublic String buildAuthChainUrl() {// tokenString 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层

 	// 授权回调@Overridepublic void authCallBack(HttpServletRequest request, HttpServletResponse response) {//授权回调try {//授权码String authorizationCode = request.getParameter("auth_code");Integer expiresIn = Integer.valueOf(request.getParameter("expires_in"));//三方AccessToken 见3.3.2String apiComponentToken = getApiComponentToken();//根据授权码获取授权信息 这个方法见下文3.3.4.2Map<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");//将授权信息存入redisString 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

	// 获取授权账号信息urlprivate 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

	// 获取/刷新接口调用令牌urlprivate static  String API_AUTHORIZER_TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/component/api_authorizer_token?component_access_token=";//获取/刷新tokenpublic 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)@SaasNoCheckpublic void eventCallBack(@PathVariable("APPID") String appId, HttpServletRequest req, HttpServletResponse resp) {vxService.eventCallBack(appId, req, resp);}

service层

	// 接收事件@Overridepublic 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;}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/741797.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

新零售SaaS架构:什么是线上商城系统?

零售商家为什么要建设线上商城 传统的实体门店服务范围有限&#xff0c;只能吸引周边500米内的消费者。因此&#xff0c;如何拓展服务范围&#xff0c;吸引更多消费者到店&#xff0c;成为了店家迫切需要解决的问题。 缺乏忠实顾客&#xff0c;客户基础不稳&#xff0c;往往是…

IEEE802.11v协议介绍

IEEE802.11v协议简介 协议全称:无线网络管理(Wireless Network Management) 批准日期:2011年2月 协议状态:并入802.11-2012 协议别名:BSS过渡管理 主要功能 支持AP和STA间交换:关于RF环境和拓扑状态的信息,以协助STA进行漫游决策支持STA之间交换:关于RF环境状态的信…

【Python】random库

专栏文章索引&#xff1a;Python 原文章&#xff1a;Python中random函数用法整理_python random-CSDN博客 目录 1.random.random() 2.random.uniform(a, b) 3.random.randint(a, b) 4.random.randrange([start], stop[, step]) 5. random.choice() 6. random.shuffle(x[,…

四川尚熠电子商务有限公司领航抖音电商新纪元

随着互联网的飞速发展&#xff0c;电子商务已经成为现代商业的重要组成部分。在这个变革的时代&#xff0c;四川尚熠电子商务有限公司凭借其敏锐的市场洞察力和创新精神&#xff0c;专注于抖音电商服务&#xff0c;为广大品牌商家提供了一站式的电商解决方案&#xff0c;成为行…

从菜鸟到大师!年薪20W的c++ QT开发工程师需要懂哪些技术?

如今Qt的知识也变得非常广泛和复杂&#xff0c;学习起来同样具有一定的挑战。对于Qt从业者来说&#xff0c;有两个主要层面&#xff1a;一个是深入理解Qt框架和基础知识&#xff0c;另一个是具备丰富的工程经验。 还不熟悉的朋友&#xff0c;这里可以先领取一份Qt开发必备技术…

基于 onsemi 汽车前置大灯设计之 PCB 设计注意事项

一、 基本介绍 Blitz Fly 方案是世平集团推出的基于 onsemi NCV7802 & NCV78723 的汽车前置大灯方案。该方案针对汽车前照灯设计&#xff0c;主要分为四个部分&#xff1a; 1. LED 驱动&#xff1a; 以 onsemi NCV78702 以及 onsemi NCV78723 为核心。NCV78702 是一款用于…

查询IPv4归属地信息的几种方式

查询IPv4归属地信息是一个在网络管理和安全领域常见的需求。IPv4地址是互联网协议第四版中定义的网络层地址&#xff0c;它允许计算机和网络设备在全球范围内进行通信。而查询IPv4归属地信息&#xff0c;即是指确定该IPv4地址所在的地理位置或注册的网络服务提供商&#xff0c;…

【HTML】1px边框与1px分割线

对比图 箭头标注的是处理过的 1px分割线 使用transform的scaleY进行缩小 码 <div class"mini-heriz"></div><br><div style"border: solid 1px black; width: 300px;height: 1px;"></div> <style> .mini-heriz {wi…

操作系统:进程控制(上)

目录 1.进程创建 1.1.fork()函数 1.2.写时拷贝 2.进程终止 2.1.进程的退出场景 2.1.1.退出码和错误码&#xff08;正常终止&&任务失败&#xff09; 2.1.2.异常终止 1.进程创建 1.1.fork()函数 在linux中&#xff0c;fork函数时非常重要的函数&#xff0c;它从已…

力扣爆刷第93天之hot100五连刷51-55

力扣爆刷第93天之hot100五连刷51-55 文章目录 力扣爆刷第93天之hot100五连刷51-55一、200. 岛屿数量二、994. 腐烂的橘子三、207. 课程表四、208. 实现 Trie (前缀树)五、46. 全排列 一、200. 岛屿数量 题目链接&#xff1a;https://leetcode.cn/problems/number-of-islands/d…

tigramite教程(五)使用TIGRAMITE 进行自助聚合和链接置信度量化

使用TIGRAMITE 进行自助聚合和链接置信度量化 自助聚合&#xff08;Bagging&#xff09;和置信度估计例子数据生成模型基本的PCMCIBagged-PCMCI使用优化后的pc_alpha进行自举聚合使用优化的pc_alpha进行CMIknn的自举聚合 TIGRAMITE是一个用于时间序列分析的Python模块。它基于P…

【Spring】学习Spring框架那点小事儿

Spring作者&#xff1a;Rod Johnson Rod Johnson 是一位软件开发人员和作家&#xff0c;他在软件开发领域有着广泛的影响力。他出生于澳大利亚&#xff0c;拥有计算机科学和音乐双学位&#xff08;能写出有优雅的代码一定有艺术细胞&#xff09;。 Rod Johnson 在 2002 年出版…

【Python】python实现Apriori算法和FP-growth算法(附源代码)

使用一种你熟悉的程序设计语言&#xff0c;实现&#xff08;1&#xff09;Apriori算法和&#xff08;2&#xff09;FP-growth算法。 目录 1、Apriori算法2、F-Growth算法3、两种算法比较 1、Apriori算法 def item(dataset): # 求第一次扫描数据库后的 候选集&#xff0c;&am…

深圳服务器托管-优质的BGP机房

服务器只需要设置一个IP地址&#xff0c;最佳访问路由是由网络上的骨干路由器根据路由跳数与其它技术指标来确定的&#xff0c;不会占用服务器的任何系统资源。服务器的上行路由与下行路由都能选择最优的路径&#xff0c;所以能真正实现高速的单IP高速访问。 BGP协议本身具有冗…

OpenCV实战--利用级联分类器检测眼睛、行人、车牌等等

1、前言 opencv 提供级联分类器除了识别人脸外,还可以检测其他的物体 级联分类器的介绍:OpenCV实战--人脸跟踪(级联分类器) 检测人脸,戴上眼镜的演示: 这里只演示几个,更多的级联分类器文件可以百度自行查看 2、眼睛跟踪 haarcascade_eye.xml 检测眼睛的级联分类器文…

C#、C++、Java、Python 选择哪个好?

作者&#xff1a;网博汇智 链接&#xff1a;https://www.zhihu.com/question/298323023/answer/2789627224 来源&#xff1a;知乎 著作权归作者所有。商业转载请联系作者获得授权&#xff0c;非商业转载请注明出处。 一个好的程序员不能把自己绑定在一种语言上&#xff0c;不…

解决Nginx 404反向代理问题的方法

问题背景 当你在使用Nginx进行反向代理时&#xff0c;有时候会遇到404错误&#xff0c;这是因为Nginx无法找到对应的资源。这个问题通常出现在配置反向代理的过程中&#xff0c;导致用户无法正常访问所需的资源&#xff0c;给网站的稳定性和用户体验带来负面影响。 解决方法 …

复杂网络——半局部中心法

一、概述 由于最近写论文需要使用复杂网络知识中的半局部中心法&#xff0c;但是截止目前来说&#xff0c;网上几乎搜索不到有关的MATLAB程序代码&#xff0c;只有一篇用Python编写的程序&#xff0c;我的电脑中没有python&#xff0c;所以我花费一些时间&#xff0c;利用matla…

海豚调度系列之:任务类型——SPARK节点

海豚调度系列之&#xff1a;任务类型——SPARK节点 一、SPARK节点二、创建任务三、任务参数四、任务样例1.spark submit2.spark sql 五、注意事项&#xff1a; 一、SPARK节点 Spark 任务类型用于执行 Spark 应用。对于 Spark 节点&#xff0c;worker 支持两个不同类型的 spark…

53、WEB攻防——通用漏洞CRLF注入URL重定向资源处理拒绝服务

文章目录 CRLF注入原理&检测&利用URL重定向web拒绝服务 CRLF注入原理&检测&利用 URL重定向 就是url中存在urlhttps://xxx&#xff0c;重定向的页面没有限制。主要用来做钓鱼。 web拒绝服务 例如&#xff0c;图片的长宽参数由前端传入&#xff0c;恶意的数据…