From dfe027a7f2207db7a2c35f95904e6fc9e482b11d Mon Sep 17 00:00:00 2001 From: Lemon Date: Thu, 27 Jul 2023 16:44:48 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=20=20=E4=BB=A3=E5=BC=80?= =?UTF-8?q?=E5=8F=91=E5=B0=8F=E7=A8=8B=E5=BA=8F=20=E6=8E=88=E6=9D=83?= =?UTF-8?q?=E4=BA=8B=E4=BB=B6=E6=8E=A5=E6=94=B6URL,=E9=AA=8C=E8=AF=81?= =?UTF-8?q?=E7=A5=A8=E6=8D=AE=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/uniapp/AgentDevController.java | 54 ++++ .../com/jsowell/service/AgentDevService.java | 89 +++++++ jsowell-common/pom.xml | 7 + .../common/constant/CacheConstants.java | 6 + .../common/util/wxplatform/AesException.java | 65 +++++ .../common/util/wxplatform/PKCS7Encoder.java | 61 +++++ .../common/util/wxplatform/WXBizMsgCrypt.java | 241 ++++++++++++++++++ .../util/wxplatform/WXXmlToMapUtil.java | 229 +++++++++++++++++ .../pile/domain/AuthorizationEventResult.java | 17 ++ 9 files changed, 769 insertions(+) create mode 100644 jsowell-admin/src/main/java/com/jsowell/api/uniapp/AgentDevController.java create mode 100644 jsowell-admin/src/main/java/com/jsowell/service/AgentDevService.java create mode 100644 jsowell-common/src/main/java/com/jsowell/common/util/wxplatform/AesException.java create mode 100644 jsowell-common/src/main/java/com/jsowell/common/util/wxplatform/PKCS7Encoder.java create mode 100644 jsowell-common/src/main/java/com/jsowell/common/util/wxplatform/WXBizMsgCrypt.java create mode 100644 jsowell-common/src/main/java/com/jsowell/common/util/wxplatform/WXXmlToMapUtil.java create mode 100644 jsowell-pile/src/main/java/com/jsowell/pile/domain/AuthorizationEventResult.java diff --git a/jsowell-admin/src/main/java/com/jsowell/api/uniapp/AgentDevController.java b/jsowell-admin/src/main/java/com/jsowell/api/uniapp/AgentDevController.java new file mode 100644 index 000000000..2d240298e --- /dev/null +++ b/jsowell-admin/src/main/java/com/jsowell/api/uniapp/AgentDevController.java @@ -0,0 +1,54 @@ +package com.jsowell.api.uniapp; + +import com.jsowell.common.annotation.Anonymous; +import com.jsowell.common.core.controller.BaseController; +import com.jsowell.pile.domain.AuthorizationEventResult; +import com.jsowell.service.AgentDevService; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +/** + * 待开发小程序Controller + * + * @author Lemon + * @Date 2023/7/27 15:09 + */ +@Anonymous +@RestController +@RequestMapping("/agentDev") +public class AgentDevController extends BaseController { + + @Autowired + private AgentDevService agentDevService; + + @ApiOperation(value = "授权事件接收URL,验证票据") + @PostMapping("/platform/event") + public ResponseEntity wechatPlatformEvent(@RequestParam("timestamp") String timestamp, + @RequestParam("nonce") String nonce, + @RequestParam("msg_signature") String msgSignature, + @RequestBody String postData) { + AuthorizationEventResult resultBean = new AuthorizationEventResult<>(); + ResponseEntity responseEntity; + logger.debug("授权事件接收URL,验证票据"); + try { + resultBean.setData(agentDevService.parseRequest(timestamp,nonce,msgSignature,postData)); + responseEntity = new ResponseEntity<>(resultBean, HttpStatus.OK); + logger.debug("第三方平台授权事件接收URL,验证票据成功"); + } catch (Exception e) { + logger.error("第三方平台授权事件接收URL,验证票据异常", e.getMessage(), e); + AuthorizationEventResult errorResultBean = new AuthorizationEventResult<>(); + errorResultBean.setMsg("第三方平台授权事件接收URL,验证票据异常"); + errorResultBean.setErrorMsg(e.getMessage()); + errorResultBean.setCode(422); + responseEntity = new ResponseEntity<>(errorResultBean, HttpStatus.UNPROCESSABLE_ENTITY); + } + return responseEntity; + } + + + + +} diff --git a/jsowell-admin/src/main/java/com/jsowell/service/AgentDevService.java b/jsowell-admin/src/main/java/com/jsowell/service/AgentDevService.java new file mode 100644 index 000000000..f45eb10b8 --- /dev/null +++ b/jsowell-admin/src/main/java/com/jsowell/service/AgentDevService.java @@ -0,0 +1,89 @@ +package com.jsowell.service; + +import com.jsowell.common.constant.CacheConstants; +import com.jsowell.common.core.redis.RedisCache; +import com.jsowell.common.util.StringUtils; +import com.jsowell.common.util.wxplatform.AesException; +import com.jsowell.common.util.wxplatform.WXBizMsgCrypt; +import com.jsowell.common.util.wxplatform.WXXmlToMapUtil; +import org.apache.commons.collections.MapUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.Map; +import java.util.concurrent.TimeUnit; + +/** + * 代开发小程序Service + * + * @author Lemon + * @Date 2023/7/27 15:58 + */ +@Service +public class AgentDevService { + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + @Autowired + private RedisCache redisCache; + + /** + * 第三方平台 appid + */ + private static final String PLATFORM_APP_ID = "****************"; + + /** + * 第三方平台 secret + */ + private static final String PLATFORM_APP_SECRET = "****************"; + + /** + * 第三方平台 消息加解密Key + */ + private static final String PLATFORM_AES_KEY = "****************"; + + /** + * 第三方平台 消息校验Token + */ + private static final String PLATFORM_COMPONENT_TOKEN = "****************"; + + + /** + * 解析请求 + * + * @param timeStamp + * @param nonce + * @param msgSignature + * @param postData + * @return + */ + public String parseRequest(String timeStamp, String nonce, String msgSignature, String postData) { + logger.debug("==============================开始授权事件接收URL================================="); + try { + //这个类是微信官网提供的解密类,需要用到消息校验Token 消息加密Key和服务平台appid + WXBizMsgCrypt pc = new WXBizMsgCrypt(PLATFORM_COMPONENT_TOKEN, PLATFORM_AES_KEY, PLATFORM_APP_ID); + String xml = pc.decryptMsg(msgSignature, timeStamp, nonce, postData); + Map result = WXXmlToMapUtil.xmlToMap(xml);// 将xml转为map + String componentVerifyTicket = MapUtils.getString(result, "ComponentVerifyTicket"); + if (StringUtils.isNotEmpty(componentVerifyTicket)) { + // 存入Redis 过期时间 12 小时 + String redisKey = CacheConstants.COMPONENT_VERIFY_TICKET; + redisCache.setCacheObject(redisKey, componentVerifyTicket, 60 * 12, TimeUnit.SECONDS); + String verifyTicket = redisCache.getCacheObject(redisKey); + + // 存储平台授权票据,保存ticket + // redisTemplate.opsForValue().set("component_verify_ticket", componentVerifyTicket, 60 * 12, TimeUnit.SECONDS); + // String verifyTicket = redisTemplate.opsForValue().get("component_verify_ticket").toString(); + logger.debug("====================授权票据【ComponentVerifyTicket】:【" + verifyTicket + "】===================="); + } else { + throw new RuntimeException("微信开放平台,第三方平台获取【验证票据】失败"); + } + } catch (AesException e) { + e.printStackTrace(); + } + logger.debug("==============================结束授权事件接收URL================================="); + return "success"; + } + +} diff --git a/jsowell-common/pom.xml b/jsowell-common/pom.xml index db037dd98..401a3605d 100644 --- a/jsowell-common/pom.xml +++ b/jsowell-common/pom.xml @@ -138,6 +138,13 @@ guava + + + dom4j + dom4j + 1.6.1 + + com.tencentcloudapi tencentcloud-sdk-java diff --git a/jsowell-common/src/main/java/com/jsowell/common/constant/CacheConstants.java b/jsowell-common/src/main/java/com/jsowell/common/constant/CacheConstants.java index c0be95e5e..aa0359080 100644 --- a/jsowell-common/src/main/java/com/jsowell/common/constant/CacheConstants.java +++ b/jsowell-common/src/main/java/com/jsowell/common/constant/CacheConstants.java @@ -104,6 +104,12 @@ public class CacheConstants { * uniApp通过站点id查询枪口列表信息 */ public static final String GET_UNIAPP_CONNECTOR_LIST_BY_STATION_ID = "GET_UNIAPP_CONNECTOR_LIST_BY_STATION_ID:"; + + /** + * 代开发小程序保存票据 + */ + public static final String COMPONENT_VERIFY_TICKET = "component_verify_ticket:"; + /** * 充电桩sn生成 key */ diff --git a/jsowell-common/src/main/java/com/jsowell/common/util/wxplatform/AesException.java b/jsowell-common/src/main/java/com/jsowell/common/util/wxplatform/AesException.java new file mode 100644 index 000000000..4b9d85233 --- /dev/null +++ b/jsowell-common/src/main/java/com/jsowell/common/util/wxplatform/AesException.java @@ -0,0 +1,65 @@ +package com.jsowell.common.util.wxplatform; + +/** + * TODO + * + * @author Lemon + * @Date 2023/7/27 16:19 + */ +@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; + } + +} diff --git a/jsowell-common/src/main/java/com/jsowell/common/util/wxplatform/PKCS7Encoder.java b/jsowell-common/src/main/java/com/jsowell/common/util/wxplatform/PKCS7Encoder.java new file mode 100644 index 000000000..ba0ba96dc --- /dev/null +++ b/jsowell-common/src/main/java/com/jsowell/common/util/wxplatform/PKCS7Encoder.java @@ -0,0 +1,61 @@ +package com.jsowell.common.util.wxplatform; + +import java.nio.charset.Charset; +import java.util.Arrays; + +/** + * TODO + * + * @author Lemon + * @Date 2023/7/27 16:18 + */ +public 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 = ""; + 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; + } +} diff --git a/jsowell-common/src/main/java/com/jsowell/common/util/wxplatform/WXBizMsgCrypt.java b/jsowell-common/src/main/java/com/jsowell/common/util/wxplatform/WXBizMsgCrypt.java new file mode 100644 index 000000000..5134b3c65 --- /dev/null +++ b/jsowell-common/src/main/java/com/jsowell/common/util/wxplatform/WXBizMsgCrypt.java @@ -0,0 +1,241 @@ +package com.jsowell.common.util.wxplatform; + +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 java.io.StringReader; +import java.nio.charset.Charset; +import java.security.MessageDigest; +import java.util.Arrays; + +import javax.crypto.Cipher; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; + +/** + * 提供接收和推送给公众平台消息的加解密接口(UTF8编码的字符串). + *
    *
  1. 第三方回复加密消息给公众平台
  2. *
  3. 第三方收到公众平台发送的消息,验证消息的安全性,并对消息进行解密。
  4. + *
+ * 说明:异常java.security.InvalidKeyException:illegal Key Size的解决方案 + *
    + *
  1. 在官方网站下载JCE无限制权限策略文件(JDK7的下载地址: * + * http://www.oracle.com/technetwork/java/javase/downloads/jce-7-download-432124.html
  2. + *
  3. 下载后解压,可以看到local_policy.jar和US_export_policy.jar以及readme.txt
  4. + *
  5. 如果安装了JRE,将两个jar文件放到%JRE_HOME%\lib\security目录下覆盖原来的文件
  6. + *
  7. 如果安装了JDK,将两个jar文件放到%JDK_HOME%\jre\lib\security目录下覆盖原来文件
  8. + * + *
+ */ +public class WXBizMsgCrypt { + static Charset CHARSET = Charset.forName("utf-8"); + Base64 base64 = new Base64(); + byte[] aesKey; + String token; + String appId; + + /** + * 构造函数 + * + * @param token 公众平台上,开发者设置的token + * @param encodingAesKey 公众平台上,开发者设置的EncodingAESKey + * @param appId 公众平台appid + * @throws AesException 执行失败,请查看该异常的错误码和具体的错误信息 + */ + 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个字节的网络字节序 + int recoverNetworkBytesOrder(byte[] orderBytes) { + int sourceNumber = 0; + for (int i = 0; i < 4; i++) { + sourceNumber <<= 8; + sourceNumber |= orderBytes[i] & 0xff; + } + return sourceNumber; + } + + /** + * 对密文进行解密. + * + * @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)); + 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_appid; + try { + // 去除补位字符 + byte[] bytes = PKCS7Encoder.decode(original); + + // 分离16位随机字符串,网络字节序和AppId + 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); + } + + // appid不相同的情况 + if (!from_appid.equals(appId)) { + throw new AesException(AesException.ValidateSignatureError); + } + return xmlContent; + + } + + /** + * * 检验消息的真实性,并且获取解密后的明文. + *
    + *
  1. 利用收到的密文生成安全签名,进行签名验证
  2. + *
  3. 若验证通过,则提取xml中的加密消息
  4. + *
  5. 对消息进行解密
  6. + *
+ * + * @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 = extract(postData); + + // 验证安全签名 + String signature = 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; + } + + /** + * 提取出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(); + 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这个元素,官网原示例代码在这里会报空 + //空指针,所以需要处理一下 + 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 票据 + * @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); + } + } +} diff --git a/jsowell-common/src/main/java/com/jsowell/common/util/wxplatform/WXXmlToMapUtil.java b/jsowell-common/src/main/java/com/jsowell/common/util/wxplatform/WXXmlToMapUtil.java new file mode 100644 index 000000000..1e1d335e5 --- /dev/null +++ b/jsowell-common/src/main/java/com/jsowell/common/util/wxplatform/WXXmlToMapUtil.java @@ -0,0 +1,229 @@ +package com.jsowell.common.util.wxplatform; + +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; + +/** + * 微信官方 xml --> Map 工具类 + * + * @author Lemon + * @Date 2023/7/27 16:06 + */ +public class WXXmlToMapUtil { + private static final Logger logger = LoggerFactory.getLogger(WXXmlToMapUtil.class); + + /** + * XML格式字符串转换为Map + * + * @param xml XML字符串 + * @return XML数据转换后的Map + */ + public static Map xmlToMap(String xml) { + try { + Map 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转换为XML格式的字符串 + * + * @param data Map类型数据 + * @return XML格式的字符串 + */ + public static String mapToXml(Map 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格式字符串转换为map + * + * @param xml xml字符串 + * @return 第一个为Root节点,Root节点之后为Root的元素,如果为多层,可以通过key获取下一层Map + */ + public static Map multilayerXmlToMap(String xml) { + Document doc = null; + try { + doc = DocumentHelper.parseText(xml); + } catch (DocumentException e) { + logger.error("xml字符串解析,失败 --> {}", e); + } + Map map = new HashMap<>(); + if (null == doc) { + return map; + } + // 获取根元素 + Element rootElement = doc.getRootElement(); + recursionXmlToMap(rootElement, map); + return map; + } + + /** + * multilayerXmlToMap核心方法,递归调用 + * + * @param element 节点元素 + * @param outmap 用于存储xml数据的map + */ + private static void recursionXmlToMap(Element element, Map outmap) { + // 得到根元素下的子元素列表 + List list = element.elements(); + int size = list.size(); + if (size == 0) { + // 如果没有子元素,则将其存储进map中 + outmap.put(element.getName(), element.getTextTrim()); + } else { + // innermap用于存储子元素的属性名和属性值 + Map innermap = new HashMap<>(); + // 遍历子元素 + list.forEach(childElement -> recursionXmlToMap(childElement, innermap)); + outmap.put(element.getName(), innermap); + } + } + + /** + * (多层)map转换为xml格式字符串 + * + * @param map 需要转换为xml的map + * @param isCDATA 是否加入CDATA标识符 true:加入 false:不加入 + * @return xml字符串 + */ + public static String multilayerMapToXml(Map 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核心方法,递归调用 + * + * @param element 节点元素 + * @param parentName 根元素属性名 + * @param map 需要转换为xml的map + * @param isCDATA 是否加入CDATA标识符 true:加入 false:不加入 + * @return xml字符串 + */ + private static String recursionMapToXml(Element element, String parentName, Map 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) 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声明与内容是否添加空行 + format.setNewLineAfterDeclaration(false); + // 是否设置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; + } + } +} diff --git a/jsowell-pile/src/main/java/com/jsowell/pile/domain/AuthorizationEventResult.java b/jsowell-pile/src/main/java/com/jsowell/pile/domain/AuthorizationEventResult.java new file mode 100644 index 000000000..1fda27ee3 --- /dev/null +++ b/jsowell-pile/src/main/java/com/jsowell/pile/domain/AuthorizationEventResult.java @@ -0,0 +1,17 @@ +package com.jsowell.pile.domain; + +import lombok.Data; + +/** + * 代开发小程序 授权事件接收URL 返回值 + * + * @author Lemon + * @Date 2023/7/27 15:54 + */ +@Data +public class AuthorizationEventResult { + private int code; + private String msg; + private String errorMsg; + private String data; +}