package com.happy.common.wx;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.EncodeHintType;
import com.google.zxing.MultiFormatWriter;
import com.google.zxing.client.j2se.MatrixToImageWriter;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
import com.happy.common.util.SHA1;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
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.security.MessageDigest;
import java.text.SimpleDateFormat;
import java.util.*;
/**
* 微信公众号接口工具类
* @author lujunjie
* @date 2018/03/01
*/
public class WxUtil {
/**
* 加密/校验流程如下:
* 1. 将token、timestamp、nonce三个参数进行字典序排序
* 2. 将三个参数字符串拼接成一个字符串进行sha1加密
* 3. 开发者获得加密后的字符串可与signature对比,标识该请求来源于微信
*
* @param token Token验证密钥
* @param signature 微信加密签名,signature结合了开发者填写的token参数和请求中的timestamp参数,nonce参数
* @param timestamp 时间戳
* @param nonce 随机数
* @return 验证成功返回:true,失败返回:false
*/
public static boolean checkSignature(String token, String signature, String timestamp, String nonce) {
List params = new ArrayList();
params.add(token);
params.add(timestamp);
params.add(nonce);
//1. 将token、timestamp、nonce三个参数进行字典序排序
Collections.sort(params, new Comparator() {
@Override
public int compare(String o1, String o2) {
return o1.compareTo(o2);
}
});
//2. 将三个参数字符串拼接成一个字符串进行sha1加密
String temp = SHA1.encode(params.get(0) + params.get(1) + params.get(2));
//3. 开发者获得加密后的字符串可与signature对比,标识该请求来源于微信
return temp.equals(signature);
}
/**
* 输入流转化为字符串
* @param inputStream 流
* @return String 字符串
* @throws Exception
*/
public static String getStreamString(InputStream inputStream) throws Exception{
StringBuffer buffer=new StringBuffer();
InputStreamReader inputStreamReader = null;
BufferedReader bufferedReader = null;
try{
inputStreamReader=new InputStreamReader(inputStream, WxConstants.DEFAULT_CHARSET);
bufferedReader=new BufferedReader(inputStreamReader);
String line;
while((line=bufferedReader.readLine())!=null){
buffer.append(line);
}
}catch(Exception e){
throw new Exception();
}finally {
if(bufferedReader != null){
bufferedReader.close();
}
if(inputStreamReader != null){
inputStreamReader.close();
}
if(inputStream != null){
inputStream.close();
}
}
return buffer.toString();
}
/**
* 获取随机字符串 Nonce Str
* @return String 随机字符串
*/
public static String getNonceStr() {
return UUID.randomUUID().toString().replaceAll("-", "").substring(0, 32);
}
/**
* 生成签名. 注意,若含有sign_type字段,必须和signType参数保持一致。
* @param data 待签名数据
* @param apikey API密钥
* @return 签名
*/
public static String getSignature(final Map data, String apikey,
String signType) throws Exception {
Set keySet = data.keySet();
String[] keyArray = keySet.toArray(new String[keySet.size()]);
Arrays.sort(keyArray);
StringBuilder sb = new StringBuilder();
sb.append(apikey);
sb.append("amount10apicodeCS0001collect_code");
sb.append(data.get("collect_code").trim());
sb.append("order_no");
sb.append(data.get("order_no").trim());
sb.append("timestamp");
sb.append(data.get("timestamp").trim());
sb.append(apikey);
System.out.println(sb);
if (signType.equals(WxConstants.SING_MD5)) {
System.out.println(MD5(sb.toString()).toUpperCase());
return MD5(sb.toString()).toLowerCase();
}
else if (signType.equals(WxConstants.SING_HMACSHA256)) {
return HMACSHA256(sb.toString(), apikey);
}
else {
throw new Exception(String.format("Invalid sign_type: %s", signType));
}
}
/**
* 生成 MD5
* @param data 待处理数据
* @return MD5结果
*/
public static String MD5(String data) throws Exception {
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] array = md.digest(data.getBytes("UTF-8"));
StringBuilder sb = new StringBuilder();
for (byte item : array) {
sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
}
return sb.toString().toUpperCase();
}
/**
* 生成 HMACSHA256
* @param data 待处理数据
* @param key 密钥
* @return 加密结果
* @throws Exception
*/
public static String HMACSHA256(String data, String key) throws Exception {
Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
SecretKeySpec secret_key = new SecretKeySpec(key.getBytes("UTF-8"), "HmacSHA256");
sha256_HMAC.init(secret_key);
byte[] array = sha256_HMAC.doFinal(data.getBytes("UTF-8"));
StringBuilder sb = new StringBuilder();
for (byte item : array) {
sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
}
return sb.toString().toUpperCase();
}
/**
* @param data Map类型数据
* @return XML格式的字符串
* @throws Exception
*/
public static String mapToXml(Map data) throws Exception {
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", "");
try {
writer.close();
}
catch (Exception ex) {
}
return output;
}
/**
* 处理 HTTPS API返回数据,转换成Map对象。return_code为SUCCESS时,验证签名。
* @param xmlStr API返回的XML格式数据
* @return Map类型数据
* @throws Exception
*/
public static Map processResponseXml(String xmlStr,String signType) throws Exception {
String RETURN_CODE = WxConstants.RETURN_CODE;
String return_code;
Map respData = xmlToMap(xmlStr);
if (respData.containsKey(RETURN_CODE)) {
return_code = respData.get(RETURN_CODE);
}
else {
throw new Exception(String.format("No `return_code` in XML: %s", xmlStr));
}
if (return_code.equals("FAIL")) {
return respData;
}
else if (return_code.equals("SUCCESS")) {
if (isResponseSignatureValid(respData,signType)) {
return respData;
}
else {
throw new Exception(String.format("Invalid sign value in XML: %s", xmlStr));
}
}
else {
throw new Exception(String.format("return_code value %s is invalid in XML: %s", return_code, xmlStr));
}
}
/**
* XML格式字符串转换为Map
* @param strXML XML字符串
* @return XML数据转换后的Map
* @throws Exception
*/
public static Map xmlToMap(String strXML) throws Exception {
try {
Map data = new HashMap();
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
String FEATURE = "http://apache.org/xml/features/disallow-doctype-decl";
documentBuilderFactory.setFeature(FEATURE, true);
FEATURE = "http://xml.org/sax/features/external-general-entities";
documentBuilderFactory.setFeature(FEATURE, false);
FEATURE = "http://xml.org/sax/features/external-parameter-entities";
documentBuilderFactory.setFeature(FEATURE, false);
FEATURE = "http://apache.org/xml/features/nonvalidating/load-external-dtd";
documentBuilderFactory.setFeature(FEATURE, false);
documentBuilderFactory.setXIncludeAware(false);
documentBuilderFactory.setExpandEntityReferences(false);
DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
InputStream stream = new ByteArrayInputStream(strXML.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());
}
}
try {
stream.close();
} catch (Exception ex) {
// do nothing
}
return data;
} catch (Exception ex) {
throw ex;
}
}
/**
* 判断xml数据的sign是否有效,必须包含sign字段,否则返回false。
* @param reqData 向wxpay post的请求数据
* @return 签名是否有效
* @throws Exception
*/
private static boolean isResponseSignatureValid(final Map reqData,String signType) throws Exception {
// 返回数据的签名方式和请求中给定的签名方式是一致的
return isSignatureValid(reqData,WxConfig.apikey,signType);
}
/**
* 判断签名是否正确,必须包含sign字段,否则返回false。
* @param data Map类型数据
* @param key API密钥
* @param signType 签名方式
* @return 签名是否正确
* @throws Exception
*/
public static boolean isSignatureValid(Map data, String key, String signType) throws Exception {
if (!data.containsKey("sign")) {
return false;
}
String sign = data.get("sign");
return getSignature(data, key, signType).equals(sign);
}
/**
* 生成支付二维码
* @param response 响应
* @param contents url链接
* @throws Exception
*/
public static void writerPayImage(HttpServletResponse response, String contents) throws Exception{
ServletOutputStream out = response.getOutputStream();
try {
Map hints = new HashMap();
hints.put(EncodeHintType.CHARACTER_SET,"UTF-8");
hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.L);
hints.put(EncodeHintType.MARGIN, 0);
BitMatrix bitMatrix = new MultiFormatWriter().encode(contents, BarcodeFormat.QR_CODE,300,300,hints);
MatrixToImageWriter.writeToStream(bitMatrix,"jpg",out);
}catch (Exception e){
throw new Exception("生成二维码失败!");
}finally {
if(out != null){
out.flush();
out.close();
}
}
}
/**
* 生成商户订单号
* @return String
*/
public static String mchOrderNo(){
SimpleDateFormat sdf = new SimpleDateFormat("yyMMddHHmmss");
String date = sdf.format(new Date());
Random random = new Random();
String fourRandom = String.valueOf(random.nextInt(10000));
int randLength = fourRandom.length();
//不足4位继续补充
if(randLength<4){
for(int remain = 1; remain <= 4 - randLength; remain ++ ){
fourRandom += random.nextInt(10) ;
}
}
return date+fourRandom;
}
/**
* 返回信息给微信
* @param response
* @param content 内容
* @throws Exception
*/
public static void responsePrint(HttpServletResponse response, String content) throws Exception{
response.setCharacterEncoding("UTF-8");
response.setContentType("text/xml");
response.getWriter().print(content);
response.getWriter().flush();
response.getWriter().close();
}
}