lxzn-boot-base-core/src/main/java/org/jeecg/config/shiro/ShiroConfig.java
@@ -163,6 +163,8 @@ //æµè¯æ¨¡åæé¤ filterChainDefinitionMap.put("/test/seata/**", "anon"); //ä¼ä¸å¾®ä¿¡æ¶æ¯åè°æ¥å£ filterChainDefinitionMap.put("/qywx/message/callback/**", "anon"); // æ·»å èªå·±çè¿æ»¤å¨å¹¶ä¸åå为jwt lxzn-module-system/lxzn-system-biz/src/main/java/org/jeecg/modules/qywx/message/MessageAPI.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,26 @@ package org.jeecg.modules.qywx.message; import com.alibaba.fastjson.JSONObject; import com.jeecg.qywx.api.core.util.HttpUtil; import org.jeecg.modules.qywx.message.vo.TemplateCard; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class MessageAPI { private static final Logger logger = LoggerFactory.getLogger(MessageAPI.class); //åéæ¶æ¯ï¼postï¼ static String message_send_url="https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=ACCESS_TOKEN"; public static JSONObject sendTemplateCardMessage(TemplateCard templateCard, String accessToken) { logger.info("[MessageAPI] sendTemplateCardMessage params:accessToken:{},templateCard:{}", new Object[]{accessToken, templateCard}); // æ¼è£ åéä¿¡æ¯çurl String url = message_send_url.replace("ACCESS_TOKEN", accessToken); // å°ä¿¡æ¯å¯¹è±¡è½¬æ¢æjsonå符串 String params = JSONObject.toJSONString(templateCard); logger.info("[MessageAPI] sendTemplateCardMessage params:jsonText:{}", new Object[]{params}); // è°ç¨æ¥å£åéä¿¡æ¯ JSONObject jsonObject = HttpUtil.sendPost(url, params); logger.info("[MessageAPI] sendTemplateCardMessage response:{}", new Object[]{jsonObject.toJSONString()}); return jsonObject; } } lxzn-module-system/lxzn-system-biz/src/main/java/org/jeecg/modules/qywx/message/controller/WeComCallbackController.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,136 @@ package org.jeecg.modules.qywx.message.controller; import lombok.extern.slf4j.Slf4j; import org.apache.commons.codec.digest.DigestUtils; import org.jeecg.modules.qywx.message.utils.WXBizMsgCrypt; import org.springframework.web.bind.annotation.*; 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.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.nio.charset.StandardCharsets; import java.util.*; /** * ä¼ä¸å¾®ä¿¡æ¶æ¯åè°æ¥å£ï¼æ¯æ URL éªè¯ + æ¶æ¯æ¥æ¶ï¼ */ @RestController @RequestMapping("/qywx/message/callback") @Slf4j public class WeComCallbackController { // æ¿æ¢ä¸ºä½ çåºç¨é ç½® private static final String TOKEN = "bPGpQimYTtjAfAj2uTE4fb"; private static final String AES_KEY = "hkbscaEtxWAydMIoWe8EW4drpHXaYroQPKBMoKCs2bf"; // EncodingAESKey private static final String CORP_ID = "ww5999b2643c95fa75"; // ä¼ä¸ ID æ AppId private static WXBizMsgCrypt wxBizMsgCrypt; static { try { wxBizMsgCrypt = new WXBizMsgCrypt(TOKEN, AES_KEY, CORP_ID); } catch (Exception e) { throw new RuntimeException("åå§å WXBizMsgCrypt 失败ï¼è¯·æ£æ¥ Token/AESKey/AppId æ¯å¦æ£ç¡®", e); } } /** * GET 请æ±ï¼ç¨äº URL éªè¯ï¼Token æ ¡éªï¼ */ @GetMapping(produces = "text/plain;charset=utf-8") public String verifyUrl( @RequestParam("msg_signature") String msgSignature, @RequestParam("timestamp") String timestamp, @RequestParam("nonce") String nonce, @RequestParam("echostr") String echostr) { try { log.info("æ¶å°ä¼ä¸å¾®ä¿¡ URL éªè¯è¯·æ±ï¼msg_signature={}, timestamp={}, nonce={}, echostr={}", msgSignature, timestamp, nonce, echostr); // è°ç¨å®æ¹ SDK éªè¯å¹¶è§£å¯ String decryptedEchoStr = wxBizMsgCrypt.VerifyURL(msgSignature, timestamp, nonce, echostr); log.info("è§£å¯åç echostr: {}", decryptedEchoStr); // è¿åè§£å¯åçå 容ï¼ä¼ä¸å¾®ä¿¡è¦æ±å¿ é¡»åæ ·è¿åï¼ return decryptedEchoStr; } catch (Exception e) { log.error("URL éªè¯å¤±è´¥", e); return "fail"; } } /** * POST 请æ±ï¼æ¥æ¶ä¼ä¸å¾®ä¿¡æ¨éçæ¶æ¯ */ @PostMapping(produces = "text/xml;charset=utf-8") public String handleWeComCallback(@RequestBody String xmlData, @RequestParam("msg_signature") String msgSignature, @RequestParam("timestamp") String timestamp, @RequestParam("nonce") String nonce) { try { log.info("æ¶å°ä¼ä¸å¾®ä¿¡æ¶æ¯æ¨éï¼msg_signature={}, timestamp={}, nonce={}", msgSignature, timestamp, nonce); log.info("åå§ XML æ°æ®: {}", xmlData); // Step 1: è§£å¯æ¶æ¯ String plainXml = wxBizMsgCrypt.DecryptMsg(msgSignature, timestamp, nonce, xmlData); log.info("è§£å¯åçæææ¶æ¯: {}", plainXml); // Step 2: è§£æ XML å¹¶å¤çä¸å¡é»è¾ Map<String, String> msgMap = parseXmlToMap(plainXml); String eventType = msgMap.get("Event"); String content = msgMap.get("Content"); log.info("äºä»¶ç±»å: {}, æ¶æ¯å 容: {}", eventType, content); // Step 3: ä¸å¡å¤çï¼ç¤ºä¾ï¼åå¤ successï¼ String responseXml = "<xml><returncode>0</returncode><returndata>success</returndata></xml>"; return responseXml; } catch (Exception e) { log.error("å¤çä¼ä¸å¾®ä¿¡æ¶æ¯å¤±è´¥", e); return "<xml><returncode>1</returncode><returndata>fail</returndata></xml>"; } } /** * å° XML 转为 Map */ private Map<String, String> parseXmlToMap(String xml) throws Exception { Map<String, String> map = new HashMap<>(); DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = factory.newDocumentBuilder(); StringReader reader = new StringReader(xml); InputSource source = new InputSource(reader); Document doc = builder.parse(source); Element root = doc.getDocumentElement(); NodeList list = root.getChildNodes(); for (int i = 0; i < list.getLength(); i++) { if (list.item(i).getNodeType() == Document.ELEMENT_NODE) { Element element = (Element) list.item(i); map.put(element.getNodeName(), element.getTextContent()); } } return map; } //public static void main(String[] args) throws UnsupportedEncodingException { // String msg_signature = "eae930cbae5d988ed29f8aac6cf221016f81669e"; // String timestamp = "1751356163"; // String nonce = "16ljuxd4gd8"; // String echostr = "Ax30h%2BharWss%2FeNNQl8x4KdzggrpmLFH8i%2F0mHqVGbcKcq1NeascovZV%2B08ooq5o0ng8RJ2WPyo69A8oGhKpxA%3D%3D"; // // String decode = URLDecoder.decode(echostr, "UTF-8"); // //System.out.println(decode); // WeComCallbackController weComCallbackController = new WeComCallbackController(); // String decryptedEchoStr = weComCallbackController.verifyUrl(msg_signature, timestamp, nonce, decode); // System.out.println(decryptedEchoStr); //} } lxzn-module-system/lxzn-system-biz/src/main/java/org/jeecg/modules/qywx/message/utils/AesException.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,59 @@ package org.jeecg.modules.qywx.message.utils; @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; //public final static int EncodeBase64Error = -40009; //public final static int DecodeBase64Error = -40010; //public final static int GenReturnXmlError = -40011; 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éæ³"; // case EncodeBase64Error: // return "base64å å¯é误"; // case DecodeBase64Error: // return "base64è§£å¯é误"; // case GenReturnXmlError: // return "xmlçæå¤±è´¥"; default: return null; // cannot be } } public int getCode() { return code; } AesException(int code) { super(getMessage(code)); this.code = code; } } lxzn-module-system/lxzn-system-biz/src/main/java/org/jeecg/modules/qywx/message/utils/ByteGroup.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,26 @@ package org.jeecg.modules.qywx.message.utils; import java.util.ArrayList; class ByteGroup { ArrayList<Byte> byteContainer = new ArrayList<Byte>(); public byte[] toBytes() { byte[] bytes = new byte[byteContainer.size()]; for (int i = 0; i < byteContainer.size(); i++) { bytes[i] = byteContainer.get(i); } return bytes; } public ByteGroup addBytes(byte[] bytes) { for (byte b : bytes) { byteContainer.add(b); } return this; } public int size() { return byteContainer.size(); } } lxzn-module-system/lxzn-system-biz/src/main/java/org/jeecg/modules/qywx/message/utils/PKCS7Encoder.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,67 @@ /** * 对ä¼ä¸å¾®ä¿¡åéç»ä¼ä¸åå°çæ¶æ¯å è§£å¯ç¤ºä¾ä»£ç . * * @copyright Copyright (c) 1998-2014 Tencent Inc. */ // ------------------------------------------------------------------------ package org.jeecg.modules.qywx.message.utils; import java.nio.charset.Charset; import java.util.Arrays; /** * æä¾åºäºPKCS7ç®æ³çå è§£å¯æ¥å£. */ class PKCS7Encoder { static Charset CHARSET = Charset.forName("utf-8"); static int BLOCK_SIZE = 32; /** * è·å¾å¯¹ææè¿è¡è¡¥ä½å¡«å çåè. * * @param count éè¦è¿è¡å¡«å è¡¥ä½æä½çææåèä¸ªæ° * @return è¡¥é½ç¨çåèæ°ç» */ 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); } /** * å é¤è§£å¯åææçè¡¥ä½å符 * * @param decrypted è§£å¯åçææ * @return å é¤è¡¥ä½å符åçææ */ 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); } /** * å°æ°å转åæASCIIç 对åºçå符ï¼ç¨äºå¯¹ææè¿è¡è¡¥ç * * @param a éè¦è½¬åçæ°å * @return 转åå¾å°çå符 */ static char chr(int a) { byte target = (byte) (a & 0xFF); return (char) target; } } lxzn-module-system/lxzn-system-biz/src/main/java/org/jeecg/modules/qywx/message/utils/SHA1.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,61 @@ /** * 对ä¼ä¸å¾®ä¿¡åéç»ä¼ä¸åå°çæ¶æ¯å è§£å¯ç¤ºä¾ä»£ç . * * @copyright Copyright (c) 1998-2014 Tencent Inc. */ // ------------------------------------------------------------------------ package org.jeecg.modules.qywx.message.utils; import java.security.MessageDigest; import java.util.Arrays; /** * SHA1 class * * è®¡ç®æ¶æ¯ç¾åæ¥å£. */ class SHA1 { /** * ç¨SHA1ç®æ³çæå®å ¨ç¾å * @param token ç¥¨æ® * @param timestamp æ¶é´æ³ * @param nonce éæºå符串 * @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); } } } lxzn-module-system/lxzn-system-biz/src/main/java/org/jeecg/modules/qywx/message/utils/Sample.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,136 @@ package org.jeecg.modules.qywx.message.utils; import java.io.StringReader; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NodeList; import org.xml.sax.InputSource; public class Sample { public static void main(String[] args) throws Exception { String sToken = "QDG6eK"; String sCorpID = "wx5823bf96d3bd56c7"; String sEncodingAESKey = "jWmYm7qr5nMoAUwZRjGtBxmz3KA1tkAj3ykkR6q2B2C"; WXBizMsgCrypt wxcpt = new WXBizMsgCrypt(sToken, sEncodingAESKey, sCorpID); /* ------------使ç¨ç¤ºä¾ä¸ï¼éªè¯åè°URL--------------- *ä¼ä¸å¼å¯åè°æ¨¡å¼æ¶ï¼ä¼ä¸å¾®ä¿¡ä¼åéªè¯urlåéä¸ä¸ªgetè¯·æ± å设ç¹å»éªè¯æ¶ï¼ä¼ä¸æ¶å°ç±»ä¼¼è¯·æ±ï¼ * GET /cgi-bin/wxpush?msg_signature=5c45ff5e21c57e6ad56bac8758b79b1d9ac89fd3×tamp=1409659589&nonce=263014780&echostr=P9nAzCzyDtyTWESHep1vC5X9xho%2FqYX3Zpb4yKa9SKld1DsH3Iyt3tP3zNdtp%2B4RPcs8TgAE7OaBO%2BFZXvnaqQ%3D%3D * HTTP/1.1 Host: qy.weixin.qq.com æ¥æ¶å°è¯¥è¯·æ±æ¶ï¼ä¼ä¸åº 1.è§£æåºGet请æ±çåæ°ï¼å æ¬æ¶æ¯ä½ç¾å(msg_signature)ï¼æ¶é´æ³(timestamp)ï¼éæºæ°å串(nonce)以åä¼ä¸å¾®ä¿¡æ¨éè¿æ¥çéæºå å¯å符串(echostr), è¿ä¸æ¥æ³¨æä½URLè§£ç ã 2.éªè¯æ¶æ¯ä½ç¾åçæ£ç¡®æ§ 3. è§£å¯åºechostråæï¼å°åæå½ä½Get请æ±çresponseï¼è¿åç»ä¼ä¸å¾®ä¿¡ 第2ï¼3æ¥å¯ä»¥ç¨ä¼ä¸å¾®ä¿¡æä¾çåºå½æ°VerifyURLæ¥å®ç°ã */ // è§£æåºurlä¸çåæ°å¼å¦ä¸ï¼ // String sVerifyMsgSig = HttpUtils.ParseUrl("msg_signature"); String sVerifyMsgSig = "5c45ff5e21c57e6ad56bac8758b79b1d9ac89fd3"; // String sVerifyTimeStamp = HttpUtils.ParseUrl("timestamp"); String sVerifyTimeStamp = "1409659589"; // String sVerifyNonce = HttpUtils.ParseUrl("nonce"); String sVerifyNonce = "263014780"; // String sVerifyEchoStr = HttpUtils.ParseUrl("echostr"); String sVerifyEchoStr = "P9nAzCzyDtyTWESHep1vC5X9xho/qYX3Zpb4yKa9SKld1DsH3Iyt3tP3zNdtp+4RPcs8TgAE7OaBO+FZXvnaqQ=="; String sEchoStr; //éè¦è¿åçææ try { sEchoStr = wxcpt.VerifyURL(sVerifyMsgSig, sVerifyTimeStamp, sVerifyNonce, sVerifyEchoStr); System.out.println("verifyurl echostr: " + sEchoStr); // éªè¯URLæåï¼å°sEchoStrè¿å // HttpUtils.SetResponse(sEchoStr); } catch (Exception e) { //éªè¯URL失败ï¼é误åå 请æ¥çå¼å¸¸ e.printStackTrace(); } /* ------------使ç¨ç¤ºä¾äºï¼å¯¹ç¨æ·åå¤çæ¶æ¯è§£å¯--------------- ç¨æ·å夿¶æ¯æè ç¹å»äºä»¶ååºæ¶ï¼ä¼ä¸ä¼æ¶å°åè°æ¶æ¯ï¼æ¤æ¶æ¯æ¯ç»è¿ä¼ä¸å¾®ä¿¡å å¯ä¹åç坿以postå½¢å¼åéç»ä¼ä¸ï¼å¯ææ ¼å¼è¯·åè宿¹ææ¡£ å设ä¼ä¸æ¶å°ä¼ä¸å¾®ä¿¡çåè°æ¶æ¯å¦ä¸ï¼ POST /cgi-bin/wxpush? msg_signature=477715d11cdb4164915debcba66cb864d751f3e6×tamp=1409659813&nonce=1372623149 HTTP/1.1 Host: qy.weixin.qq.com Content-Length: 613 <xml> <ToUserName><![CDATA[wx5823bf96d3bd56c7]]></ToUserName><Encrypt><![CDATA[RypEvHKD8QQKFhvQ6QleEB4J58tiPdvo+rtK1I9qca6aM/wvqnLSV5zEPeusUiX5L5X/0lWfrf0QADHHhGd3QczcdCUpj911L3vg3W/sYYvuJTs3TUUkSUXxaccAS0qhxchrRYt66wiSpGLYL42aM6A8dTT+6k4aSknmPj48kzJs8qLjvd4Xgpue06DOdnLxAUHzM6+kDZ+HMZfJYuR+LtwGc2hgf5gsijff0ekUNXZiqATP7PF5mZxZ3Izoun1s4zG4LUMnvw2r+KqCKIw+3IQH03v+BCA9nMELNqbSf6tiWSrXJB3LAVGUcallcrw8V2t9EL4EhzJWrQUax5wLVMNS0+rUPA3k22Ncx4XXZS9o0MBH27Bo6BpNelZpS+/uh9KsNlY6bHCmJU9p8g7m3fVKn28H3KDYA5Pl/T8Z1ptDAVe0lXdQ2YoyyH2uyPIGHBZZIs2pDBS8R07+qN+E7Q==]]></Encrypt> <AgentID><![CDATA[218]]></AgentID> </xml> ä¼ä¸æ¶å°post请æ±ä¹ååºè¯¥ 1.è§£æåºurlä¸çåæ°ï¼å æ¬æ¶æ¯ä½ç¾å(msg_signature)ï¼æ¶é´æ³(timestamp)以åéæºæ°å串(nonce) 2.éªè¯æ¶æ¯ä½ç¾åçæ£ç¡®æ§ã 3.å°post请æ±çæ°æ®è¿è¡xmlè§£æï¼å¹¶å°<Encrypt>æ ç¾çå 容è¿è¡è§£å¯ï¼è§£å¯åºæ¥çææå³æ¯ç¨æ·å夿¶æ¯çææï¼æææ ¼å¼è¯·åè宿¹ææ¡£ 第2ï¼3æ¥å¯ä»¥ç¨ä¼ä¸å¾®ä¿¡æä¾çåºå½æ°DecryptMsgæ¥å®ç°ã */ // String sReqMsgSig = HttpUtils.ParseUrl("msg_signature"); String sReqMsgSig = "477715d11cdb4164915debcba66cb864d751f3e6"; // String sReqTimeStamp = HttpUtils.ParseUrl("timestamp"); String sReqTimeStamp = "1409659813"; // String sReqNonce = HttpUtils.ParseUrl("nonce"); String sReqNonce = "1372623149"; // post请æ±çå¯ææ°æ® // sReqData = HttpUtils.PostData(); String sReqData = "<xml><ToUserName><![CDATA[wx5823bf96d3bd56c7]]></ToUserName><Encrypt><![CDATA[RypEvHKD8QQKFhvQ6QleEB4J58tiPdvo+rtK1I9qca6aM/wvqnLSV5zEPeusUiX5L5X/0lWfrf0QADHHhGd3QczcdCUpj911L3vg3W/sYYvuJTs3TUUkSUXxaccAS0qhxchrRYt66wiSpGLYL42aM6A8dTT+6k4aSknmPj48kzJs8qLjvd4Xgpue06DOdnLxAUHzM6+kDZ+HMZfJYuR+LtwGc2hgf5gsijff0ekUNXZiqATP7PF5mZxZ3Izoun1s4zG4LUMnvw2r+KqCKIw+3IQH03v+BCA9nMELNqbSf6tiWSrXJB3LAVGUcallcrw8V2t9EL4EhzJWrQUax5wLVMNS0+rUPA3k22Ncx4XXZS9o0MBH27Bo6BpNelZpS+/uh9KsNlY6bHCmJU9p8g7m3fVKn28H3KDYA5Pl/T8Z1ptDAVe0lXdQ2YoyyH2uyPIGHBZZIs2pDBS8R07+qN+E7Q==]]></Encrypt><AgentID><![CDATA[218]]></AgentID></xml>"; try { String sMsg = wxcpt.DecryptMsg(sReqMsgSig, sReqTimeStamp, sReqNonce, sReqData); System.out.println("after decrypt msg: " + sMsg); // TODO: è§£æåºææxmlæ ç¾çå 容è¿è¡å¤ç // For example: DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); DocumentBuilder db = dbf.newDocumentBuilder(); StringReader sr = new StringReader(sMsg); InputSource is = new InputSource(sr); Document document = db.parse(is); Element root = document.getDocumentElement(); NodeList nodelist1 = root.getElementsByTagName("Content"); String Content = nodelist1.item(0).getTextContent(); System.out.println("Contentï¼" + Content); } catch (Exception e) { // TODO // è§£å¯å¤±è´¥ï¼å¤±è´¥åå 请æ¥çå¼å¸¸ e.printStackTrace(); } /* ------------使ç¨ç¤ºä¾ä¸ï¼ä¼ä¸åå¤ç¨æ·æ¶æ¯çå å¯--------------- ä¼ä¸è¢«å¨åå¤ç¨æ·çæ¶æ¯ä¹éè¦è¿è¡å å¯ï¼å¹¶ä¸æ¼æ¥æå¯ææ ¼å¼çxml串ã å设ä¼ä¸éè¦åå¤ç¨æ·çææå¦ä¸ï¼ <xml> <ToUserName><![CDATA[mycreate]]></ToUserName> <FromUserName><![CDATA[wx5823bf96d3bd56c7]]></FromUserName> <CreateTime>1348831860</CreateTime> <MsgType><![CDATA[text]]></MsgType> <Content><![CDATA[this is a test]]></Content> <MsgId>1234567890123456</MsgId> <AgentID>128</AgentID> </xml> 为äºå°æ¤æ®µææåå¤ç»ç¨æ·ï¼ä¼ä¸åºï¼ 1.èªå·±çææ¶é´æ¶é´æ³(timestamp),éæºæ°å串(nonce)ä»¥ä¾¿çææ¶æ¯ä½ç¾åï¼ä¹å¯ä»¥ç´æ¥ç¨ä»ä¼ä¸å¾®ä¿¡çpost urlä¸è§£æåºç对åºå¼ã 2.å°ææå å¯å¾å°å¯æã 3.ç¨å¯æï¼æ¥éª¤1çæçtimestamp,nonceåä¼ä¸å¨ä¼ä¸å¾®ä¿¡è®¾å®çtokençææ¶æ¯ä½ç¾åã 4.å°å¯æï¼æ¶æ¯ä½ç¾åï¼æ¶é´æ³ï¼éæºæ°åä¸²æ¼æ¥æxmlæ ¼å¼çå符串ï¼åéç»ä¼ä¸ã 以ä¸2ï¼3ï¼4æ¥å¯ä»¥ç¨ä¼ä¸å¾®ä¿¡æä¾çåºå½æ°EncryptMsgæ¥å®ç°ã */ String sRespData = "<xml><ToUserName><![CDATA[mycreate]]></ToUserName><FromUserName><![CDATA[wx5823bf96d3bd56c7]]></FromUserName><CreateTime>1348831860</CreateTime><MsgType><![CDATA[text]]></MsgType><Content><![CDATA[this is a test]]></Content><MsgId>1234567890123456</MsgId><AgentID>128</AgentID></xml>"; try{ String sEncryptMsg = wxcpt.EncryptMsg(sRespData, sReqTimeStamp, sReqNonce); System.out.println("after encrypt sEncrytMsg: " + sEncryptMsg); // å 坿å // TODO: // HttpUtils.SetResponse(sEncryptMsg); } catch(Exception e) { e.printStackTrace(); // å å¯å¤±è´¥ } } } lxzn-module-system/lxzn-system-biz/src/main/java/org/jeecg/modules/qywx/message/utils/WXBizMsgCrypt.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,289 @@ /** * 对ä¼ä¸å¾®ä¿¡åéç»ä¼ä¸åå°çæ¶æ¯å è§£å¯ç¤ºä¾ä»£ç . * * @copyright Copyright (c) 1998-2014 Tencent Inc. */ // ------------------------------------------------------------------------ /** * é对org.apache.commons.codec.binary.Base64ï¼ * éè¦å¯¼å ¥æ¶å commons-codec-1.9ï¼æcommons-codec-1.8çå ¶ä»çæ¬ï¼ * 宿¹ä¸è½½å°åï¼http://commons.apache.org/proper/commons-codec/download_codec.cgi */ package org.jeecg.modules.qywx.message.utils; import org.apache.commons.codec.binary.Base64; import javax.crypto.Cipher; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import java.nio.charset.Charset; import java.util.Arrays; import java.util.Random; /** * æä¾æ¥æ¶åæ¨éç»ä¼ä¸å¾®ä¿¡æ¶æ¯çå è§£å¯æ¥å£(UTF8ç¼ç çå符串). * <ol> * <li>ç¬¬ä¸æ¹åå¤å 坿¶æ¯ç»ä¼ä¸å¾®ä¿¡</li> * <li>ç¬¬ä¸æ¹æ¶å°ä¼ä¸å¾®ä¿¡åéçæ¶æ¯ï¼éªè¯æ¶æ¯çå®å ¨æ§ï¼å¹¶å¯¹æ¶æ¯è¿è¡è§£å¯ã</li> * </ol> * 说æï¼å¼å¸¸java.security.InvalidKeyException:illegal Key Sizeçè§£å³æ¹æ¡ * <ol> * <li>å¨å®æ¹ç½ç«ä¸è½½JCEæ éå¶æéçç¥æä»¶ï¼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ï¼å°ä¸¤ä¸ªjaræä»¶æ¾å°%JRE_HOME%\lib\securityç®å½ä¸è¦ç忥çæä»¶</li> * <li>妿å®è£ äºJDKï¼å°ä¸¤ä¸ª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 receiveid; /** * æé 彿° * @param token ä¼ä¸å¾®ä¿¡åå°ï¼å¼åè 设置çtoken * @param encodingAesKey ä¼ä¸å¾®ä¿¡åå°ï¼å¼åè 设置çEncodingAESKey * @param receiveid, ä¸ååºæ¯å«ä¹ä¸åï¼è¯¦è§ææ¡£ * * @throws AesException æ§è¡å¤±è´¥ï¼è¯·æ¥ç该å¼å¸¸çé误ç åå ·ä½çéè¯¯ä¿¡æ¯ */ public WXBizMsgCrypt(String token, String encodingAesKey, String receiveid) throws AesException { if (encodingAesKey.length() != 43) { throw new AesException(AesException.IllegalAesKey); } this.token = token; this.receiveid = receiveid; aesKey = Base64.decodeBase64(encodingAesKey + "="); } // çæ4个åèçç½ç»åèåº byte[] getNetworkBytesOrder(int sourceNumber) { byte[] orderBytes = new byte[4]; orderBytes[3] = (byte) (sourceNumber & 0xFF); orderBytes[2] = (byte) (sourceNumber >> 8 & 0xFF); orderBytes[1] = (byte) (sourceNumber >> 16 & 0xFF); orderBytes[0] = (byte) (sourceNumber >> 24 & 0xFF); return orderBytes; } // è¿å4个åèçç½ç»åèåº int recoverNetworkBytesOrder(byte[] orderBytes) { int sourceNumber = 0; for (int i = 0; i < 4; i++) { sourceNumber <<= 8; sourceNumber |= orderBytes[i] & 0xff; } return sourceNumber; } // éæºçæ16ä½å符串 String getRandomStr() { String base = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; Random random = new Random(); StringBuffer sb = new StringBuffer(); for (int i = 0; i < 16; i++) { int number = random.nextInt(base.length()); sb.append(base.charAt(number)); } return sb.toString(); } /** * 对ææè¿è¡å å¯. * * @param text éè¦å å¯çææ * @return å å¯åbase64ç¼ç çå符串 * @throws AesException aeså å¯å¤±è´¥ */ String encrypt(String randomStr, String text) throws AesException { ByteGroup byteCollector = new ByteGroup(); byte[] randomStrBytes = randomStr.getBytes(CHARSET); byte[] textBytes = text.getBytes(CHARSET); byte[] networkBytesOrder = getNetworkBytesOrder(textBytes.length); byte[] receiveidBytes = receiveid.getBytes(CHARSET); // randomStr + networkBytesOrder + text + receiveid byteCollector.addBytes(randomStrBytes); byteCollector.addBytes(networkBytesOrder); byteCollector.addBytes(textBytes); byteCollector.addBytes(receiveidBytes); // ... + pad: 使ç¨èªå®ä¹çå¡«å æ¹å¼å¯¹ææè¿è¡è¡¥ä½å¡«å byte[] padBytes = PKCS7Encoder.encode(byteCollector.size()); byteCollector.addBytes(padBytes); // è·å¾æç»çåèæµ, æªå å¯ byte[] unencrypted = byteCollector.toBytes(); try { // 设置å 坿¨¡å¼ä¸ºAESçCBCæ¨¡å¼ Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding"); SecretKeySpec keySpec = new SecretKeySpec(aesKey, "AES"); IvParameterSpec iv = new IvParameterSpec(aesKey, 0, 16); cipher.init(Cipher.ENCRYPT_MODE, keySpec, iv); // å å¯ byte[] encrypted = cipher.doFinal(unencrypted); // 使ç¨BASE64对å å¯åçå符串è¿è¡ç¼ç String base64Encrypted = base64.encodeToString(encrypted); return base64Encrypted; } catch (Exception e) { e.printStackTrace(); throw new AesException(AesException.EncryptAESError); } } /** * 坹坿è¿è¡è§£å¯. * * @param text éè¦è§£å¯ç坿 * @return è§£å¯å¾å°çææ * @throws AesException aesè§£å¯å¤±è´¥ */ String decrypt(String text) throws AesException { byte[] original; try { // è®¾ç½®è§£å¯æ¨¡å¼ä¸º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)); //IvParameterSpec iv = new IvParameterSpec(new byte[16]); // åºå® IV cipher.init(Cipher.DECRYPT_MODE, key_spec, iv); // 使ç¨BASE64坹坿è¿è¡è§£ç byte[] encrypted = Base64.decodeBase64(text); // è§£å¯ original = cipher.doFinal(encrypted); } catch (Exception e) { e.printStackTrace(); throw new AesException(AesException.DecryptAESError); } String xmlContent, from_receiveid; try { // å»é¤è¡¥ä½å符 byte[] bytes = PKCS7Encoder.decode(original); // å离16ä½éæºå符串,ç½ç»åèåºåreceiveid byte[] networkOrder = Arrays.copyOfRange(bytes, 16, 20); int xmlLength = recoverNetworkBytesOrder(networkOrder); xmlContent = new String(Arrays.copyOfRange(bytes, 20, 20 + xmlLength), CHARSET); from_receiveid = new String(Arrays.copyOfRange(bytes, 20 + xmlLength, bytes.length), CHARSET); } catch (Exception e) { e.printStackTrace(); throw new AesException(AesException.IllegalBuffer); } // receiveidä¸ç¸åçæ åµ if (!from_receiveid.equals(receiveid)) { throw new AesException(AesException.ValidateCorpidError); } return xmlContent; } /** * å°ä¼ä¸å¾®ä¿¡åå¤ç¨æ·çæ¶æ¯å 坿å . * <ol> * <li>对è¦åéçæ¶æ¯è¿è¡AES-CBCå å¯</li> * <li>çæå®å ¨ç¾å</li> * <li>å°æ¶æ¯å¯æåå®å ¨ç¾åæå æxmlæ ¼å¼</li> * </ol> * * @param replyMsg ä¼ä¸å¾®ä¿¡å¾ åå¤ç¨æ·çæ¶æ¯ï¼xmlæ ¼å¼çå符串 * @param timeStamp æ¶é´æ³ï¼å¯ä»¥èªå·±çæï¼ä¹å¯ä»¥ç¨URLåæ°çtimestamp * @param nonce éæºä¸²ï¼å¯ä»¥èªå·±çæï¼ä¹å¯ä»¥ç¨URLåæ°çnonce * * @return å å¯åçå¯ä»¥ç´æ¥åå¤ç¨æ·ç坿ï¼å æ¬msg_signature, timestamp, nonce, encryptçxmlæ ¼å¼çå符串 * @throws AesException æ§è¡å¤±è´¥ï¼è¯·æ¥ç该å¼å¸¸çé误ç åå ·ä½çéè¯¯ä¿¡æ¯ */ public String EncryptMsg(String replyMsg, String timeStamp, String nonce) throws AesException { // å å¯ String encrypt = encrypt(getRandomStr(), replyMsg); // çæå®å ¨ç¾å if (timeStamp == "") { timeStamp = Long.toString(System.currentTimeMillis()); } String signature = SHA1.getSHA1(token, timeStamp, nonce, encrypt); // System.out.println("åéç»å¹³å°çç¾åæ¯: " + signature[1].toString()); // çæåéçxml String result = XMLParse.generate(encrypt, signature, timeStamp, nonce); return result; } /** * æ£éªæ¶æ¯çç宿§ï¼å¹¶ä¸è·åè§£å¯åçææ. * <ol> * <li>å©ç¨æ¶å°ç坿çæå®å ¨ç¾åï¼è¿è¡ç¾åéªè¯</li> * <li>è¥éªè¯éè¿ï¼åæåxmlä¸çå 坿¶æ¯</li> * <li>å¯¹æ¶æ¯è¿è¡è§£å¯</li> * </ol> * * @param msgSignature ç¾å串ï¼å¯¹åºURLåæ°çmsg_signature * @param timeStamp æ¶é´æ³ï¼å¯¹åºURLåæ°çtimestamp * @param nonce éæºä¸²ï¼å¯¹åºURLåæ°çnonce * @param postData 坿ï¼å¯¹åºPOST请æ±çæ°æ® * * @return è§£å¯åçåæ * @throws AesException æ§è¡å¤±è´¥ï¼è¯·æ¥ç该å¼å¸¸çé误ç åå ·ä½çéè¯¯ä¿¡æ¯ */ public String DecryptMsg(String msgSignature, String timeStamp, String nonce, String postData) throws AesException { // å¯é¥ï¼å ¬ä¼è´¦å·çapp secret // æå坿 Object[] encrypt = XMLParse.extract(postData); // éªè¯å®å ¨ç¾å String signature = SHA1.getSHA1(token, timeStamp, nonce, encrypt[1].toString()); // åURLä¸çç¾åæ¯è¾æ¯å¦ç¸ç // System.out.println("ç¬¬ä¸æ¹æ¶å°URLä¸çç¾åï¼" + msg_sign); // System.out.println("ç¬¬ä¸æ¹æ ¡éªç¾åï¼" + signature); if (!signature.equals(msgSignature)) { throw new AesException(AesException.ValidateSignatureError); } // è§£å¯ String result = decrypt(encrypt[1].toString()); return result; } /** * éªè¯URL * @param msgSignature ç¾å串ï¼å¯¹åºURLåæ°çmsg_signature * @param timeStamp æ¶é´æ³ï¼å¯¹åºURLåæ°çtimestamp * @param nonce éæºä¸²ï¼å¯¹åºURLåæ°çnonce * @param echoStr éæºä¸²ï¼å¯¹åºURLåæ°çechostr * * @return è§£å¯ä¹åçechostr * @throws AesException æ§è¡å¤±è´¥ï¼è¯·æ¥ç该å¼å¸¸çé误ç åå ·ä½çéè¯¯ä¿¡æ¯ */ public String VerifyURL(String msgSignature, String timeStamp, String nonce, String echoStr) throws AesException { String signature = SHA1.getSHA1(token, timeStamp, nonce, echoStr); if (!signature.equals(msgSignature)) { throw new AesException(AesException.ValidateSignatureError); } String result = decrypt(echoStr); return result; } } lxzn-module-system/lxzn-system-biz/src/main/java/org/jeecg/modules/qywx/message/utils/XMLParse.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,104 @@ /** * 对ä¼ä¸å¾®ä¿¡åéç»ä¼ä¸åå°çæ¶æ¯å è§£å¯ç¤ºä¾ä»£ç . * * @copyright Copyright (c) 1998-2014 Tencent Inc. */ // ------------------------------------------------------------------------ package org.jeecg.modules.qywx.message.utils; import java.io.StringReader; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NodeList; import org.xml.sax.InputSource; /** * XMLParse class * * æä¾æåæ¶æ¯æ ¼å¼ä¸ç坿åçæå夿¶æ¯æ ¼å¼çæ¥å£. */ class XMLParse { /** * æååºxmlæ°æ®å ä¸çå 坿¶æ¯ * @param xmltext å¾ æåçxmlå符串 * @return æååºçå 坿¶æ¯å符串 * @throws AesException */ public static Object[] extract(String xmltext) throws AesException { Object[] result = new Object[3]; try { DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); String FEATURE = null; // This is the PRIMARY defense. If DTDs (doctypes) are disallowed, almost all XML entity attacks are prevented // Xerces 2 only - http://xerces.apache.org/xerces2-j/features.html#disallow-doctype-decl FEATURE = "http://apache.org/xml/features/disallow-doctype-decl"; dbf.setFeature(FEATURE, true); // If you can't completely disable DTDs, then at least do the following: // Xerces 1 - http://xerces.apache.org/xerces-j/features.html#external-general-entities // Xerces 2 - http://xerces.apache.org/xerces2-j/features.html#external-general-entities // JDK7+ - http://xml.org/sax/features/external-general-entities FEATURE = "http://xml.org/sax/features/external-general-entities"; dbf.setFeature(FEATURE, false); // Xerces 1 - http://xerces.apache.org/xerces-j/features.html#external-parameter-entities // Xerces 2 - http://xerces.apache.org/xerces2-j/features.html#external-parameter-entities // JDK7+ - http://xml.org/sax/features/external-parameter-entities FEATURE = "http://xml.org/sax/features/external-parameter-entities"; dbf.setFeature(FEATURE, false); // Disable external DTDs as well FEATURE = "http://apache.org/xml/features/nonvalidating/load-external-dtd"; dbf.setFeature(FEATURE, false); // and these as well, per Timothy Morgan's 2014 paper: "XML Schema, DTD, and Entity Attacks" dbf.setXIncludeAware(false); dbf.setExpandEntityReferences(false); // And, per Timothy Morgan: "If for some reason support for inline DOCTYPEs are a requirement, then // ensure the entity settings are disabled (as shown above) and beware that SSRF attacks // (http://cwe.mitre.org/data/definitions/918.html) and denial // of service attacks (such as billion laughs or decompression bombs via "jar:") are a risk." // remaining parser logic 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"); result[0] = 0; result[1] = nodelist1.item(0).getTextContent(); return result; } catch (Exception e) { e.printStackTrace(); throw new AesException(AesException.ParseXmlError); } } /** * çæxmlæ¶æ¯ * @param encrypt å å¯åçæ¶æ¯å¯æ * @param signature å®å ¨ç¾å * @param timestamp æ¶é´æ³ * @param nonce éæºå符串 * @return çæçxmlå符串 */ public static String generate(String encrypt, String signature, String timestamp, String nonce) { String format = "<xml>\n" + "<Encrypt><![CDATA[%1$s]]></Encrypt>\n" + "<MsgSignature><![CDATA[%2$s]]></MsgSignature>\n" + "<TimeStamp>%3$s</TimeStamp>\n" + "<Nonce><![CDATA[%4$s]]></Nonce>\n" + "</xml>"; return String.format(format, encrypt, signature, timestamp, nonce); } } lxzn-module-system/lxzn-system-biz/src/main/java/org/jeecg/modules/qywx/message/vo/TemplateCard.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,20 @@ package org.jeecg.modules.qywx.message.vo; import lombok.Data; /** * ä¼ä¸å¾®ä¿¡æ¨¡æ¿å¡çæ¶æ¯ï¼ææ¬éç¥åï¼ */ @Data public class TemplateCard { private String touser;//æåIDåè¡¨ï¼æ¶æ¯æ¥æ¶è ï¼å¤ä¸ªæ¥æ¶è ç¨â|âåéï¼æå¤æ¯æ1000个ï¼ãç¹æ®æ åµï¼æå®ä¸º@allï¼ååå ³æ³¨è¯¥ä¼ä¸åºç¨çå ¨é¨æååé private String toparty;//é¨é¨IDå表ï¼å¤ä¸ªæ¥æ¶è ç¨â|âåéï¼æå¤æ¯æ100个ãå½touser为@allæ¶å¿½ç¥æ¬å private String totag;//æ ç¾IDå表ï¼å¤ä¸ªæ¥æ¶è ç¨â|âåéï¼æå¤æ¯æ100个ãå½touser为@allæ¶å¿½ç¥æ¬åæ° private int agentid;//ä¼ä¸åºç¨çidï¼æ´åãå¯å¨åºç¨çè®¾ç½®é¡µé¢æ¥ç private TemplateCardEntity template_card;//æ¶æ¯å®ä½ private String enable_id_trans;// å¦ è¡¨ç¤ºæ¯å¦å¼å¯id转è¯ï¼0表示å¦ï¼1表示æ¯ï¼é»è®¤0 private String enable_duplicate_check;// å¦ è¡¨ç¤ºæ¯å¦å¼å¯é夿¶æ¯æ£æ¥ï¼0表示å¦ï¼1表示æ¯ï¼é»è®¤0 private String duplicate_check_interval;// å¦ è¡¨ç¤ºæ¯å¦é夿¶æ¯æ£æ¥çæ¶é´é´éï¼é»è®¤1800sï¼æå¤§ä¸è¶ è¿4å°æ¶ private String msgtype = "template_card";//æ¶æ¯ç±»åï¼æ¤æ¶åºå®ä¸ºï¼template_card } lxzn-module-system/lxzn-system-biz/src/main/java/org/jeecg/modules/qywx/message/vo/TemplateCardEntity.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,94 @@ package org.jeecg.modules.qywx.message.vo; import lombok.AllArgsConstructor; import lombok.Data; import java.util.List; /** * 模æ¿å¡çæ¶æ¯å®ä½ */ @Data public class TemplateCardEntity { private Source source;//å¦ å¡çæ¥æºæ ·å¼ä¿¡æ¯ï¼ä¸éè¦æ¥æºæ ·å¼å¯ä¸å¡«å private ActionMenu action_menu;//å¦ å¡çå³ä¸è§æ´å¤æä½æé® private String task_id;//å¦ ä»»å¡idï¼åä¸ä¸ªåºç¨ä»»å¡idä¸è½éå¤ï¼åªè½ç±æ°åã忝åâ_-@âç»æï¼æé¿128åèï¼å¡«äºaction_menuåæ®µçè¯æ¬åæ®µå¿ å¡« private MainTitle main_title; private QuoteArea quote_area;//å¦ å¼ç¨æç®æ ·å¼ private EmphasisContent emphasis_content; private String sub_title_text;//äºçº§æ®éææ¬ï¼å»ºè®®ä¸è¶ è¿160个åï¼ï¼æ¯æid转è¯ï¼ private List<HorizontalContent> horizontal_content_list; private List<Jump> jump_list;//å¦ è·³è½¬æå¼æ ·å¼çå表ï¼è¯¥å段å¯ä¸ºç©ºæ°ç»ï¼ä½ææ°æ®çè¯é确认对åºå段æ¯å¦å¿ å¡«ï¼å表é¿åº¦ä¸è¶ è¿3 private CardAction card_action;//æ¯ æ´ä½å¡ççç¹å»è·³è½¬äºä»¶ï¼text_noticeå¿ å¡«æ¬å段 //徿å±ç¤ºåå¡ç¥¨ç¬æçåæ° private CardImage card_image; private String card_type = "text_notice";// 模æ¿å¡çç±»åï¼ææ¬éç¥åå¡çå¡«å "text_notice" @Data public static class Source { private String icon_url;//å¦ æ¥æºå¾ççurlï¼æ¥æºå¾çç尺寸建议为72*72 private String desc;//å¦ æ¥æºå¾ççæè¿°ï¼å»ºè®®ä¸è¶ è¿20个åï¼ï¼æ¯æid转è¯ï¼ private String desc_color;//å¦ æ¥æºæåçé¢è²ï¼ç®åæ¯æï¼0(é»è®¤) ç°è²ï¼1 é»è²ï¼2 红è²ï¼3 ç»¿è² } @Data public static class ActionMenu { private String desc;//å¦ æ´å¤æä½çé¢çæè¿° private List<ActionList> action_list;//æ¯ æä½å表ï¼å表é¿åº¦åå¼èå´ä¸º [1, 3] @Data @AllArgsConstructor public static class ActionList { private String key;//æ¯ æä½keyå¼ï¼ç¨æ·ç¹å»åï¼ä¼äº§çåè°äºä»¶å°æ¬åæ°ä½ä¸ºEventKeyè¿åï¼åè°äºä»¶ä¼å¸¦ä¸è¯¥keyå¼ï¼æé¿æ¯æ1024åèï¼ä¸å¯éå¤ private String text;//æ¯ æä½çæè¿°ææ¡ } } @Data public static class MainTitle { private String title;//å¦ ä¸çº§æ é¢ï¼å»ºè®®ä¸è¶ è¿36个åï¼ææ¬éç¥åå¡çæ¬å段éå¿ å¡«ï¼ä½ä¸å¯æ¬å段åsub_title_texté½ä¸å¡«ï¼ï¼æ¯æid转è¯ï¼ private String desc;//å¦ æ é¢è¾ å©ä¿¡æ¯ï¼å»ºè®®ä¸è¶ è¿44个åï¼ï¼æ¯æid转è¯ï¼ } @Data private class QuoteArea { private int type;//å¦ å¼ç¨æç®æ ·å¼åºåç¹å»äºä»¶ï¼0æä¸å¡«ä»£è¡¨æ²¡æç¹å»äºä»¶ï¼1 代表跳转urlï¼2 代表跳转å°ç¨åº private String url;//å¦ ç¹å»è·³è½¬çurlï¼quote_area.typeæ¯1æ¶å¿ å¡« private String title;//å¦ å¼ç¨æç®æ ·å¼çæ é¢ private String quote_text;//å¦ å¼ç¨æç®æ ·å¼çå¼ç¨ææ¡ } @Data private class EmphasisContent { private String title;//å¦ å ³é®æ°æ®æ ·å¼çæ°æ®å 容ï¼å»ºè®®ä¸è¶ è¿14个å private String desc;//å¦ å ³é®æ°æ®æ ·å¼çæ°æ®æè¿°å 容ï¼å»ºè®®ä¸è¶ è¿22个å } @Data public static class HorizontalContent { private String keyname;//æ¯ äºçº§æ é¢ï¼å»ºè®®ä¸è¶ è¿5个å private String value;//å¦ äºçº§ææ¬ï¼å¦æhorizontal_content_list.typeæ¯2ï¼è¯¥å段代表æä»¶åç§°ï¼è¦å å«æä»¶ç±»åï¼ï¼å»ºè®®ä¸è¶ è¿30个åï¼ï¼æ¯æid转è¯ï¼ } @Data @AllArgsConstructor public static class Jump { private int type;//å¦ è·³è½¬é¾æ¥ç±»åï¼0æä¸å¡«ä»£è¡¨ä¸æ¯é¾æ¥ï¼1 代表跳转urlï¼2 代表跳转å°ç¨åº private String title;//æ¯ è·³è½¬é¾æ¥æ ·å¼çææ¡å 容ï¼å»ºè®®ä¸è¶ è¿18个å private String url;//å¦ è·³è½¬é¾æ¥çurlï¼jump_list.typeæ¯1æ¶å¿ å¡« } @Data public static class CardAction { private int type;//æ¯ è·³è½¬äºä»¶ç±»åï¼1 代表跳转urlï¼2 代表æå¼å°ç¨åºãtext_noticeå¡ç模çä¸è¯¥å段åå¼èå´ä¸º[1,2] private String url;//å¦ è·³è½¬äºä»¶çurlï¼card_action.typeæ¯1æ¶å¿ å¡« private String appid;//å¦ è·³è½¬äºä»¶çå°ç¨åºçappidï¼å¿ é¡»æ¯ä¸å½ååºç¨å ³èçå°ç¨åºï¼card_action.typeæ¯2æ¶å¿ å¡« private String pagepath;//å¦ è·³è½¬äºä»¶çå°ç¨åºçpagepathï¼card_action.typeæ¯2æ¶éå¡« } @Data public static class CardImage { private String url;//æ¯ å¾ççurl private String aspect_ratio;//å¦ å¾çç宽髿¯ï¼å®½é«æ¯è¦å°äº2.25ï¼å¤§äº1.3ï¼ä¸å¡«è¯¥åæ°é»è®¤1.3 } }