什么是第三方开放平台
来波官方解释:
我才是官方文档
简单的说,就是让公众号授权给第三个开放平台,根据授权不同,第三开放平台可以获取到该公众号的接口权限,从而直接调用微信api,进行公众号开发;
开通创建流程
开发者资质审核通过后,就可以创建第三方开放平台了,由于我们第三方开放平台已经建立了。就不演示创建平台的过程了,下面解释下填写的资料
- 第一步基本资料填写,不做多余说明,填写基本资料即可
- 第二步选择权限集,大概意思就是 选择下 你这个第三方开放平台 要代替公众号实现那些业务 获取公众号的那些接口权限,需要注意的是首先要确保该公众号已经有了这个权限 我是官方文档
- 第三步是填写开发资料,基本上就是域名,白名单一些的配置了 这里官方文档写的也比较清楚 不懂看这里
开始开发
首先要做的 是授权事件接收URL的处理,用于接收取消授权通知、授权成功通知、授权更新通知,也用于接收ticket,ticket是验证平台方的重要凭据,服务方在获取component_access_token时需要提供最新推送的ticket以供验证身份合法性。此ticket作为验证服务方的重要凭据,请妥善保存。
权限变更这些可以先不考虑,先考虑的是 接受这个ticket,只有拥有了ticket才能去换取第三方平台的token
下面是主要代码。实体类这些不提供了,根据需求可以自行创建
1 package com.ysh.wxtest.controller; 2 3 import java.io.BufferedReader; 4 import java.io.IOException; 5 import java.io.PrintWriter; 6 import java.security.MessageDigest; 7 import java.security.NoSuchAlgorithmException; 8 import java.util.Arrays; 9 import java.util.Date; 10 import java.util.List; 11 12 import javax.servlet.http.HttpServletRequest; 13 import javax.servlet.http.HttpServletResponse; 14 import javax.servlet.http.HttpSession; 15 import javax.websocket.Session; 16 17 import org.apache.commons.lang.StringUtils; 18 import org.dom4j.Document; 19 import org.dom4j.DocumentException; 20 import org.dom4j.DocumentHelper; 21 import org.dom4j.Element; 22 import org.springframework.beans.factory.annotation.Autowired; 23 import org.springframework.stereotype.Controller; 24 import org.springframework.web.bind.annotation.RequestMapping; 25 import org.springframework.web.bind.annotation.ResponseBody; 26 import org.springframework.web.servlet.ModelAndView; 27 28 import com.qq.weixin.mp.aes.AesException; 29 import com.qq.weixin.mp.aes.WXBizMsgCrypt; 30 import com.ysh.wxtest.model.Users; 31 import com.ysh.wxtest.model.Wxauthinfo; 32 import com.ysh.wxtest.model.Wxauthorizerinfo; 33 import com.ysh.wxtest.model.Wxcomtoken; 34 import com.ysh.wxtest.model.Wxticket; 35 import com.ysh.wxtest.service.WxauthinfoService; 36 import com.ysh.wxtest.service.WxauthorizerinfoService; 37 import com.ysh.wxtest.service.WxcomtokenService; 38 import com.ysh.wxtest.service.WxticketService; 39 import com.ysh.wxtest.util.AjaxMessage; 40 41 import common.Logger; 42 import weixin.popular.bean.component.*; 43 import weixin.popular.util.XMLConverUtil; 44 45 46 /** 47 * 微信 第三方开放平台 controller层 48 * @author YaoShiHang 49 * 50 */ 51 @Controller 52 public class WeixinAccreditController { 53 54 @Autowired 55 private WxticketService wxticketService; //微信推送 ticket服务,用于保存 ticket 56 @Autowired 57 private WxcomtokenService wxcomtokenService; //微信第三方平台 token服务 ,有效期2小时。获取次数有限 注意缓存 58 @Autowired 59 private WxauthinfoService wxauthinfoService; //微信授权信息 服务 60 @Autowired 61 private WxauthorizerinfoService WxinfoService; 62 63 private final String APPID = "???"; 64 65 private Logger log= Logger.getLogger(getClass()); 66 67 /** 68 * 微信全网测试账号 69 */ 70 private final static String COMPONENT_APPID = "XXXXXXXXXXXX"; //第三方平台 APPID 71 private final String COMPONENT_APPSECRET = "XXXXXXXXXXXX"; //第三方平台 秘钥 72 private final static String COMPONENT_ENCODINGAESKEY = "XXXXXXXXXXXX"; //开发者 设置的 key 73 private final static String COMPONENT_TOKEN = "XXXXXXXXXXXX"; //开发者 设置的 token 74 75 // 授权事件接受url 每隔10分钟 获取微信服务器推送ticket 接收后需要解密 接收到后 必须返回字符串success 76 @RequestMapping("/openwx/getticket") 77 public void getTicket(HttpServletRequest request, HttpServletResponse response) 78 throws IOException, DocumentException, AesException { 79 processAuthorizeEvent(request); 80 output(response, "success"); // 输出响应的内容。 81 } 82 83 /** 84 * 授权事件处理 85 * @param request 86 * @throws IOException 87 * @throws DocumentException 88 * @throws AesException 89 */ 90 public void processAuthorizeEvent(HttpServletRequest request) throws IOException, DocumentException, AesException { 91 String nonce = request.getParameter("nonce"); 92 String timestamp = request.getParameter("timestamp"); 93 String signature = request.getParameter("signature"); 94 String msgSignature = request.getParameter("msg_signature"); 95 HttpSession session = request.getSession(); 96 if (!StringUtils.isNotBlank(msgSignature)){ //判断消息是否空 97 return;// 微信推送给第三方开放平台的消息一定是加过密的,无消息加密无法解密消息 98 } 99 boolean isValid = checkSignature(COMPONENT_TOKEN, signature, timestamp, nonce); 100 if (isValid) { 101 StringBuilder sb = new StringBuilder(); 102 BufferedReader in = request.getReader(); 103 String line; 104 while ((line = in.readLine()) != null) { 105 sb.append(line); 106 } 107 String xml = sb.toString(); 108 log.info("第三方平台全网发布-----------------------原始 Xml="+xml); 109 String encodingAesKey = COMPONENT_ENCODINGAESKEY;// 第三方平台组件加密密钥 110 String appId = (xml);// 此时加密的xml数据中ToUserName是非加密的,解析xml获取即可 111 log.info("第三方平台全网发布-------------appid----------getAuthorizerAppidFromXml(xml)-----------appId="+appId); 112 WXBizMsgCrypt pc = new WXBizMsgCrypt(COMPONENT_TOKEN, encodingAesKey, COMPONENT_APPID); 113 xml = pc.decryptMsg(msgSignature, timestamp, nonce, xml); 114 log.info("第三方平台全网发布-----------------------解密后 Xml="+xml); 115 ComponentReceiveXML com = XMLConverUtil.convertToObject(ComponentReceiveXML.class, xml); 116 session.setAttribute("com",com); 117 processAuthorizationEvent(xml); 118 } 119 } 120 /** 121 * 保存Ticket 122 * @param xml 123 */ 124 void processAuthorizationEvent(String xml) { 125 Document doc; 126 127 try { 128 doc = DocumentHelper.parseText(xml); 129 Element rootElt = doc.getRootElement(); 130 String ticket = rootElt.elementText("ComponentVerifyTicket"); 131 if(ticket!=null){ 132 Wxticket wxticket = new Wxticket(); 133 wxticket.setAppid(APPID); 134 wxticket.setAddtime(new Date());; 135 wxticket.setId(1l); 136 wxticket.setTicket(ticket); 137 wxticketService.updateNotNull(wxticket); 138 } 139 } catch (DocumentException e) { 140 e.printStackTrace(); 141 } 142 } 143 144 String getAuthorizerAppidFromXml(String xml) { 145 Document doc; 146 try { 147 doc = DocumentHelper.parseText(xml); 148 Element rootElt = doc.getRootElement(); 149 String toUserName = rootElt.elementText("ToUserName"); 150 return toUserName; 151 } catch (DocumentException e) { 152 // TODO Auto-generated catch block 153 e.printStackTrace(); 154 } 155 return null; 156 } 157 158 /** 159 * 判断消息是否加密 160 * @param token 161 * @param signature 162 * @param timestamp 163 * @param nonce 164 * @return 165 */ 166 public static boolean checkSignature(String token, String signature, String timestamp, String nonce) { 167 System.out.println( 168 "###token:" + token + ";signature:" + signature + ";timestamp:" + timestamp + "nonce:" + nonce); 169 boolean flag = false; 170 if (signature != null && !signature.equals("") && timestamp != null && !timestamp.equals("") && nonce != null 171 && !nonce.equals("")) { 172 String sha1 = ""; 173 String[] ss = new String[] { token, timestamp, nonce }; 174 Arrays.sort(ss); 175 for (String s : ss) { 176 sha1 += s; 177 } 178 sha1 = AddSHA1.SHA1(sha1); 179 if (sha1.equals(signature)) { 180 flag = true; 181 } 182 } 183 return flag; 184 } 185 /** 186 * 工具类:回复微信服务器"文本消息" 187 * @param response 188 * @param returnvaleue 189 */ 190 public void output(HttpServletResponse response, String returnvaleue) { 191 try { 192 PrintWriter pw = response.getWriter(); 193 pw.write(returnvaleue); 194 System.out.println("****************returnvaleue***************="+returnvaleue); 195 pw.flush(); 196 } catch (IOException e) { 197 e.printStackTrace(); 198 } 199 } 200 201 } 202 203 class AddSHA1 { 204 public static String SHA1(String inStr) { 205 MessageDigest md = null; 206 String outStr = null; 207 try { 208 md = MessageDigest.getInstance("SHA-1"); // 选择SHA-1,也可以选择MD5 209 byte[] digest = md.digest(inStr.getBytes()); // 返回的是byet[],要转化为String存储比较方便 210 outStr = bytetoString(digest); 211 } catch (NoSuchAlgorithmException nsae) { 212 nsae.printStackTrace(); 213 } 214 return outStr; 215 } 216 public static String bytetoString(byte[] digest) { 217 String str = ""; 218 String tempStr = ""; 219 for (int i = 0; i < digest.length; i++) { 220 tempStr = (Integer.toHexString(digest[i] & 0xff)); 221 if (tempStr.length() == 1) { 222 str = str + "0" + tempStr; 223 } else { 224 str = str + tempStr; 225 } 226 } 227 return str.toLowerCase(); 228 } 229 }
提供一个xml 解析工具类
1 import java.io.IOException; 2 import java.io.InputStream; 3 import java.io.InputStreamReader; 4 import java.io.Reader; 5 import java.io.StringReader; 6 import java.io.StringWriter; 7 import java.io.Writer; 8 import java.util.HashMap; 9 import java.util.LinkedHashMap; 10 import java.util.Map; 11 12 import javax.xml.bind.JAXBContext; 13 import javax.xml.bind.JAXBException; 14 import javax.xml.bind.Marshaller; 15 import javax.xml.bind.Unmarshaller; 16 import javax.xml.parsers.DocumentBuilder; 17 import javax.xml.parsers.DocumentBuilderFactory; 18 import javax.xml.parsers.ParserConfigurationException; 19 20 import org.w3c.dom.DOMException; 21 import org.w3c.dom.Document; 22 import org.w3c.dom.Element; 23 import org.w3c.dom.Node; 24 import org.w3c.dom.NodeList; 25 import org.xml.sax.InputSource; 26 import org.xml.sax.SAXException; 27 28 import com.sun.xml.bind.marshaller.CharacterEscapeHandler; 29 30 /** 31 * XML 数据接收对象转换工具类 32 * @author LiYi 33 * 34 */ 35 public class XMLConverUtil{ 36 37 private static final ThreadLocal<Map<Class<?>,Marshaller>> mMapLocal = new ThreadLocal<Map<Class<?>,Marshaller>>() { 38 @Override 39 protected Map<Class<?>, Marshaller> initialValue() { 40 return new HashMap<Class<?>, Marshaller>(); 41 } 42 }; 43 44 private static final ThreadLocal<Map<Class<?>,Unmarshaller>> uMapLocal = new ThreadLocal<Map<Class<?>,Unmarshaller>>(){ 45 @Override 46 protected Map<Class<?>, Unmarshaller> initialValue() { 47 return new HashMap<Class<?>, Unmarshaller>(); 48 } 49 }; 50 51 /** 52 * XML to Object 53 * @param <T> T 54 * @param clazz clazz 55 * @param xml xml 56 * @return T 57 */ 58 public static <T> T convertToObject(Class<T> clazz,String xml){ 59 return convertToObject(clazz,new StringReader(xml)); 60 } 61 62 /** 63 * XML to Object 64 * @param <T> T 65 * @param clazz clazz 66 * @param inputStream inputStream 67 * @return T 68 */ 69 public static <T> T convertToObject(Class<T> clazz,InputStream inputStream){ 70 return convertToObject(clazz,new InputStreamReader(inputStream)); 71 } 72 73 /** 74 * XML to Object 75 * @param <T> T 76 * @param clazz clazz 77 * @param reader reader 78 * @return T 79 */ 80 @SuppressWarnings("unchecked") 81 public static <T> T convertToObject(Class<T> clazz,Reader reader){ 82 try { 83 Map<Class<?>, Unmarshaller> uMap = uMapLocal.get(); 84 if(!uMap.containsKey(clazz)){ 85 JAXBContext jaxbContext = JAXBContext.newInstance(clazz); 86 Unmarshaller unmarshaller = jaxbContext.createUnmarshaller(); 87 uMap.put(clazz, unmarshaller); 88 } 89 return (T) uMap.get(clazz).unmarshal(reader); 90 } catch (JAXBException e) { 91 e.printStackTrace(); 92 } 93 return null; 94 } 95 96 /** 97 * Object to XML 98 * @param object object 99 * @return xml 100 */ 101 public static String convertToXML(Object object){ 102 try { 103 Map<Class<?>, Marshaller> mMap = mMapLocal.get(); 104 if(!mMap.containsKey(object.getClass())){ 105 JAXBContext jaxbContext = JAXBContext.newInstance(object.getClass()); 106 Marshaller marshaller = jaxbContext.createMarshaller(); 107 marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); 108 //设置CDATA输出字符 109 marshaller.setProperty(CharacterEscapeHandler.class.getName(), new CharacterEscapeHandler() { 110 public void escape(char[] ac, int i, int j, boolean flag, Writer writer) throws IOException { 111 writer.write(ac, i, j); 112 } 113 }); 114 mMap.put(object.getClass(), marshaller); 115 } 116 StringWriter stringWriter = new StringWriter(); 117 mMap.get(object.getClass()).marshal(object,stringWriter); 118 return stringWriter.getBuffer().toString(); 119 } catch (JAXBException e) { 120 e.printStackTrace(); 121 } 122 return null; 123 } 124 125 /** 126 * 转换简单的xml to map 127 * @param xml xml 128 * @return map 129 */ 130 public static Map<String,String> convertToMap(String xml){ 131 Map<String, String> map = new LinkedHashMap<String,String>(); 132 try { 133 DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); 134 DocumentBuilder db = dbf.newDocumentBuilder(); 135 StringReader sr = new StringReader(xml); 136 InputSource is = new InputSource(sr); 137 Document document = db.parse(is); 138 139 Element root = document.getDocumentElement(); 140 if(root != null){ 141 NodeList childNodes = root.getChildNodes(); 142 if(childNodes != null && childNodes.getLength()>0){ 143 for(int i = 0;i < childNodes.getLength();i++){ 144 Node node = childNodes.item(i); 145 if( node != null && node.getNodeType() == Node.ELEMENT_NODE){ 146 map.put(node.getNodeName(), node.getTextContent()); 147 } 148 } 149 } 150 } 151 } catch (DOMException e) { 152 e.printStackTrace(); 153 } catch (ParserConfigurationException e) { 154 e.printStackTrace(); 155 } catch (SAXException e) { 156 e.printStackTrace(); 157 } catch (IOException e) { 158 e.printStackTrace(); 159 } 160 return map; 161 } 162 }
这一步结束后 第三方开放平台就可以审核通过了。然后就是进行代公众号实现业务了,相对来说简单了很多,微信提供的都有相关文档。直接调用接口就行了。需要注意的是在首先要确保公众号获取了这个权限,其次确保公众号把这个权限授权给了第三方平台。在开发中代公众号实现网页授权的接口 假如使用第三方开放平台的话,接口地址是有所改变的,参考第三方开放平台开发文档,其他的接口 只需将原来公众号的token 改变为 通过第三方开放平台token 获取的公众号授权给第三方开放平台的token 两个token是不一样的。有效期都是2小时 需要缓存。最后上一波成功图片