微信第三方平台开发
文章目錄
- 需求
- 實(shí)現(xiàn)
- 步驟:獲取component_verify_ticket——>獲取component_access_token——>獲取authorizer_access_token——>調(diào)接口發(fā)布小程序
- 1、獲取component_verify_ticket
- controller
- 實(shí)現(xiàn)類
- 2、獲取component_access_token
- 實(shí)現(xiàn)類
- 3、獲取authorizer_access_token
- 步驟:
- 獲取預(yù)授權(quán)碼pre_auth_code——>微信公眾平臺管理員授權(quán)——>獲取authorizer_access_token&authorizer_refresh_token
- ①獲取預(yù)授權(quán)碼pre_auth_code
- ②微信公眾平臺管理員授權(quán)
- ③獲取authorizer_access_token&authorizer_refresh_token
- 4、調(diào)接口發(fā)布小程序
- 提交代碼接口:[上傳小程序代碼并生成體驗(yàn)版 | 微信開放文檔 (qq.com)](https://developers.weixin.qq.com/doc/oplatform/Third-party_Platforms/2.0/api/code/commit.html)
- 設(shè)置用戶隱私接口:[配置小程序用戶隱私保護(hù)指引 | 微信開放文檔 (qq.com)](https://developers.weixin.qq.com/doc/oplatform/Third-party_Platforms/2.0/api/privacy_config/set_privacy_setting.html)
- 提交審核接口:[提交審核 | 微信開放文檔 (qq.com)](https://developers.weixin.qq.com/doc/oplatform/Third-party_Platforms/2.0/api/code/submit_audit.html)
- 查詢審核結(jié)果接口:[查詢最新一次提交的審核狀態(tài) | 微信開放文檔 (qq.com)](https://developers.weixin.qq.com/doc/oplatform/Third-party_Platforms/2.0/api/code/get_latest_auditstatus.html)
- 最后
需求
根據(jù)現(xiàn)有的平臺小程序,快速發(fā)布一個(gè)相同類型的小程序
如果按常規(guī)的做法每個(gè)小程序都需要經(jīng)歷這樣的步驟:在公眾平臺申請appid——>拉代碼到自己的倉庫——>打開編譯器綁定自己的appId——>上傳代碼到公眾平臺——>再提交審核——>在公眾平臺調(diào)整開發(fā)設(shè)置。
如果用微信開放平臺的服務(wù)平臺開發(fā)的步驟:在微信開放平臺申請第三方服務(wù)平臺——>上傳小程序模板(只有第一次有)——>調(diào)接口上傳小程序代碼
——>調(diào)接口提交審核——>調(diào)接口發(fā)布小程序。
上傳小程序模板的步驟:在公眾平臺申請appid——>在微信開放平臺的第三方服務(wù)平臺中添加開發(fā)小程序——>上傳小程序代碼(此時(shí)會直接上傳到了開放平臺的草稿箱)——>將草稿箱的代碼作為普通模板
這就是服務(wù)平臺開發(fā)平臺的便捷,只需上傳一次小程序代碼,就可以快速的發(fā)布小程序。
實(shí)現(xiàn)
步驟:獲取component_verify_ticket——>獲取component_access_token——>獲取authorizer_access_token——>調(diào)接口發(fā)布小程序
1、獲取component_verify_ticket
官方文檔:驗(yàn)證票據(jù) | 微信開放文檔 (qq.com)
因?yàn)檫@個(gè)驗(yàn)證票據(jù)是微信官方主動推送的,所以需要在第三方平臺配置一波
controller
@ApiOperation(value = "微信開放平臺:授權(quán)事件接收URL,驗(yàn)證票據(jù)", 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); }實(shí)現(xiàn)類
關(guān)于存儲component_verify_ticket方案,我這里是mysql和redis各存一份。
public String parseRequest(String timeStamp, String nonce, String msgSignature, String postData) {try {if (redisUtils.hasKey(CacheKey.OPEN_PLATFORM_TICKET)) {return "success";}//這個(gè)類是微信官網(wǎng)提供的解密類,需要用到消息校驗(yàn)Token 消息加密Key和服務(wù)平臺appidWXBizMsgCrypt pc = new WXBizMsgCrypt(Token, Key, APPID);String xml = pc.decryptMsg(msgSignature, timeStamp, nonce, postData);Map<String, String> result = WXXmlToMapUtil.xmlToMap(xml);// 將xml轉(zhuǎn)為mapString componentVerifyTicket = result.get("ComponentVerifyTicket");if (StringUtils.isNotEmpty(componentVerifyTicket)) {// 存儲平臺授權(quán)票據(jù),保存ticketSpiritOpenPlatform 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("微信開放平臺,第三方平臺獲取【驗(yàn)證票據(jù)】成功");} else {log.error("微信開放平臺,第三方平臺獲取【驗(yàn)證票據(jù)】失敗");}} catch (AesException e) {log.error("微信開放平臺,第三方平臺獲取【驗(yàn)證票據(jù)】失敗,異常信息:" + e.getMessage());}return "success";}官方提供的解密類:WXBizMsgCrypt.java
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;/*** 提供接收和推送給公眾平臺消息的加解密接口(UTF8編碼的字符串).* <ol> * <li>第三方回復(fù)加密消息給公眾平臺</li> * <li>第三方收到公眾平臺發(fā)送的消息,驗(yàn)證消息的安全性,并對消息進(jìn)行解密。</li>* </ol>* 說明:異常java.security.InvalidKeyException:illegal Key Size的解決方案* <ol>* <li>在官方網(wǎng)站下載JCE無限制權(quán)限策略文件(JDK7的下載地址: ** http://www.oracle.com/technetwork/java/javase/downloads/jce-7-download-432124.html</li>* <li>下載后解壓,可以看到local_policy.jar和US_export_policy.jar以及readme.txt</li>* <li>如果安裝了JRE,將兩個(gè)jar文件放到%JRE_HOME%\lib\security目錄下覆蓋原來的文件</li>* <li>如果安裝了JDK,將兩個(gè)jar文件放到%JDK_HOME%\jre\lib\security目錄下覆蓋原來文件</li>** </ol>*/ public class WXBizMsgCrypt {static Charset CHARSET = Charset.forName("utf-8");Base64 base64 = new Base64();byte[] aesKey;String token;String appId;/*** 構(gòu)造函數(shù)** @param token 公眾平臺上,開發(fā)者設(shè)置的token* @param encodingAesKey 公眾平臺上,開發(fā)者設(shè)置的EncodingAESKey* @param appId 公眾平臺appid* @throws AesException 執(zhí)行失敗,請查看該異常的錯誤碼和具體的錯誤信息*/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 + "=");}// 還原4個(gè)字節(jié)的網(wǎng)絡(luò)字節(jié)序int recoverNetworkBytesOrder(byte[] orderBytes) {int sourceNumber = 0;for (int i = 0; i < 4; i++) {sourceNumber <<= 8;sourceNumber |= orderBytes[i] & 0xff;}return sourceNumber;}/*** 對密文進(jìn)行解密.** @param text 需要解密的密文* @return 解密得到的明文* @throws AesException aes解密失敗*/String decrypt(String text) throws AesException {byte[] original;try {// 設(shè)置解密模式為AES的CBC模式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);// 使用BASE64對密文進(jìn)行解碼byte[] encrypted = Base64.decodeBase64(text);// 解密original = cipher.doFinal(encrypted);} catch (Exception e) {e.printStackTrace();throw new AesException(AesException.DecryptAESError);}String xmlContent, from_appid;try {// 去除補(bǔ)位字符byte[] bytes = PKCS7Encoder.decode(original);// 分離16位隨機(jī)字符串,網(wǎng)絡(luò)字節(jié)序和AppIdbyte[] 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);}// appid不相同的情況if (!from_appid.equals(appId)) {throw new AesException(AesException.ValidateSignatureError);}return xmlContent;}/*** * 檢驗(yàn)消息的真實(shí)性,并且獲取解密后的明文.* <ol>* <li>利用收到的密文生成安全簽名,進(jìn)行簽名驗(yàn)證</li>* <li>若驗(yàn)證通過,則提取xml中的加密消息</li>* <li>對消息進(jìn)行解密</li>* </ol>** @param msgSignature 簽名串,對應(yīng)URL參數(shù)的msg_signature* @param timeStamp 時(shí)間戳,對應(yīng)URL參數(shù)的timestamp* @param nonce 隨機(jī)串,對應(yīng)URL參數(shù)的nonce* @param postData 密文,對應(yīng)POST請求的數(shù)據(jù)* @return 解密后的原文* @throws AesException 執(zhí)行失敗,請查看該異常的錯誤碼和具體的錯誤信息*/public String decryptMsg(String msgSignature, String timeStamp, String nonce, String postData)throws AesException {// 密鑰,公眾賬號的app secret// 提取密文Object[] encrypt = extract(postData);// 驗(yàn)證安全簽名String signature = getSHA1(token, timeStamp, nonce, encrypt[1].toString());// 和URL中的簽名比較是否相等// System.out.println("第三方收到URL中的簽名:" + msg_sign);// System.out.println("第三方校驗(yàn)簽名:" + signature);if (!signature.equals(msgSignature)) {throw new AesException(AesException.ValidateSignatureError);}// 解密String result = decrypt(encrypt[1].toString());return result;}/*** 提取出xml數(shù)據(jù)包中的加密消息** @param xmltext 待提取的xml字符串* @return 提取出的加密消息字符串* @throws AesException*/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();//注意這里,獲取ticket中的xml里面沒有ToUserName這個(gè)元素,官網(wǎng)原示例代碼在這里會報(bào)空//空指針,所以需要處理一下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);}}/*** 用SHA1算法生成安全簽名** @param token 票據(jù)* @param timestamp 時(shí)間戳* @param nonce 隨機(jī)字符串* @param encrypt 密文* @return 安全簽名* @throws AesException*/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();// SHA1簽名生成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
import java.nio.charset.Charset; import java.util.Arrays;/*** 提供基于PKCS7算法的加解密接口.*/ public class PKCS7Encoder {static Charset CHARSET = Charset.forName("utf-8");static int BLOCK_SIZE = 32;/*** 獲得對明文進(jìn)行補(bǔ)位填充的字節(jié).** @param count 需要進(jìn)行填充補(bǔ)位操作的明文字節(jié)個(gè)數(shù)* @return 補(bǔ)齊用的字節(jié)數(shù)組*/static byte[] encode(int count) {// 計(jì)算需要填充的位數(shù)int amountToPad = BLOCK_SIZE - (count % BLOCK_SIZE);if (amountToPad == 0) {amountToPad = BLOCK_SIZE;}// 獲得補(bǔ)位所用的字符char padChr = chr(amountToPad);String tmp = new String();for (int index = 0; index < amountToPad; index++) {tmp += padChr;}return tmp.getBytes(CHARSET);}/*** 刪除解密后明文的補(bǔ)位字符** @param decrypted 解密后的明文* @return 刪除補(bǔ)位字符后的明文*/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);}/*** 將數(shù)字轉(zhuǎn)化成ASCII碼對應(yīng)的字符,用于對明文進(jìn)行補(bǔ)碼** @param a 需要轉(zhuǎn)化的數(shù)字* @return 轉(zhuǎn)化得到的字符*/static char chr(int a) {byte target = (byte) (a & 0xFF);return (char) target;} }WXXmlToMapUtil.java
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);/*** XML格式字符串轉(zhuǎn)換為Map** @param xml XML字符串* @return XML數(shù)據(jù)轉(zhuǎn)換后的Map*/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;}}/*** 將Map轉(zhuǎn)換為XML格式的字符串** @param data Map類型數(shù)據(jù)* @return XML格式的字符串*/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(); //.replaceAll("\n|\r", "");writer.close();return output;} catch (Exception e) {e.printStackTrace();return null;}}/*** (多層)xml格式字符串轉(zhuǎn)換為map** @param xml xml字符串* @return 第一個(gè)為Root節(jié)點(diǎn),Root節(jié)點(diǎn)之后為Root的元素,如果為多層,可以通過key獲取下一層Map*/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;}/*** multilayerXmlToMap核心方法,遞歸調(diào)用** @param element 節(jié)點(diǎn)元素* @param outmap 用于存儲xml數(shù)據(jù)的map*/private static void recursionXmlToMap(Element element, Map<String, Object> outmap) {// 得到根元素下的子元素列表List<Element> list = element.elements();int size = list.size();if (size == 0) {// 如果沒有子元素,則將其存儲進(jìn)map中outmap.put(element.getName(), element.getTextTrim());} else {// innermap用于存儲子元素的屬性名和屬性值Map<String, Object> innermap = new HashMap<>();// 遍歷子元素list.forEach(childElement -> recursionXmlToMap(childElement, innermap));outmap.put(element.getName(), innermap);}}/*** (多層)map轉(zhuǎn)換為xml格式字符串** @param map 需要轉(zhuǎn)換為xml的map* @param isCDATA 是否加入CDATA標(biāo)識符 true:加入 false:不加入* @return xml字符串*/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);}/*** multilayerMapToXml核心方法,遞歸調(diào)用** @param element 節(jié)點(diǎn)元素* @param parentName 根元素屬性名* @param map 需要轉(zhuǎn)換為xml的map* @param isCDATA 是否加入CDATA標(biāo)識符 true:加入 false:不加入* @return 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();}/*** 格式化xml,顯示為容易看的XML格式** @param xml 需要格式化的xml字符串*/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);// xml聲明與內(nèi)容是否添加空行format.setNewLineAfterDeclaration(false);// 是否設(shè)置xml聲明頭部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
@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 "簽名驗(yàn)證錯誤";case ParseXmlError:return "xml解析失敗";case ComputeSignatureError:return "sha加密生成簽名失敗";case IllegalAesKey:return "SymmetricKey非法";case ValidateCorpidError:return "corpid校驗(yàn)失敗";case EncryptAESError:return "aes加密失敗";case DecryptAESError:return "aes解密失敗";case IllegalBuffer:return "解密后得到的buffer非法";default:return null; // cannot be}}public int getCode() {return code;}AesException(int code) {super(getMessage(code));this.code = code;}}2、獲取component_access_token
官方文檔:令牌 | 微信開放文檔 (qq.com)
實(shí)現(xiàn)類
@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();//第三方平臺的appid、appsecretparams.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 {//查詢數(shù)據(jù)庫中的component_verify_ticketcomponent_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("微信開放平臺,第三方平臺獲取【驗(yàn)證票據(jù)】失敗");}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");//將component_access_token存入redis中,并設(shè)置2個(gè)小時(shí)的過期時(shí)間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
步驟:
獲取預(yù)授權(quán)碼pre_auth_code——>微信公眾平臺管理員授權(quán)——>獲取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
①獲取預(yù)授權(quán)碼pre_auth_code
@Override public String getPreAuthCode() {//先判斷component_access_token是否過期,如果過期重新獲取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("獲取預(yù)授權(quán)失敗,接口返回信息:" + data.toJSONString()); }②微信公眾平臺管理員授權(quán)
生成授權(quán)鏈接,可以在前端用a標(biāo)簽填入這個(gè)接口地址,點(diǎn)擊a標(biāo)簽打開新標(biāo)簽頁,管理員進(jìn)行掃碼授權(quán)
@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://服務(wù)端地址/callback";String preAuthCode = getPreAuthCode();response.setStatus(301);response.sendRedirect(String.format(authUrl, APPID, preAuthCode, redirectUrl)); }授權(quán)之后,會回調(diào):“https://服務(wù)端地址/callback”,并將授權(quán)碼auth_code攜帶到這個(gè)接口
@ApiOperation(value = "微信開放平臺:用戶授權(quán)后,回調(diào)地址", notes = "zhuguangliang")@AnonymousGetMapping("/callback")@Notice("2393194918@qq.com")public void callback(String auth_code) { // openPlatformService.getAuthorizationInfo(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就是授權(quán)之后,返回的授權(quán)碼
如果你使用微信的第三方平臺管理工具,可以在數(shù)據(jù)庫中直接獲取authorizer_refresh_token,然后根據(jù):
獲取/刷新接口調(diào)用令牌 | 微信開放文檔 (qq.com)這個(gè)接口進(jìn)行刷新authorizer_access_token,下面是實(shí)現(xiàn)代碼:
@Override//authorizerAppId為授權(quán)的appid public void getAuthorizationInfo(String authorizerAppId) {String key = CacheKey.AUTH_ACCESS_TOKEN + authorizerAppId;//這一步是為如果發(fā)現(xiàn)這個(gè)token快過期,則刷新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 {//從微信第三方管理工具的數(shù)據(jù)庫中查詢出authorizer_refresh_tokenrefreshToken = platformMapper.selectRefreshTokenByAppId(authorizerAppId);if (StringUtils.isBlank(refreshToken)) {throw new BadRequestException("小程序管理員未授權(quán),請讓小程序管理員重新授權(quán)");}//計(jì)算刷新令牌的過期時(shí)間//從微信第三方管理工具的數(shù)據(jù)庫中查詢出authorizer_refresh_token的過期時(shí)間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()); }到此,微信第三方平臺開發(fā)中的關(guān)鍵token,authorizer_access_token已經(jīng)成功獲取
4、調(diào)接口發(fā)布小程序
提交代碼接口:上傳小程序代碼并生成體驗(yàn)版 | 微信開放文檔 (qq.com)
設(shè)置用戶隱私接口:配置小程序用戶隱私保護(hù)指引 | 微信開放文檔 (qq.com)
提交審核接口:提交審核 | 微信開放文檔 (qq.com)
查詢審核結(jié)果接口:查詢最新一次提交的審核狀態(tài) | 微信開放文檔 (qq.com)
- 項(xiàng)目中可以采用定時(shí)任務(wù)的策略調(diào)用該接口
最后
總結(jié):微信第三方平臺開發(fā)的流程,涉及的接口有點(diǎn)多,微信的文檔其實(shí)也寫的很詳細(xì)了,但是在開發(fā)過程中,免不了遇見莫名其妙的問題,這個(gè)時(shí)候可以在微信開放社區(qū)里找找或者搜下博客。
總結(jié)
- 上一篇: 新版Microsoft Edge支持跨平
- 下一篇: 百度网盘7.3.1.10版本增加工作空间