最近在做微信第三方的业务,把最近做的微信开发相关陆续做一个总结。
第三方平台的申请和上线:
根据微信官方文档开放平台描述,首先需要注册一个开发者账号,按照微信开发文档流程申请审核成功后获取AppID和APPSecret,然后可以开始进行开发。
第三方平台所需要配置的信息:
授权事件接收URL:配置接收微信发送过来的ticket接口
消息与事件接收URL:配置接收微信发送的公总号消息和事件
微信授权流程
1)获取预授权码(pre_auth_code)
微信服务器会每隔十分钟向第三方平台的授权事件接收接收地址发送一次验证票据 (component_verify_ticket)。为了确保安全性,发送过来的ticket是加密的,所以获取ticket需要解密。
获取ticket
授权事件接收接收接口:
@ApiOperation(value = "授权事件接收")@RequestMapping(value = "/eventAccept")public String eventAccept(HttpServletRequest request, HttpServletResponse response) {return weChatAuthorizeService.eventAccept(request);}
解密ticket:
微信公众平台提供了 c++, php, java, python, c# 5 种语言的示例代码:
微信公众平台加解密示例代码
注意:微信发送的component_verify_ticket里没有ToUserName,所以引用微信代码之后XMLParse解析ToUserName会报错,需要修改一下。
@Overridepublic String eventAccept(HttpServletRequest request) {String nonce = request.getParameter("nonce");String timestamp = request.getParameter("timestamp");String msgSignature = request.getParameter("msg_signature");String encryptType = request.getParameter("encrypt_type");String signature = request.getParameter("signature");String postData = "";try {postData = StreamUtils.getInputStreamJson(request.getInputStream());} catch (IOException e) {ExceptionUtils.logError(e);}logger.info("nonce: " + nonce);logger.info("timestamp: " + timestamp);logger.info("msg_signature: " + msgSignature);logger.info("encrypt_type: " + encryptType);logger.info("signature: " + signature);logger.info("postData: " + postData);//获取配置文件中的appid等信息String encodingAesKey = weChatComponentProperties.getEncodingAesKey();String appId = weChatComponentProperties.getAppId();String token = weChatComponentProperties.getToken();//解密WXBizMsgCrypt pc;String requestXml = "";try {pc = new WXBizMsgCrypt(token, encodingAesKey, appId);requestXml = pc.decryptMsg(msgSignature, timestamp, nonce, postData);} catch (AesException e) {e.printStackTrace();}logger.info("解密后明文是: " + requestXml);String ticket = "";try {if(!StringUtils.isEmpty(requestXml)) {Document doc = DocumentHelper.parseText(requestXml);Element rootElt = doc.getRootElement();ticket = rootElt.elementTextTrim("ComponentVerifyTicket");logger.info("ComponentVerifyTicket: " + ticket);}} catch (DocumentException e) {ExceptionUtils.logError(e);}//保存到redisredisService.put(weChatComponentProperties.getAppId()+"Ticket", ticket, 600);logger.info("redisServiceticket====>:" + redisService.get(weChatComponentProperties.getAppId()+"Ticket"));return "success";}
获取令牌
令牌(component_access_token)是第三方平台接口的调用凭据。令牌是有期限的,每个令牌的有效期为2小时,并且令牌的调用次数有限,所以最好是在令牌失效前一点再重新获取最好。
@Overridepublic ResultUtils getComponentAccessToken() {String componentAccessToken = redisService.get(weChatComponentProperties.getAppId()+"ComponentAccessToken");String ticket = redisService.get(weChatComponentProperties.getAppId()+"Ticket");logger.info("component_access_token:" + ticket);logger.info("component_access_ticket:" + ticket);//判断数据库中是否存在component_access_tokenif(StringUtils.isNotEmpty(componentAccessToken)) {// 如果存在,直接返回token的值return ResultUtils.success(componentAccessToken);}//判断ticket是否存在if(StringUtils.isEmpty(ticket)) {return ResultUtils.noCorrect("noTicket");}// 如果不存在ticket,重新获取JSONObject json = new JSONObject();json.put("component_appid",weChatComponentProperties.getAppId());json.put("component_appsecret",weChatComponentProperties.getAppSecret());json.put("component_verify_ticket", ticket);logger.info("ComponentAccessToken:json=" + json);//发送http请求String url = weChatComponentProperties.getServerUrl() + "cgi-bin/component/api_component_token";String result = HttpUtils.doPostJson(url, json.toString());logger.info("component_access_tokenresult:" + result);JSONObject object = JSON.parseObject(result);if(null == object) {return ResultUtils.noCorrect("componentAccessToken调用失败");}//判断是否获取成功if(StringUtils.isNotEmpty(object.getString("errcode"))&&object.getString("errcode").equals("46003")) {return ResultUtils.noCorrect("componentAccessToken获取测失败");}logger.info("component_access_token:" + object.get("component_access_token"));componentAccessToken= object.get("component_access_token").toString();//将令牌和过期时间存入redisredisService.put(weChatComponentProperties.getAppId()+"ComponentAccessToken", componentAccessToken, Integer.parseInt(object.get("expires_in").toString()));return ResultUtils.success(componentAccessToken);}
获取预授权码(pre_auth_code)
第三方平台要实现授权托管也需要预授权码,授权码的有效期是10分钟。
@Overridepublic ResultUtils getAuthCode(String componentAccessToken) {String authCode = redisService.get(weChatComponentProperties.getAppId()+"AuthCode");if(StringUtils.isNotEmpty(authCode)) {return ResultUtils.success(authCode);}//获取pre_auth_codeString authCodeUrl = weChatComponentProperties.getServerUrl() + "cgi-bin/component/api_create_preauthcode?component_access_token=" + componentAccessToken;JSONObject jsonAuthCode = new JSONObject();jsonAuthCode.put("component_appid",weChatComponentProperties.getAppId());String authCodeResult = HttpUtils.doPostJson(authCodeUrl, jsonAuthCode.toString());JSONObject authCodeobject = JSON.parseObject(authCodeResult);if(null == authCodeobject) {return ResultUtils.noCorrect("authCode调用失败") ;}if(StringUtils.isNotEmpty(authCodeobject.getString("errcode"))&&authCodeobject.getString("errcode").equals("46003")) {return ResultUtils.noCorrect("authCode调用失败" + authCodeobject);}logger.info("authCode:" + authCodeobject.get("pre_auth_code"));authCode = authCodeobject.get("pre_auth_code").toString();//将预授权码和过期时间存入redisredisService.put(weChatComponentProperties.getAppId()+"AuthCode", authCode, Integer.parseInt(authCodeobject.get("expires_in").toString()));return ResultUtils.success(authCode);}
授权
授权有两种方式,分别是授权注册页面扫码授权和点击移动端链接快速授权
1)授权注册页面扫码授权
扫码授权的时候需要将生成授权链接放置在移动网页中,否则会报错。
@Overridepublic ResultUtils processAuthorizeEvent() {ResultUtils componentAccessTokenReuslt = getComponentAccessToken();if(!CodeEnums.SUCCESS_CODE.getCode().equals(componentAccessTokenReuslt.getCode())) {return ResultUtils.noCorrect("第三方ComponentAccessToken获取失败");}String componentAccessToken = componentAccessTokenReuslt.getData().toString();ResultUtils authCodeResult = getAuthCode(componentAccessToken);if(!CodeEnums.SUCCESS_CODE.getCode().equals(authCodeResult.getCode())) {return ResultUtils.noCorrect("第三方权限预码获取失败");}String authCode = authCodeResult.getData().toString();String url = weChatComponentProperties.getWeChatComponentServerUrl() + "cgi-bin/componentloginpage?" +"component_appid="+weChatComponentProperties.getAppId()+"&pre_auth_code="+ authCode +"&redirect_uri="+ weChatComponentProperties.getRedirectUrl() +"&auth_type=1";return ResultUtils.success(url);}
2)点击移动端链接快速授权
链接生成之后,将链接通过移动端直接发给授权管理员,管理员确认后即授权成功。
@Overridepublic ResultUtils processAuthorizeEventClick() {ResultUtils componentAccessTokenReuslt = getComponentAccessToken();if(!CodeEnums.SUCCESS_CODE.getCode().equals(componentAccessTokenReuslt.getCode())) {return ResultUtils.noCorrect("第三方ComponentAccessToken获取失败");}String componentAccessToken = componentAccessTokenReuslt.getData().toString();ResultUtils authCodeResult = getAuthCode(componentAccessToken);if(!CodeEnums.SUCCESS_CODE.getCode().equals(authCodeResult.getCode())) {return ResultUtils.noCorrect("第三方权限预码获取失败");}String authCode = authCodeResult.getData().toString();String url = weChatComponentProperties.getWeChatComponentServerUrl() + "safe/bindcomponent?action=bindcomponent&auth_type=3&no_scan=1&" +"component_appid="+weChatComponentProperties.getAppId()+"&pre_auth_code="+ authCode +"&redirect_uri="+ weChatComponentProperties.getRedirectUrl() +"&auth_type=1#wechat_redirect";logger.info(url);return ResultUtils.success(url);}
授权成功
授权成功之后微信服务器会回调redirect_uri,发送授权信息中的刷新令牌authorizer_refresh_token需要保存下来。