1.从request获取推送xml包
String callBackXml = testNoticeService.formatNoticeParams(request);
public static String formatNoticeParams(HttpServletRequest request){
try(ByteArrayOutputStream output = new ByteArrayOutputStream();
InputStream input = request.getInputStream()){
byte[] by = new byte[1024];
int length = 0;
while((length = input .read(by)) != -1){
output.write(by, 0, length);
}
return new String(output.toByteArray(), "UTF-8");
}catch(IOException e){
throw new BusinessException(ConstCode.PARAM.getCode(), "回调参数解析异常");
}
}
2.转换成map
Map<String, String> baseMap = new Parser(callBackXml).xmlToMap();
public final class Parser {
private String xmlString;
public Parser(String xmlString) {
if (StringUtils.isEmpty(xmlString)) {
throw new BusinessException(ConstCode.FAILED.getCode(), "解析内容为空");
}
if (!isXML(xmlString)) {
throw new BusinessException(ConstCode.FAILED.getCode(), "解析内容不是合法的xml格式");
}
this.xmlString = xmlString;
}
}
private boolean isXML(String value) {
try {
DocumentHelper.parseText(value);
} catch (DocumentException e) {
return false;
}
return true;
}
private static InputStream getStringStream(String sInputString) {
ByteArrayInputStream tInputStringStream = null;
if (sInputString != null && !"".equals(sInputString.trim())) {
tInputStringStream = new ByteArrayInputStream(sInputString.getBytes());
}
return tInputStringStream;
}
public synchronized Map<String, String> xmlToMap() throws ParserConfigurationException, IOException, SAXException {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
InputStream is = getStringStream(xmlString);
Document document = builder.parse(is);
NodeList allNodes = document.getFirstChild().getChildNodes();
Node node;
Map<String, String> map = new HashMap<>(0);
int i = 0;
while (i < allNodes.getLength()) {
node = allNodes.item(i);
if (node instanceof Element) {
map.put(node.getNodeName(), node.getTextContent());
}
i++;
}
return map;
}
3.初始化并获得解密消息内容,appId,token,encodingAesKey从微信公众号平台基本配置获取到
String xmlContent = new WxMsgCryptUtils(appId,token,encodingAesKey).decrypt(baseMap.get("Encrypt"));
public class WxMsgCryptUtils {
/**
* 公众平台上,开发者设置的 token
*/
private static String token;
/**
* 公众平台 appID
*/
private static String appID;
/**
* 公众平台上,开发者设置的 EncodingAESKey
*/
private static byte[] aesKey;
/**
* UTF_8 字符集
*/
private static final Charset UTF_8 = StandardCharsets.UTF_8;
/**
* 初始化
*
* @param appID 公众平台 appID
* @param token 公众平台上,开发者设置的 token
* @param aesKey 公众平台上,开发者设置的 EncodingAESKey
*/
public WxMsgCryptUtils(String appID, String token, String aesKey) {
if (StrUtil.hasBlank(appID, token, aesKey) || aesKey.length() != 43) {
throw new IllegalArgumentException("微信公众号配置信息有误");
}
this.appID = appID;
this.token = token;
this.aesKey = Base64.decode(aesKey);
}
/**
* 4 个字节的网络字节序 bytes 数组还原成一个数字.
*/
public static int bytesNetworkOrder2Number(byte[] bytesInNetworkOrder) {
int sourceNumber = 0;
for (int i = 0; i < 4; i++) {
sourceNumber <<= 8;
sourceNumber |= bytesInNetworkOrder[i] & 0xff;
}
return sourceNumber;
}
/**
* 删除解密后明文的补位字符.
*
* @param decrypted 解密后的明文
* @return 删除补位字符后的明文
*/
public static byte[] decode(byte[] decrypted) {
int pad = decrypted[decrypted.length - 1];
if (pad < 1 || pad > 32) {
pad = 0;
}
return Arrays.copyOfRange(decrypted, 0, decrypted.length - pad);
}
/**
* 对密文进行解密.
*
* @param cipherText 需要解密的密文
* @return 解密得到的明文
*/
public String decrypt(String cipherText) throws Exception {
// 设置解密模式为AES的CBC模式
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
SecretKeySpec keySpec = new SecretKeySpec(aesKey, "AES");
IvParameterSpec iv = new IvParameterSpec(Arrays.copyOfRange(aesKey, 0, 16));
cipher.init(Cipher.DECRYPT_MODE, keySpec, iv);
// 使用BASE64对密文进行解码
byte[] encrypted = Base64.decode(cipherText);
// 解密
byte[] original = cipher.doFinal(encrypted);
byte[] bytes = decode(original);
// 分离16位随机字符串,网络字节序和AppId
byte[] networkOrder = Arrays.copyOfRange(bytes, 16, 20);
int xmlLength = bytesNetworkOrder2Number(networkOrder);
String xmlContent = new String(Arrays.copyOfRange(bytes, 20, 20 + xmlLength), UTF_8);
return xmlContent;
}
}
4.解析成实体对象
WechatFollowVo wechatFollowVo = (WechatFollowVo) XmlUtil.fromXML(xmlContent, WechatFollowVo.class);
public class XmlUtil {
/**
* xml⽂档解析为对象
* @param xml xml文档
* @param clazz 要转换的类
* @return
*/
public static Object fromXML(String xml, Class clazz){
XStream xmlStream = new XStream(new XppDriver(new XmlFriendlyNameCoder("_-", "_")));
XStream.setupDefaultSecurity(xmlStream);
xmlStream.processAnnotations(new Class[]{clazz});
xmlStream.allowTypes(new Class[]{clazz});
xmlStream.ignoreUnknownElements();
Object result = xmlStream.fromXML(xml);
return result;
}
}
@Data
@Accessors(chain = true)
@XStreamAlias(value = "xml")
public class WechatFollowVo implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty("开发者微信号")
@XStreamAlias(value = "ToUserName")
private String toUserName;
@ApiModelProperty("发送?帐号(?个OpenID)")
@XStreamAlias(value = "FromUserName")
private String fromUserName;
@ApiModelProperty("消息创建时间 (整型)")
@XStreamAlias(value = "CreateTime")
private Long createTime;
@ApiModelProperty("消息类型,event")
@XStreamAlias(value = "MsgType")
private String messageType;
@ApiModelProperty("事件类型,subscribe(订阅)、unsubscribe(取消订阅)")
@XStreamAlias(value = "Event")
private String event;
@ApiModelProperty("事件 KEY 值,未关注:qrscene_为前缀,后面为二维码的参数值,以关注:是一个32位无符号整数,即创建二维码时的二维码scene_id")
@XStreamAlias(value = "EventKey")
private String eventKey;
@ApiModelProperty("二维码的ticket,可用来换取二维码图片")
@XStreamAlias(value = "Ticket")
private String ticket;
@ApiModelProperty("消息内容")
@XStreamAlias(value = "Content")
private String content;
}
以上步骤已经完成了xml数据包转换成实体对象。当时写到try()里的声明,有些不明,也去查看了百度。好似有些懂了。Java7的新特性,作用相当于finally手动释放被占用的资源,如果使用之后,就会在程序块结束时自动释放掉占用的资源,代码也更简洁美观。前提是括号声明的资源实现类实现了Closeable或AutoCloseable接口。
注意:多个资源需要分号隔开