转自我的个人博客:微信公众号消息、加解密、模板消息封装
最近又在做个公众号相关的系统,之前写公众号经常动不动Map,这次实在看不下去了,基本都进行了封装。
以此文记录,一来以后复用,二来也是希望能帮助到有需要的人。
一、加、解密工具类EncryptUtils
public class EncryptUtils { private static Logger logger = LoggerFactory.getLogger(EncryptUtils.class); public static boolean checkSignature(String token, String signature, String timestamp, String nonce) { // 拼接字符串 String[] arr = new String[] { token, timestamp, nonce }; // 排序 Arrays.sort(arr); // 生成字符串 StringBuffer content = new StringBuffer(); for (int i = 0; i < arr.length; i++) { content.append(arr[i]); } // SHA1加密 String tmp = EncryptUtils.SHA1(content.toString()); return tmp.equals(signature); } /** * 用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); } } /** * 参数签名 * @param params 参与签名的参数 * @param key 密码 * @param type 签名算法 * @return * @throws Exception */ public static String getSign(Map<String,String> params, String key, String type)throws Exception{ if (params == null || params.isEmpty()){ return null; } String[] keys = new String[params.size()]; StringBuffer sb = new StringBuffer(); Set<String> strings = params.keySet(); keys = strings.toArray(keys); // 字符串排序 Arrays.sort(keys); for (int i = 0; i < keys.length; i++) { sb.append(keys[i]) .append("=") .append(params.get(keys[i])) .append("&"); } String str = sb.append("key=") .append(key).toString(); MessageDigest md = MessageDigest.getInstance(type); 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().toUpperCase(); } public static String SHA1(String decript) { try { MessageDigest digest = java.security.MessageDigest .getInstance("SHA-1"); digest.update(decript.getBytes()); byte messageDigest[] = digest.digest(); // Create Hex String StringBuffer hexString = new StringBuffer(); // 字节数组转换为 十六进制 数 for (int i = 0; i < messageDigest.length; i++) { String shaHex = Integer.toHexString(messageDigest[i] & 0xFF); if (shaHex.length() < 2) { hexString.append(0); } hexString.append(shaHex); } return hexString.toString(); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } return ""; } /** * * @param decript * @return */ public static String SHA(String decript) { try { MessageDigest digest = java.security.MessageDigest .getInstance("SHA"); digest.update(decript.getBytes()); byte messageDigest[] = digest.digest(); // Create Hex String StringBuffer hexString = new StringBuffer(); // 字节数组转换为 十六进制 数 for (int i = 0; i < messageDigest.length; i++) { String shaHex = Integer.toHexString(messageDigest[i] & 0xFF); if (shaHex.length() < 2) { hexString.append(0); } hexString.append(shaHex); } return hexString.toString(); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } return ""; } /** * 取字符串MD5值 * @param input * @return */ public static String MD5(String input) { try { // 获得MD5摘要算法的 MessageDigest 对象 MessageDigest mdInst = MessageDigest.getInstance("MD5"); // 使用指定的字节更新摘要 mdInst.update(input.getBytes()); // 获得密文 byte[] md = mdInst.digest(); // 把密文转换成十六进制的字符串形式 StringBuffer hexString = new StringBuffer(); // 字节数组转换为 十六进制 数 for (int i = 0; i < md.length; i++) { String shaHex = Integer.toHexString(md[i] & 0xFF); if (shaHex.length() < 2) { hexString.append(0); } hexString.append(shaHex); } return hexString.toString(); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } return ""; } /** * AES加密 * * @param content * 需要加密的内容 * @param password * 加密密码 * @return */ public static byte[] encryptAES(String content, String password) { try { KeyGenerator kgen = KeyGenerator.getInstance("AES"); kgen.init(128, new SecureRandom(password.getBytes())); SecretKey secretKey = kgen.generateKey(); byte[] enCodeFormat = secretKey.getEncoded(); SecretKeySpec key = new SecretKeySpec(enCodeFormat, "AES"); Cipher cipher = Cipher.getInstance("AES");// 创建密码器 byte[] byteContent = content.getBytes("utf-8"); cipher.init(Cipher.ENCRYPT_MODE, key);// 初始化 byte[] result = cipher.doFinal(byteContent); return result; // 加密 } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } catch (NoSuchPaddingException e) { e.printStackTrace(); } catch (InvalidKeyException e) { e.printStackTrace(); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } catch (IllegalBlockSizeException e) { e.printStackTrace(); } catch (BadPaddingException e) { e.printStackTrace(); } return null; } /** * AES解密 * * @param content * 待解密内容 * @param password * 解密密钥 * @return */ public static byte[] decryptAES(byte[] content, String password) { try { KeyGenerator kgen = KeyGenerator.getInstance("AES"); kgen.init(128, new SecureRandom(password.getBytes())); SecretKey secretKey = kgen.generateKey(); byte[] enCodeFormat = secretKey.getEncoded(); SecretKeySpec key = new SecretKeySpec(enCodeFormat, "AES"); Cipher cipher = Cipher.getInstance("AES");// 创建密码器 cipher.init(Cipher.DECRYPT_MODE, key);// 初始化 byte[] result = cipher.doFinal(content); return result; // 加密 } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } catch (NoSuchPaddingException e) { e.printStackTrace(); } catch (InvalidKeyException e) { e.printStackTrace(); } catch (IllegalBlockSizeException e) { e.printStackTrace(); } catch (BadPaddingException e) { e.printStackTrace(); } return null; } /** * 创建微信支付密钥 * @param params * @param key * @return */ public static String createPaySign(Map params, String key){ List ls = new ArrayList(); Set keys = params.keySet(); Iterator iterator = keys.iterator(); while (iterator.hasNext()){ String paramKey = iterator.next().toString(); ls.add(paramKey); } Collections.sort(ls); String str = ""; for (int i = 0; i < ls.size(); i++) { str += ls.get(i) + "=" + params.get(ls.get(i)) + "&"; } str += "key=" + key; return MD5(str).toUpperCase(); } }
二、消息封装
1、来自微信服务器的消息ReceiveMessage
@Data @XmlAccessorType(XmlAccessType.FIELD) @XmlRootElement(name = "xml") public class ReceiveMessage { @XmlElement(name = "ToUserName", required = true) private String toUserName; @XmlElement(name = "FromUserName", required = true) private String fromUserName; @XmlElement(name = "CreateTime", required = true) private Long createTime; @XmlElement(name = "MsgType", required = true) private String msgType; //文本消息内容 @XmlElement(name = "Content") private String content; @XmlElement(name = "MsgId", required = true) private Integer msgId; @XmlElement(name = "PicUrl") private String picUrl; @XmlElement(name = "MediaId") private String mediaId; @XmlElement(name = "Format") private String format; @XmlElement(name = "Recognition") private String recognition; @XmlElement(name = "ThumbMediaId") private String thumbMediaId; //地理位置纬度 @XmlElement(name = "Location_X") private Double locationX; //地理位置经度 @XmlElement(name = "Location_Y") private Double locationY; //地图缩放大小 @XmlElement(name = "Scale") private Integer scale; //地理位置信息 @XmlElement(name = "Label") private String label; @XmlElement(name = "Title") private String title; @XmlElement(name = "Description") private String description; @XmlElement(name = "Url") private String url; @XmlElement(name = "Event") private String event; @XmlElement(name = "EventKey") private String eventKey; @XmlElement(name = "Ticket") private String ticket; //事件消息 地理位置纬度 @XmlElement(name = "Latitude") private Double latitude; //事件消息 地理位置经度 @XmlElement(name = "Longitude") private Double longitude; //地理位置精度 @XmlElement(name = "Precision") private Double precision; }
2、回复微信服务器的消息ReplyMessage
@Data @XmlAccessorType(XmlAccessType.FIELD) @XmlRootElement(name = "xml") public class ReplyMessage { @JSONField(name = "touser") @XmlElement(name = "ToUserName", required = true) private String toUserName; @XmlElement(name = "FromUserName", required = true) private String fromUserName; @XmlElement(name = "CreateTime", required = true) private Long createTime; @JSONField(name = "msgtype") @XmlElement(name = "MsgType", required = true) private String msgType; @XmlElement(name = "Content", required = false) private String content; @JSONField(name = "text") private Text sendContent; @XmlElement(name = "ArticleCount", required = true) private Integer articleCount; @XmlElementWrapper(name = "Articles") @XmlElement(name = "item", required = false) private List<Article> Articles; @JSONField(name = "news") private ArticlesDto articlesDto; @JSONField(name = "image") @XmlElement(name = "Image", required = false) private Image image; @JSONField(name = "voice") @XmlElement(name = "Voice", required = false) private Voice voice; @JSONField(name = "video") @XmlElement(name = "Video", required = false) private Video video; @JSONField(name = "music") @XmlElement(name = "Music", required = false) private Music music; }
回复文本消息Text
@Data public class Text { @JSONField(name = "content") private String content; }
回复图文消息Article
@Data @XmlAccessorType(XmlAccessType.FIELD) @XmlRootElement(name = "xml") public class Article { @JSONField(name = "title") @XmlElement(name = "Title", required = true) private String title; @JSONField(name = "description") @XmlElement(name = "Description", required = true) private String Description; @JSONField(name = "picurl") @XmlElement(name = "PicUrl", required = true) private String PicUrl; @JSONField(name = "url") @XmlElement(name = "Url", required = true) private String Url; }
回复图片消息Image
@Data @XmlAccessorType(XmlAccessType.FIELD) public class Image { @JSONField(name = "media_id") @XmlElement(name = "MediaId", required = true) private String mediaId; }
回复语音消息Voice
@Data @XmlAccessorType(XmlAccessType.FIELD) public class Voice { @JSONField(name = "media_id") @XmlElement(name = "MediaId", required = true) private String mediaId; }
回复视频消息Video
@Data @XmlAccessorType(XmlAccessType.FIELD) public class Video { @JSONField(name = "media_id") @XmlElement(name = "MediaId", required = true) private String mediaId; @JSONField(name = "title") @XmlElement(name = "Title") private String title; @JSONField(name = "description") @XmlElement(name = "Description") private String description; @JSONField(name = "thumb_media_id") private String thumbMediaId; }
回复音乐消息Music???????
@Data @XmlAccessorType(XmlAccessType.FIELD) public class Music { @JSONField(name = "title") @XmlElement(name = "Title") private String title; @JSONField(name = "description") @XmlElement(name = "Description") private String description; @JSONField(name = "musicurl") @XmlElement(name = "MusicURL") private String musicUrl; @JSONField(name = "hqmusicurl") @XmlElement(name = "HQMusicUrl") private String hQMusicUrl; @JSONField(name = "thumb_media_id") @XmlElement(name = "ThumbMediaId", required = true) private String thumbMediaId; }
消息XML解析工具类XMLParse
package com.gaoxiaobo.download.utils; import com.gaoxiaobo.download.exception.AesException; import org.dom4j.DocumentException; import org.dom4j.io.SAXReader; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.InputSource; import javax.servlet.http.HttpServletRequest; import javax.xml.bind.JAXBContext; import javax.xml.bind.Marshaller; import javax.xml.bind.Unmarshaller; 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.*; import java.util.Enumeration; import java.util.HashMap; import java.util.List; import java.util.Map; /** * @Auther: gaoxiaobo * @Date: 2020/8/3 21:35 * @Description: */ public 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(); 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(); result[2] = nodelist2.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 toUser, String timestamp, String nonce) { String format = "<xml>\n" + "<Encrypt><![CDATA[%1$s]]></Encrypt>\n" + "<MsgSignature><![CDATA[%2$s]]></MsgSignature>\n" + "<ToUserName><![CDATA[%3$s]]></ToUserName>" + "<TimeStamp>%4$s</TimeStamp>\n" + "<Nonce><![CDATA[%5$s]]></Nonce>\n" + "</xml>"; return String.format(format, encrypt, signature, toUser, timestamp, nonce); } /** * 将Map转换为XML格式的字符串 * * @param data Map类型数据 * @return XML格式的字符串 * @throws Exception */ public static String mapToXml(Map<String, Object> 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) == null ? null : data.get(key).toString(); 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 XML数据转换后的Map * @throws Exception */ 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; } } @SuppressWarnings("unchecked") public static<T> T xmlToBean(InputStream is,Class<T> c){ T t = null; try { JAXBContext context = JAXBContext.newInstance(c); Unmarshaller unmarshaller = context.createUnmarshaller(); t = (T)unmarshaller.unmarshal(is); } catch (Exception e) { e.printStackTrace(); } return t; } @SuppressWarnings("unchecked") public static <T> T xmlToBean(String xml, Class<T> beanClass)throws Exception{ JAXBContext context = JAXBContext.newInstance(beanClass); Unmarshaller unmarshaller = context.createUnmarshaller(); return (T) unmarshaller.unmarshal(new StringReader(xml)); } public static String beanToXML(Object bean)throws Exception{ JAXBContext context = JAXBContext.newInstance(bean.getClass()); Marshaller marshaller = context.createMarshaller(); marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); marshaller.setProperty(Marshaller.JAXB_ENCODING, "UTF-8"); StringWriter writer = new StringWriter(); marshaller.marshal(bean, writer); return writer.toString(); } public static Map<String, String> xmlToMap(HttpServletRequest request) { Map<String, String> map = new HashMap<String, String>(); SAXReader reader = new SAXReader(); InputStream ins = null; try { ins = request.getInputStream(); } catch (IOException e1) { e1.printStackTrace(); } org.dom4j.Document doc = null; try { doc = reader.read(ins); } catch (DocumentException e1) { e1.printStackTrace(); } org.dom4j.Element root = doc.getRootElement(); @SuppressWarnings("unchecked") List<org.dom4j.Element> list = root.elements(); for (org.dom4j.Element e : list) { map.put(e.getName(), e.getText()); } try { ins.close(); } catch (IOException e1) { e1.printStackTrace(); } return map; } }
模板消息封装TemplateMessage
package com.gaoxiaobo.download.component.wx.message; import com.alibaba.fastjson.annotation.JSONField; import lombok.Data; import java.util.Map; @Data public class TemplateMessage { private String touser; @JSONField(name = "template_id") private String templateId; private String url; private String topcolor; private Map<String, TemplateData> data; }
package com.gaoxiaobo.download.component.wx.message; import lombok.Data; @Data public class TemplateData { private String color; private String value; }