WxUtil.java 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446
  1. package com.repair.common.utils;
  2. import com.repair.config.WxOpenidConfig;
  3. import org.apache.commons.codec.digest.DigestUtils;
  4. import org.springframework.beans.factory.annotation.Autowired;
  5. import org.w3c.dom.Node;
  6. import org.w3c.dom.NodeList;
  7. import javax.crypto.Mac;
  8. import javax.crypto.spec.SecretKeySpec;
  9. import javax.servlet.ServletOutputStream;
  10. import javax.servlet.http.HttpServletResponse;
  11. import javax.xml.parsers.DocumentBuilder;
  12. import javax.xml.parsers.DocumentBuilderFactory;
  13. import javax.xml.transform.OutputKeys;
  14. import javax.xml.transform.Transformer;
  15. import javax.xml.transform.TransformerFactory;
  16. import javax.xml.transform.dom.DOMSource;
  17. import javax.xml.transform.stream.StreamResult;
  18. import java.io.*;
  19. import java.security.MessageDigest;
  20. import java.text.SimpleDateFormat;
  21. import java.util.*;
  22. /**
  23. * 微信公众号接口工具类
  24. * @author lujunjie
  25. * @date 2018/03/01
  26. */
  27. public class WxUtil {
  28. @Autowired
  29. private static WxOpenidConfig wxOpenidConfig;
  30. /**
  31. * 加密/校验流程如下:
  32. * 1. 将token、timestamp、nonce三个参数进行字典序排序<br>
  33. * 2. 将三个参数字符串拼接成一个字符串进行sha1加密<br>
  34. * 3. 开发者获得加密后的字符串可与signature对比,标识该请求来源于微信<br>
  35. *
  36. * @param token Token验证密钥
  37. * @param signature 微信加密签名,signature结合了开发者填写的token参数和请求中的timestamp参数,nonce参数
  38. * @param timestamp 时间戳
  39. * @param nonce 随机数
  40. * @return 验证成功返回:true,失败返回:false
  41. */
  42. public static boolean checkSignature(String token, String signature, String timestamp, String nonce) {
  43. List<String> params = new ArrayList<String>();
  44. params.add(token);
  45. params.add(timestamp);
  46. params.add(nonce);
  47. //1. 将token、timestamp、nonce三个参数进行字典序排序
  48. Collections.sort(params, new Comparator<String>() {
  49. @Override
  50. public int compare(String o1, String o2) {
  51. return o1.compareTo(o2);
  52. }
  53. });
  54. //2. 将三个参数字符串拼接成一个字符串进行sha1加密
  55. String temp = SHA1.encode(params.get(0) + params.get(1) + params.get(2));
  56. //3. 开发者获得加密后的字符串可与signature对比,标识该请求来源于微信
  57. return temp.equals(signature);
  58. }
  59. /**
  60. * 输入流转化为字符串
  61. * @param inputStream 流
  62. * @return String 字符串
  63. * @throws Exception
  64. */
  65. public static String getStreamString(InputStream inputStream) throws Exception{
  66. StringBuffer buffer=new StringBuffer();
  67. InputStreamReader inputStreamReader = null;
  68. BufferedReader bufferedReader = null;
  69. try{
  70. inputStreamReader=new InputStreamReader(inputStream, WxConstants.DEFAULT_CHARSET);
  71. bufferedReader=new BufferedReader(inputStreamReader);
  72. String line;
  73. while((line=bufferedReader.readLine())!=null){
  74. buffer.append(line);
  75. }
  76. }catch(Exception e){
  77. throw new Exception();
  78. }finally {
  79. if(bufferedReader != null){
  80. bufferedReader.close();
  81. }
  82. if(inputStreamReader != null){
  83. inputStreamReader.close();
  84. }
  85. if(inputStream != null){
  86. inputStream.close();
  87. }
  88. }
  89. return buffer.toString();
  90. }
  91. /**
  92. * 获取随机字符串 Nonce Str
  93. * @return String 随机字符串
  94. */
  95. public static String getNonceStr() {
  96. return UUID.randomUUID().toString().replaceAll("-", "").substring(0, 32);
  97. }
  98. /**
  99. * 生成签名. 注意,若含有sign_type字段,必须和signType参数保持一致。
  100. * @param data 待签名数据
  101. * @param apikey API密钥
  102. * @return 签名
  103. */
  104. public static String getSignature(final Map<String, String> data, String apikey,
  105. String signType) throws Exception {
  106. Set<String> keySet = data.keySet();
  107. String[] keyArray = keySet.toArray(new String[keySet.size()]);
  108. Arrays.sort(keyArray);
  109. StringBuilder sb = new StringBuilder();
  110. sb.append(apikey);
  111. sb.append("amount10apicodeCS0001collect_code");
  112. sb.append(data.get("collect_code").trim());
  113. sb.append("order_no");
  114. sb.append(data.get("order_no").trim());
  115. sb.append("timestamp");
  116. sb.append(data.get("timestamp").trim());
  117. sb.append(apikey);
  118. System.out.println(sb);
  119. if (signType.equals(WxConstants.SING_MD5)) {
  120. System.out.println(MD5(sb.toString()).toUpperCase());
  121. return MD5(sb.toString()).toLowerCase();
  122. }
  123. else if (signType.equals(WxConstants.SING_HMACSHA256)) {
  124. return HMACSHA256(sb.toString(), apikey);
  125. }
  126. else {
  127. throw new Exception(String.format("Invalid sign_type: %s", signType));
  128. }
  129. }
  130. /**
  131. * 生成 MD5
  132. * @param data 待处理数据
  133. * @return MD5结果
  134. */
  135. public static String MD5(String data) throws Exception {
  136. MessageDigest md = MessageDigest.getInstance("MD5");
  137. byte[] array = md.digest(data.getBytes("UTF-8"));
  138. StringBuilder sb = new StringBuilder();
  139. for (byte item : array) {
  140. sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
  141. }
  142. return sb.toString().toUpperCase();
  143. }
  144. /**
  145. * 生成 HMACSHA256
  146. * @param data 待处理数据
  147. * @param key 密钥
  148. * @return 加密结果
  149. * @throws Exception
  150. */
  151. public static String HMACSHA256(String data, String key) throws Exception {
  152. Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
  153. SecretKeySpec secret_key = new SecretKeySpec(key.getBytes("UTF-8"), "HmacSHA256");
  154. sha256_HMAC.init(secret_key);
  155. byte[] array = sha256_HMAC.doFinal(data.getBytes("UTF-8"));
  156. StringBuilder sb = new StringBuilder();
  157. for (byte item : array) {
  158. sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
  159. }
  160. return sb.toString().toUpperCase();
  161. }
  162. /**
  163. * @param data Map类型数据
  164. * @return XML格式的字符串
  165. * @throws Exception
  166. */
  167. public static String mapToXml(Map<String, String> data) throws Exception {
  168. DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
  169. DocumentBuilder documentBuilder= documentBuilderFactory.newDocumentBuilder();
  170. org.w3c.dom.Document document = documentBuilder.newDocument();
  171. org.w3c.dom.Element root = document.createElement("xml");
  172. document.appendChild(root);
  173. for (String key: data.keySet()) {
  174. String value = data.get(key);
  175. if (value == null) {
  176. value = "";
  177. }
  178. value = value.trim();
  179. org.w3c.dom.Element filed = document.createElement(key);
  180. filed.appendChild(document.createTextNode(value));
  181. root.appendChild(filed);
  182. }
  183. TransformerFactory tf = TransformerFactory.newInstance();
  184. Transformer transformer = tf.newTransformer();
  185. DOMSource source = new DOMSource(document);
  186. transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
  187. transformer.setOutputProperty(OutputKeys.INDENT, "yes");
  188. StringWriter writer = new StringWriter();
  189. StreamResult result = new StreamResult(writer);
  190. transformer.transform(source, result);
  191. String output = writer.getBuffer().toString(); //.replaceAll("\n|\r", "");
  192. try {
  193. writer.close();
  194. }
  195. catch (Exception ex) {
  196. }
  197. return output;
  198. }
  199. /**
  200. * 处理 HTTPS API返回数据,转换成Map对象。return_code为SUCCESS时,验证签名。
  201. * @param xmlStr API返回的XML格式数据
  202. * @return Map类型数据
  203. * @throws Exception
  204. */
  205. public static Map<String, String> processResponseXml(String xmlStr,String signType) throws Exception {
  206. String RETURN_CODE = WxConstants.RETURN_CODE;
  207. String return_code;
  208. Map<String, String> respData = xmlToMap(xmlStr);
  209. if (respData.containsKey(RETURN_CODE)) {
  210. return_code = respData.get(RETURN_CODE);
  211. }
  212. else {
  213. throw new Exception(String.format("No `return_code` in XML: %s", xmlStr));
  214. }
  215. if (return_code.equals("FAIL")) {
  216. return respData;
  217. }
  218. else if (return_code.equals("SUCCESS")) {
  219. if (isResponseSignatureValid(respData,signType)) {
  220. return respData;
  221. }
  222. else {
  223. throw new Exception(String.format("Invalid sign value in XML: %s", xmlStr));
  224. }
  225. }
  226. else {
  227. throw new Exception(String.format("return_code value %s is invalid in XML: %s", return_code, xmlStr));
  228. }
  229. }
  230. /**
  231. * XML格式字符串转换为Map
  232. * @param strXML XML字符串
  233. * @return XML数据转换后的Map
  234. * @throws Exception
  235. */
  236. public static Map<String, String> xmlToMap(String strXML) throws Exception {
  237. try {
  238. Map<String, String> data = new HashMap<String, String>();
  239. DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
  240. String FEATURE = "http://apache.org/xml/features/disallow-doctype-decl";
  241. documentBuilderFactory.setFeature(FEATURE, true);
  242. FEATURE = "http://xml.org/sax/features/external-general-entities";
  243. documentBuilderFactory.setFeature(FEATURE, false);
  244. FEATURE = "http://xml.org/sax/features/external-parameter-entities";
  245. documentBuilderFactory.setFeature(FEATURE, false);
  246. FEATURE = "http://apache.org/xml/features/nonvalidating/load-external-dtd";
  247. documentBuilderFactory.setFeature(FEATURE, false);
  248. documentBuilderFactory.setXIncludeAware(false);
  249. documentBuilderFactory.setExpandEntityReferences(false);
  250. DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
  251. InputStream stream = new ByteArrayInputStream(strXML.getBytes("UTF-8"));
  252. org.w3c.dom.Document doc = documentBuilder.parse(stream);
  253. doc.getDocumentElement().normalize();
  254. NodeList nodeList = doc.getDocumentElement().getChildNodes();
  255. for (int idx = 0; idx < nodeList.getLength(); ++idx) {
  256. Node node = nodeList.item(idx);
  257. if (node.getNodeType() == Node.ELEMENT_NODE) {
  258. org.w3c.dom.Element element = (org.w3c.dom.Element) node;
  259. data.put(element.getNodeName(), element.getTextContent());
  260. }
  261. }
  262. try {
  263. stream.close();
  264. } catch (Exception ex) {
  265. // do nothing
  266. }
  267. return data;
  268. } catch (Exception ex) {
  269. throw ex;
  270. }
  271. }
  272. /**
  273. * 判断xml数据的sign是否有效,必须包含sign字段,否则返回false。
  274. * @param reqData 向wxpay post的请求数据
  275. * @return 签名是否有效
  276. * @throws Exception
  277. */
  278. private static boolean isResponseSignatureValid(final Map<String, String> reqData,String signType) throws Exception {
  279. // 返回数据的签名方式和请求中给定的签名方式是一致的
  280. return isSignatureValid(reqData,wxOpenidConfig.getAppkey(),signType);
  281. }
  282. /**
  283. * 判断签名是否正确,必须包含sign字段,否则返回false。
  284. * @param data Map类型数据
  285. * @param key API密钥
  286. * @param signType 签名方式
  287. * @return 签名是否正确
  288. * @throws Exception
  289. */
  290. public static boolean isSignatureValid(Map<String, String> data, String key, String signType) throws Exception {
  291. if (!data.containsKey("sign")) {
  292. return false;
  293. }
  294. String sign = data.get("sign");
  295. return getSignature(data, key, signType).equals(sign);
  296. }
  297. // /**
  298. // * 生成支付二维码
  299. // * @param response 响应
  300. // * @param contents url链接
  301. // * @throws Exception
  302. // */
  303. // public static void writerPayImage(HttpServletResponse response, String contents) throws Exception{
  304. // ServletOutputStream out = response.getOutputStream();
  305. // try {
  306. // Map<EncodeHintType,Object> hints = new HashMap<EncodeHintType,Object>();
  307. // hints.put(EncodeHintType.CHARACTER_SET,"UTF-8");
  308. // hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.L);
  309. // hints.put(EncodeHintType.MARGIN, 0);
  310. // BitMatrix bitMatrix = new MultiFormatWriter().encode(contents, BarcodeFormat.QR_CODE,300,300,hints);
  311. // MatrixToImageWriter.writeToStream(bitMatrix,"jpg",out);
  312. // }catch (Exception e){
  313. // throw new Exception("生成二维码失败!");
  314. // }finally {
  315. // if(out != null){
  316. // out.flush();
  317. // out.close();
  318. // }
  319. // }
  320. // }
  321. /**
  322. * 生成商户订单号
  323. * @return String
  324. */
  325. public static String mchOrderNo(){
  326. SimpleDateFormat sdf = new SimpleDateFormat("yyMMddHHmmss");
  327. String date = sdf.format(new Date());
  328. Random random = new Random();
  329. String fourRandom = String.valueOf(random.nextInt(10000));
  330. int randLength = fourRandom.length();
  331. //不足4位继续补充
  332. if(randLength<4){
  333. for(int remain = 1; remain <= 4 - randLength; remain ++ ){
  334. fourRandom += random.nextInt(10) ;
  335. }
  336. }
  337. return date+fourRandom;
  338. }
  339. /**
  340. * 返回信息给微信
  341. * @param response
  342. * @param content 内容
  343. * @throws Exception
  344. */
  345. public static void responsePrint(HttpServletResponse response, String content) throws Exception{
  346. response.setCharacterEncoding("UTF-8");
  347. response.setContentType("text/xml");
  348. response.getWriter().print(content);
  349. response.getWriter().flush();
  350. response.getWriter().close();
  351. }
  352. /**
  353. * 获得随机字符串
  354. *
  355. * @return
  356. */
  357. public static String getWxNonceStr() {
  358. Random random = new Random();
  359. long val = random.nextLong();
  360. String res = DigestUtils.md5Hex(val + "yzx").toUpperCase();
  361. if (32 < res.length())
  362. return res.substring(0, 32);
  363. else
  364. return res;
  365. }
  366. /**
  367. * 获得签名
  368. *
  369. * @param params
  370. * 待编码参数,参数值为空不传入
  371. * @param key
  372. * key设置路径:微信商户平台(pay.weixin.qq.com)-->账户设置-->API安全-->密钥设置
  373. * @return
  374. */
  375. public static String getSign(Map<String, String> params, String key)
  376. throws Exception {
  377. List<String> list = new ArrayList<String>(params.keySet());
  378. Collections.sort(list, new DictionaryCompare());
  379. StringBuffer sb = new StringBuffer();
  380. for (String keyVal : list) {
  381. if (params.get(keyVal) != null) {
  382. sb.append(keyVal + "=" + params.get(keyVal) + "&");
  383. }
  384. }
  385. sb.append("key=" + key);
  386. return DigestUtils
  387. .md5Hex(new String(sb.toString().getBytes(), "utf-8"))
  388. .toUpperCase();
  389. }
  390. /**
  391. * 把map转换成xml格式
  392. * @param params
  393. * @return
  394. * @throws Exception
  395. */
  396. public static String getRequestXml(Map<String, String> params) throws Exception{
  397. StringBuffer sb = new StringBuffer();
  398. sb.append("<xml>");
  399. Set<Map.Entry<String, String>> es = params.entrySet();
  400. Iterator<Map.Entry<String, String>> it = es.iterator();
  401. while (it.hasNext()) {
  402. Map.Entry<String, String> entry = (Map.Entry<String, String>) it
  403. .next();
  404. String k = (String) entry.getKey();
  405. String v = (String) entry.getValue();
  406. if ("attach".equalsIgnoreCase(k) || "body".equalsIgnoreCase(k)) {
  407. sb.append("<" + k + ">" + "<![CDATA[" + v + "]]></" + k + ">");
  408. } else {
  409. sb.append("<" + k + ">" + v + "</" + k + ">");
  410. }
  411. }
  412. sb.append("</xml>");
  413. // return sb.toString();
  414. return new String(sb.toString().toString().getBytes(), "utf-8");
  415. }
  416. }