WxUtil.java 13 KB

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