mirror of
https://codeup.aliyun.com/67c68d4e484ca2f0a13ac3c1/ydc/jsowell-charger-web.git
synced 2026-04-20 02:55:04 +08:00
新增 代开发小程序 授权事件接收URL,验证票据接口
This commit is contained in:
@@ -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<AuthorizationEventResult> wechatPlatformEvent(@RequestParam("timestamp") String timestamp,
|
||||
@RequestParam("nonce") String nonce,
|
||||
@RequestParam("msg_signature") String msgSignature,
|
||||
@RequestBody String postData) {
|
||||
AuthorizationEventResult<String> resultBean = new AuthorizationEventResult<>();
|
||||
ResponseEntity<AuthorizationEventResult> 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<String> errorResultBean = new AuthorizationEventResult<>();
|
||||
errorResultBean.setMsg("第三方平台授权事件接收URL,验证票据异常");
|
||||
errorResultBean.setErrorMsg(e.getMessage());
|
||||
errorResultBean.setCode(422);
|
||||
responseEntity = new ResponseEntity<>(errorResultBean, HttpStatus.UNPROCESSABLE_ENTITY);
|
||||
}
|
||||
return responseEntity;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -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<String, String> 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";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -138,6 +138,13 @@
|
||||
<artifactId>guava</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!--dom4j -->
|
||||
<dependency>
|
||||
<groupId>dom4j</groupId>
|
||||
<artifactId>dom4j</artifactId>
|
||||
<version>1.6.1</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.tencentcloudapi</groupId>
|
||||
<artifactId>tencentcloud-sdk-java</artifactId>
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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编码的字符串).
|
||||
* <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 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;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* * 检验消息的真实性,并且获取解密后的明文.
|
||||
* <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 = 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<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转换为XML格式的字符串
|
||||
*
|
||||
* @param data Map类型数据
|
||||
* @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格式字符串转换为map
|
||||
*
|
||||
* @param xml xml字符串
|
||||
* @return 第一个为Root节点,Root节点之后为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核心方法,递归调用
|
||||
*
|
||||
* @param element 节点元素
|
||||
* @param outmap 用于存储xml数据的map
|
||||
*/
|
||||
private static void recursionXmlToMap(Element element, Map<String, Object> outmap) {
|
||||
// 得到根元素下的子元素列表
|
||||
List<Element> list = element.elements();
|
||||
int size = list.size();
|
||||
if (size == 0) {
|
||||
// 如果没有子元素,则将其存储进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转换为xml格式字符串
|
||||
*
|
||||
* @param map 需要转换为xml的map
|
||||
* @param isCDATA 是否加入CDATA标识符 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核心方法,递归调用
|
||||
*
|
||||
* @param element 节点元素
|
||||
* @param parentName 根元素属性名
|
||||
* @param map 需要转换为xml的map
|
||||
* @param isCDATA 是否加入CDATA标识符 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声明与内容是否添加空行
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<T> {
|
||||
private int code;
|
||||
private String msg;
|
||||
private String errorMsg;
|
||||
private String data;
|
||||
}
|
||||
Reference in New Issue
Block a user