需求
根据一个小程序,发布一个和其一模一样的小程序。(可能不只一个)
如果按常规的做法每个小程序都需要经历这样的步骤:在公众平台申请appid——>拉代码到自己的仓库——>打开编译器绑定自己的appId——>上传代码到公众平台——>再提交审核——>在公众平台调整开发设置。
如果用微信开放平台的服务平台开发的步骤:在微信开放平台申请第三方服务平台——>上传小程序模板(只有第一次有)——>调接口上传小程序代码
——>调接口提交审核——>调接口发布小程序。
上传小程序模板的步骤:在公众平台申请appid——>在微信开放平台的第三方服务平台中添加开发小程序——>上传小程序代码(此时会直接上传到了开放平台的草稿箱)——>将草稿箱的代码作为普通模板
这就是服务平台开发平台的便捷,只需上传一次小程序代码,就可以快速的发布小程序。
实现
步骤:获取component_verify_ticket——>获取component_access_token——>获取authorizer_access_token——>调接口发布小程序
1、获取component_verify_ticket
官方文档:验证票据 | 微信开放文档 (qq.com)
因为这个验证票据是微信官方主动推送的,所以需要在第三方平台配置一波
controller
1 2 3 4 5 6 7 8 @ApiOperation (value = "微信开放平台:授权事件接收URL,验证票据" , notes = "zhuguangliang" )@AnonymousPostMapping ("/pushTicket" )public String wechatPlatformEvent(@RequestParam ("timestamp" ) String timestamp, @RequestParam ("nonce" ) String nonce, @RequestParam ("msg_signature" ) String msgSignature, @RequestBody String postData) { return openPlatformUtil .parseRequest (timestamp, nonce, msgSignature, postData); }
实现类
关于存储component_verify_ticket方案,我这里是mysql和redis各存一份。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 public String parseRequest (String timeStamp, String nonce, String msgSignature, String postData) { try { if (redisUtils.hasKey(CacheKey.OPEN_PLATFORM_TICKET)) { return "success" ; } WXBizMsgCrypt pc = new WXBizMsgCrypt (Token, Key, APPID); String xml = pc.decryptMsg(msgSignature, timeStamp, nonce, postData); Map<String, String> result = WXXmlToMapUtil.xmlToMap(xml); String componentVerifyTicket = result.get("ComponentVerifyTicket" ); if (StringUtils.isNotEmpty(componentVerifyTicket)) { SpiritOpenPlatform spiritOpenPlatform = new SpiritOpenPlatform (); if (platformMapper.selectByAppId(APPID) == 0 ) { spiritOpenPlatform.setAppid(APPID); spiritOpenPlatform.setAppSecret(AppSecret); spiritOpenPlatform.setPlatformKey(Key); spiritOpenPlatform.setToken(Token); spiritOpenPlatform.setComponentVerifyTicket(componentVerifyTicket); platformMapper.insert(spiritOpenPlatform); } else { spiritOpenPlatform.setComponentVerifyTicket(componentVerifyTicket); LambdaUpdateWrapper<SpiritOpenPlatform> updateWrapper = new LambdaUpdateWrapper <>(); updateWrapper.eq(SpiritOpenPlatform::getAppid, APPID); platformMapper.update(spiritOpenPlatform, updateWrapper); } redisUtils.set(CacheKey.OPEN_PLATFORM_TICKET, componentVerifyTicket, 60 * 60 * 12 ); log.info("微信开放平台,第三方平台获取【验证票据】成功" ); } else { log.error("微信开放平台,第三方平台获取【验证票据】失败" ); } } catch (AesException e) { log.error("微信开放平台,第三方平台获取【验证票据】失败,异常信息:" + e.getMessage()); } return "success" ; }
官方提供的解密类:WXBizMsgCrypt.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 import org.apache.commons.codec.binary.Base64;import org.w3c.dom.Document;import org.w3c.dom.Element;import org.w3c.dom.NodeList;import org.xml.sax.InputSource;import javax.crypto.Cipher;import javax.crypto.spec.IvParameterSpec;import javax.crypto.spec.SecretKeySpec;import javax.xml.parsers.DocumentBuilder;import javax.xml.parsers.DocumentBuilderFactory;import java.io.StringReader;import java.nio.charset.Charset;import java.security.MessageDigest;import java.util.Arrays;public class WXBizMsgCrypt { static Charset CHARSET = Charset.forName("utf-8" ); Base64 base64 = new Base64 (); byte [] aesKey; String token; String appId; public WXBizMsgCrypt (String token, String encodingAesKey, String appId) throws AesException { if (encodingAesKey.length() != 43 ) { throw new AesException (AesException.IllegalAesKey); } this .token = token; this .appId = appId; aesKey = Base64.decodeBase64(encodingAesKey + "=" ); } int recoverNetworkBytesOrder (byte [] orderBytes) { int sourceNumber = 0 ; for (int i = 0 ; i < 4 ; i++) { sourceNumber <<= 8 ; sourceNumber |= orderBytes[i] & 0xff ; } return sourceNumber; } String decrypt (String text) throws AesException { byte [] original; try { Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding" ); SecretKeySpec key_spec = new SecretKeySpec (aesKey, "AES" ); IvParameterSpec iv = new IvParameterSpec (Arrays.copyOfRange(aesKey, 0 , 16 )); cipher.init(Cipher.DECRYPT_MODE, key_spec, iv); byte [] encrypted = Base64.decodeBase64(text); original = cipher.doFinal(encrypted); } catch (Exception e) { e.printStackTrace(); throw new AesException (AesException.DecryptAESError); } String xmlContent, from_appid; try { byte [] bytes = PKCS7Encoder.decode(original); byte [] networkOrder = Arrays.copyOfRange(bytes, 16 , 20 ); int xmlLength = recoverNetworkBytesOrder(networkOrder); xmlContent = new String (Arrays.copyOfRange(bytes, 20 , 20 + xmlLength), CHARSET); from_appid = new String (Arrays.copyOfRange(bytes, 20 + xmlLength, bytes.length), CHARSET); } catch (Exception e) { e.printStackTrace(); throw new AesException (AesException.IllegalBuffer); } if (!from_appid.equals(appId)) { throw new AesException (AesException.ValidateSignatureError); } return xmlContent; } public String decryptMsg (String msgSignature, String timeStamp, String nonce, String postData) throws AesException { Object[] encrypt = extract(postData); String signature = getSHA1(token, timeStamp, nonce, encrypt[1 ].toString()); if (!signature.equals(msgSignature)) { throw new AesException (AesException.ValidateSignatureError); } String result = decrypt(encrypt[1 ].toString()); return result; } public static Object[] extract(String xmltext) throws AesException { Object[] result = new Object [3 ]; try { DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl" , true ); dbf.setFeature("http://xml.org/sax/features/external-general-entities" , false ); dbf.setFeature("http://xml.org/sax/features/external-parameter-entities" , false ); dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd" , false ); dbf.setXIncludeAware(false ); dbf.setExpandEntityReferences(false ); DocumentBuilder db = dbf.newDocumentBuilder(); StringReader sr = new StringReader (xmltext); InputSource is = new InputSource (sr); Document document = db.parse(is); Element root = document.getDocumentElement(); NodeList nodelist1 = root.getElementsByTagName("Encrypt" ); NodeList nodelist2 = root.getElementsByTagName("ToUserName" ); result[0 ] = 0 ; result[1 ] = nodelist1.item(0 ).getTextContent(); if (nodelist2 != null ) { if (nodelist2.item(0 ) != null ) { result[2 ] = nodelist2.item(0 ).getTextContent(); } } return result; } catch (Exception e) { e.printStackTrace(); throw new AesException (AesException.ParseXmlError); } } public static String getSHA1 (String token, String timestamp, String nonce, String encrypt) throws AesException { try { String[] array = new String []{token, timestamp, nonce, encrypt}; StringBuffer sb = new StringBuffer (); Arrays.sort(array); for (int i = 0 ; i < 4 ; i++) { sb.append(array[i]); } String str = sb.toString(); MessageDigest md = MessageDigest.getInstance("SHA-1" ); md.update(str.getBytes()); byte [] digest = md.digest(); StringBuffer hexstr = new StringBuffer (); String shaHex = "" ; for (int i = 0 ; i < digest.length; i++) { shaHex = Integer.toHexString(digest[i] & 0xFF ); if (shaHex.length() < 2 ) { hexstr.append(0 ); } hexstr.append(shaHex); } return hexstr.toString(); } catch (Exception e) { e.printStackTrace(); throw new AesException (AesException.ComputeSignatureError); } } }
PKCS7Encode.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 import java.nio.charset.Charset;import java.util.Arrays;public class PKCS7Encoder { static Charset CHARSET = Charset.forName("utf-8" ); static int BLOCK_SIZE = 32 ; static byte [] encode(int count) { int amountToPad = BLOCK_SIZE - (count % BLOCK_SIZE); if (amountToPad == 0 ) { amountToPad = BLOCK_SIZE; } char padChr = chr(amountToPad); String tmp = new String (); for (int index = 0 ; index < amountToPad; index++) { tmp += padChr; } return tmp.getBytes(CHARSET); } static byte [] decode(byte [] decrypted) { int pad = (int ) decrypted[decrypted.length - 1 ]; if (pad < 1 || pad > 32 ) { pad = 0 ; } return Arrays.copyOfRange(decrypted, 0 , decrypted.length - pad); } static char chr (int a) { byte target = (byte ) (a & 0xFF ); return (char ) target; } }
WXXmlToMapUtil.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 import org.dom4j.Document;import org.dom4j.DocumentException;import org.dom4j.DocumentHelper;import org.dom4j.Element;import org.dom4j.io.OutputFormat;import org.dom4j.io.SAXReader;import org.dom4j.io.XMLWriter;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.w3c.dom.Node;import org.w3c.dom.NodeList;import javax.xml.parsers.DocumentBuilder;import javax.xml.parsers.DocumentBuilderFactory;import javax.xml.transform.OutputKeys;import javax.xml.transform.Transformer;import javax.xml.transform.TransformerFactory;import javax.xml.transform.dom.DOMSource;import javax.xml.transform.stream.StreamResult;import java.io.ByteArrayInputStream;import java.io.InputStream;import java.io.StringReader;import java.io.StringWriter;import java.util.HashMap;import java.util.List;import java.util.Map;public class WXXmlToMapUtil { private static final Logger logger = LoggerFactory.getLogger(WXXmlToMapUtil.class); public static Map<String, String> xmlToMap (String xml) { try { Map<String, String> data = new HashMap <>(); DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); InputStream stream = new ByteArrayInputStream (xml.getBytes("UTF-8" )); org.w3c.dom.Document doc = documentBuilder.parse(stream); doc.getDocumentElement().normalize(); NodeList nodeList = doc.getDocumentElement().getChildNodes(); for (int idx = 0 ; idx < nodeList.getLength(); ++idx) { Node node = nodeList.item(idx); if (node.getNodeType() == Node.ELEMENT_NODE) { org.w3c.dom.Element element = (org.w3c.dom.Element) node; data.put(element.getNodeName(), element.getTextContent()); } } stream.close(); return data; } catch (Exception e) { e.printStackTrace(); return null ; } } public static String mapToXml (Map<String, String> data) throws Exception { try { DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); org.w3c.dom.Document document = documentBuilder.newDocument(); org.w3c.dom.Element root = document.createElement("xml" ); document.appendChild(root); for (String key : data.keySet()) { String value = data.get(key); if (value == null ) { value = "" ; } value = value.trim(); org.w3c.dom.Element filed = document.createElement(key); filed.appendChild(document.createTextNode(value)); root.appendChild(filed); } TransformerFactory tf = TransformerFactory.newInstance(); Transformer transformer = tf.newTransformer(); DOMSource source = new DOMSource (document); transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8" ); transformer.setOutputProperty(OutputKeys.INDENT, "yes" ); StringWriter writer = new StringWriter (); StreamResult result = new StreamResult (writer); transformer.transform(source, result); String output = writer.getBuffer().toString(); writer.close(); return output; } catch (Exception e) { e.printStackTrace(); return null ; } } public static Map<String, Object> multilayerXmlToMap (String xml) { Document doc = null ; try { doc = DocumentHelper.parseText(xml); } catch (DocumentException e) { logger.error("xml字符串解析,失败 --> {}" , e); } Map<String, Object> map = new HashMap <>(); if (null == doc) { return map; } Element rootElement = doc.getRootElement(); recursionXmlToMap(rootElement, map); return map; } private static void recursionXmlToMap (Element element, Map<String, Object> outmap) { List<Element> list = element.elements(); int size = list.size(); if (size == 0 ) { outmap.put(element.getName(), element.getTextTrim()); } else { Map<String, Object> innermap = new HashMap <>(); list.forEach(childElement -> recursionXmlToMap(childElement, innermap)); outmap.put(element.getName(), innermap); } } public static String multilayerMapToXml (Map<String, Object> map, boolean isCDATA) { String parentName = "xml" ; Document doc = DocumentHelper.createDocument(); doc.addElement(parentName); String xml = recursionMapToXml(doc.getRootElement(), parentName, map, isCDATA); return formatXML(xml); } private static String recursionMapToXml (Element element, String parentName, Map<String, Object> map, boolean isCDATA) { Element xmlElement = element.addElement(parentName); map.keySet().forEach(key -> { Object obj = map.get(key); if (obj instanceof Map) { recursionMapToXml(xmlElement, key, (Map<String, Object>) obj, isCDATA); } else { String value = obj == null ? "" : obj.toString(); if (isCDATA) { xmlElement.addElement(key).addCDATA(value); } else { xmlElement.addElement(key).addText(value); } } }); return xmlElement.asXML(); } public static String formatXML (String xml) { String requestXML = null ; try { SAXReader reader = new SAXReader (); Document document = reader.read(new StringReader (xml)); if (null != document) { StringWriter stringWriter = new StringWriter (); OutputFormat format = new OutputFormat (" " , true ); format.setNewLineAfterDeclaration(false ); format.setSuppressDeclaration(false ); format.setNewlines(true ); XMLWriter writer = new XMLWriter (stringWriter, format); writer.write(document); writer.flush(); writer.close(); requestXML = stringWriter.getBuffer().toString(); } return requestXML; } catch (Exception e) { logger.error("格式化xml,失败 --> {}" , e); return null ; } } }
AesException.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 @SuppressWarnings("serial") public class AesException extends Exception { public final static int OK = 0 ; public final static int ValidateSignatureError = -40001 ; public final static int ParseXmlError = -40002 ; public final static int ComputeSignatureError = -40003 ; public final static int IllegalAesKey = -40004 ; public final static int ValidateCorpidError = -40005 ; public final static int EncryptAESError = -40006 ; public final static int DecryptAESError = -40007 ; public final static int IllegalBuffer = -40008 ; private int code; private static String getMessage (int code) { switch (code) { case ValidateSignatureError: return "签名验证错误" ; case ParseXmlError: return "xml解析失败" ; case ComputeSignatureError: return "sha加密生成签名失败" ; case IllegalAesKey: return "SymmetricKey非法" ; case ValidateCorpidError: return "corpid校验失败" ; case EncryptAESError: return "aes加密失败" ; case DecryptAESError: return "aes解密失败" ; case IllegalBuffer: return "解密后得到的buffer非法" ; default : return null ; } } public int getCode () { return code; } AesException(int code) { super (getMessage(code)); this .code = code; } }
2、获取component_access_token
官方文档:令牌 | 微信开放文档 (qq.com)
实现类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 @Override public void getComponentAccessToken () { String componentAccessTokenUrl = "https://api.weixin.qq.com/cgi-bin/component/api_component_token" ; if (redisUtils.hasKey(CacheKey.COMPONENT_ACCESS_TOKEN)) { if (redisUtils.getExpire(CacheKey.COMPONENT_ACCESS_TOKEN) > 10 * 60 ) { return ; } } JSONObject params = new JSONObject (); params.put("component_appid" , APPID); params.put("component_appsecret" , AppSecret); String component_verify_ticket; if (redisUtils.hasKey(CacheKey.OPEN_PLATFORM_TICKET)) { component_verify_ticket = (String) redisUtils.get(CacheKey.OPEN_PLATFORM_TICKET); } else { component_verify_ticket = platformMapper.selectTicket(); redisUtils.set(CacheKey.OPEN_PLATFORM_TICKET, component_verify_ticket, 60 * 60 * 12 ); } if (StringUtils.isBlank(component_verify_ticket)) { throw new BadRequestException ("微信开放平台,第三方平台获取【验证票据】失败" ); } params.put("component_verify_ticket" , component_verify_ticket); JSONObject data = JSONObject.parseObject(HttpUtil.post(componentAccessTokenUrl, JSON.toJSONString(params))); if (data.containsKey("component_access_token" )) { String componentAccessToken = data.getString("component_access_token" ); redisUtils.set(CacheKey.COMPONENT_ACCESS_TOKEN, componentAccessToken, 60 * 60 * 2 ); return ; } redisUtils.del(CacheKey.COMPONENT_ACCESS_TOKEN); redisUtils.del(CacheKey.OPEN_PLATFORM_TICKET); throw new BadRequestException ("获取component_access_token失败,请重试,接口返回信息:" + data.toJSONString()); }
3、获取authorizer_access_token
步骤:
获取预授权码pre_auth_code——>微信公众平台管理员授权——>获取authorizer_access_token&authorizer_refresh_token
官方文档:https://developers.weixin.qq.com/doc/oplatform/Third-party_Platforms/2.0/api/Before_Develop/Authorization_Process_Technical_Description.html
①获取预授权码pre_auth_code
1 2 3 4 5 6 7 8 9 10 11 12 13 @Override public String getPreAuthCode () { getComponentAccessToken(); String preAuthCodeUrl = "https://api.weixin.qq.com/cgi-bin/component/api_create_preauthcode?component_access_token=" + redisUtils.get(CacheKey.COMPONENT_ACCESS_TOKEN); JSONObject params = new JSONObject (); params.put("component_appid" , APPID); JSONObject data = JSONObject.parseObject(HttpUtil.post(preAuthCodeUrl, JSON.toJSONString(params))); if (data.containsKey("pre_auth_code" )) { return data.getString("pre_auth_code" ); } throw new BadRequestException ("获取预授权失败,接口返回信息:" + data.toJSONString()); }
②微信公众平台管理员授权
生成授权链接,可以在前端用a标签填入这个接口地址,点击a标签打开新标签页,管理员进行扫码授权
1 2 3 4 5 6 7 8 @Override public void toAuthorization (HttpServletResponse response) throws IOException { String authUrl = "https://mp.weixin.qq.com/cgi-bin/componentloginpagecomponent_appid=%s&pre_auth_code=%s&redirect_uri=%s&auth_type=1" ; String redirectUrl = "https://服务端地址/callback" ; String preAuthCode = getPreAuthCode(); response.setStatus(301 ); response.sendRedirect(String.format(authUrl, APPID, preAuthCode, redirectUrl)); }
授权之后,会回调:“https://服务端地址/callback ”,并将授权码auth_code携带到这个接口
1 2 3 4 5 6 @ApiOperation(value = "微信开放平台:用户授权后,回调地址", notes = "zhuguangliang") @AnonymousGetMapping("/callback") @Notice("2393194918@qq.com") public void callback (String auth_code) { }
③获取authorizer_access_token&authorizer_refresh_token
官方文档:https://developers.weixin.qq.com/doc/oplatform/Third-party_Platforms/2.0/api/ThirdParty/token/authorization_info.html
说明:authorization_code就是授权之后,返回的授权码
如果你使用微信的第三方平台管理工具,可以在数据库中直接获取authorizer_refresh_token,然后根据:
获取/刷新接口调用令牌 | 微信开放文档 (qq.com) 这个接口进行刷新authorizer_access_token,下面是实现代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 @Override public void getAuthorizationInfo (String authorizerAppId) { String key = CacheKey.AUTH_ACCESS_TOKEN + authorizerAppId; if (redisUtils.hasKey(key) && redisUtils.getExpire(key) > 5 * 60 ) { return ; } getComponentAccessToken(); String refreshTokenUrl = "https://api.weixin.qq.com/cgi-bin/component/api_authorizer_token?component_access_token=" + redisUtils.get(CacheKey.COMPONENT_ACCESS_TOKEN); JSONObject param = new JSONObject (); param.put("component_appid" , APPID); param.put("authorizer_appid" , authorizerAppId); String refreshToken; if (redisUtils.hasKey(CacheKey.AUTH_REFRESH_TOKEN + authorizerAppId)) { refreshToken = (String) redisUtils.get(CacheKey.AUTH_REFRESH_TOKEN + authorizerAppId); } else { refreshToken = platformMapper.selectRefreshTokenByAppId(authorizerAppId); if (StringUtils.isBlank(refreshToken)) { throw new BadRequestException ("小程序管理员未授权,请让小程序管理员重新授权" ); } LocalDateTime authTime = platformMapper.selectAuthTimeByAppId(authorizerAppId).toLocalDateTime(); Duration duration = Duration.between(authTime, LocalDateTime.now()); redisUtils.set(CacheKey.AUTH_REFRESH_TOKEN + authorizerAppId, refreshToken, 60 * 60 * 24 * 30 - (duration.toMillis() / 1000 + 60 )); } param.put("authorizer_refresh_token" , refreshToken); JSONObject tokenData = JSONObject.parseObject(HttpUtil.post(refreshTokenUrl, JSON.toJSONString(param))); if (tokenData.containsKey("authorizer_access_token" )) { redisUtils.set(CacheKey.AUTH_ACCESS_TOKEN + authorizerAppId, tokenData.getString("authorizer_access_token" ), 2 * 60 * 60 ); LambdaUpdateWrapper<SpiritOpenPlatform> updateWrapper = new LambdaUpdateWrapper <>(); updateWrapper.eq(SpiritOpenPlatform::getAppid, authorizerAppId); SpiritOpenPlatform openPlatform = new SpiritOpenPlatform (); openPlatform.setAuthorizerRefreshToken(refreshToken); openPlatform.setAuthorizerAccessToken(tokenData.getString("authorizer_access_token" )); platformMapper.update(openPlatform, updateWrapper); return ; } throw new BadRequestException ("获取authorizer_access_token失败,接口返回信息:" + tokenData.toJSONString()); }
到此,微信第三方平台开发中的关键token,authorizer_access_token已经成功获取
4、调接口发布小程序
提交代码接口:上传小程序代码并生成体验版 | 微信开放文档 (qq.com)
设置用户隐私接口:配置小程序用户隐私保护指引 | 微信开放文档 (qq.com)
提交审核接口:提交审核 | 微信开放文档 (qq.com)
查询审核结果接口:查询最新一次提交的审核状态 | 微信开放文档 (qq.com)
最后
总结:微信第三方平台开发的流程,涉及的接口有点多,微信的文档其实也写的很详细了,但是在开发过程中,免不了遇见莫名其妙的问题,这个时候可以在微信开放社区里找找或者搜下博客。