小程序开发——微信支付篇

小程序开发中常常会用到微信支付。微信支付里面的坑谁做谁知道。

先来个官方贴图谈一下支付的流程

小程序开发——微信支付篇

对,支付是个异步的过程。

收集到用户的openid,支付金额,ip地址之后,我们生成随机串nonce_str和时间戳、订单号。当然商户号、商户秘钥、小程序的APPID、通知地址这些也是提前存好的。然后用这些信息按字典排序,它要求的是键值对,也就是key=value&  这样来拼接(具体的下面说)。拼接完成后尾处加上商户秘钥。最后用md5加密,提交到微信服务器生成预付单。拿到预付单之后我们传给小程序,小程序中用预付单的参数调起微信支付。然后支付结果通过我们前面传递的通知地址来通知我们(就是普通post请求传给你一个xml)。收到回调的xml后,我们去验证sign,如果验证通过则返回一个回馈给微信服务器(不然它还会持续发给你),然后我们执行支付成功的业务操作。

那我们现在从代码的角度来走一遍支付流程。

首先我们应该已知以下参数:appid(小程序appid)、mch_id(商户号)、key(商户秘钥)、trade_type(交易类型,JSAPI)、notify_url(支付回调地址,也就是通知我们的请求地址)

然后前端的提交一些信息,我们算出支付金额,获取支付用户的openid、支付用户客户端的IP地址、商品名称(其实可以固定写测试商品)。然后我们生成订单号,随机串和时间戳在后面生成。一个简单的controller如下:

/**
 * 请求支付接口,当然参数不可能这么简单,其他业务参数我们就省去了
 * @param openid   支付用户的openid
 * @param request  用来获取支付用户的IP地址
 *
 * @return  预付单的信息  ,你也可以用其他类来进行封装,这里我直接用Map传回给前端了
 */
@RequestMapping("pay")
public Map insert(String openid,HttpServletRequest request) {
   //我们假装计算出了支付金额,记住不能支付0元
   //如果你不想用BigDecimal ,用其他也行。但是记住,最后提交的时候是以分为单位的
   BigDecimal money = new BigDecimal(20);
   //生成一个订单号,生成策略自己决定
   String id = "123456";
   //准备一个回调地址,为了给pay方法解耦,我们每种不同支付采用不同回调地址
   String notifyUrl ="http://www.baidu.com/wxnotify";//要填能访问到的,别用本地localhost或者内网IP,尽量使用域名和公网IP
   Map map = null;
   //用异常包围起来,也许你想用事物来做
   try {
      map = pay(id,openid,money,request);
   }catch (Exception e){
      //处理一下异常
   }
   return map;
}

然后我们看一下这个pay方法。下面会调用StringUtil和HttpRequestor ,这两个是我自己准备的工具包,文末会贴上

/**
 *  发起支付请求
 * @param orderid  订单ID
 * @param openid   用户ID
 * @param money   支付金额
 * @param request   请求体
 * @param notifyUrl 支付结果回调通知地址
 * @return   预付单参数
 * @throws Exception
 */
public  Map pay(String orderid, String openid, BigDecimal money,String notifyUrl, HttpServletRequest request) throws Exception {
   //转化为分为单位
   money = money.multiply(new BigDecimal(100));
   //转换格式,最终还是转换成了long类型。。。。。但是绝对不要忘记是以分为单位
   Long price = money.longValue();
   //不能支付0元
   if(price==0L){
      return  null;
   }
   // 生成的随机字符串,最长32位,uuid去掉“-”正好
   String nonce_str = UUID.randomUUID().toString().replaceAll("-", "").toUpperCase();
   // 商品名称,这里我写死了商品名称,如果你不想这样就改成参数吧
   String productName = "测试商品";
   // 获取终端IP
   String ip = HttpRequestor.getIpFromRequest(request);
  //GlobalPerties 是一个参数类,里面的所有变量都是static final的,方便访问。我用来存不会变动的参数
   // 组装参数,用户生成统一下单接口的签名,这里用TreeMap 来就不需要自己排序了
   SortedMap<String, String> packageParams = new TreeMap<String, String>();
   packageParams.put("appid", GlobalProperties.APPID);// 小程序的aapid
   packageParams.put("mch_id", GlobalProperties.MCHID);// 商户号
   packageParams.put("nonce_str", nonce_str);// 随机字符串
   packageParams.put("body", productName);// 商品名称
   packageParams.put("out_trade_no", orderid);// 商户订单号
   packageParams.put("total_fee", price + "");// 标价金额
   packageParams.put("spbill_create_ip", ip);// 终端IP
   packageParams.put("notify_url",notifyUrl);// 通知地址
   packageParams.put("trade_type", "JSAPI");// 交易类型
   packageParams.put("openid", openid);// 微信用户的openid
   //参数map准备好之后我们进行拼接并加密。具体方法往后看
   // MD5运算生成签名,这里是第一次签名,用于调用统一下单接口
   String mysign = getSign(packageParams);
   // 拼接统一下单接口使用的xml数据,要将上一步生成的签名一起拼接进去
   String xml = "<xml>" + "<appid>" + GlobalProperties.APPID + "</appid>" + "<body><![CDATA[" + productName + "]]></body>"
         + "<mch_id>" + GlobalProperties.MCHID + "</mch_id>" + "<nonce_str>" + nonce_str + "</nonce_str>"
         + "<notify_url>" + notifyUrl + "</notify_url>" + "<openid>" + openid + "</openid>"
         + "<out_trade_no>" + orderid + "</out_trade_no>" + "<spbill_create_ip>" + ip + "</spbill_create_ip>"
         + "<total_fee>" + price + "</total_fee>" + "<trade_type>" + GlobalProperties.TRADETYPE + "</trade_type>"
         + "<sign>" + mysign + "</sign>" + "</xml>";
   //打印一下日志方便调试,用了@Slf4j
   log.info("调试模式_统一下单接口 请求XML数据:" + xml);
   
   // 调用统一下单接口,并接受预付订单参数。这里我执行了一个post请求,这个请求工具类我放在文末
   String result = HttpRequestor.doPost(GlobalProperties.PAYREQUEST_URL, xml);
   //打印日志
   log.info("调试模式_统一下单接口 返回XML数据:" + result);
   
   // 将解析结果存储在HashMap中,xml转map 我是用了dom4j
   
   Map<String, Object> map = StringUtil.doXMLParse(result);
   /**
    * 返回状态码
    */
   String return_code = (String) map.get("return_code");
   /**
    * 准备返回给前端的map集合
    */
   Map<String, Object> response = new HashMap<String, Object>(16);
   // 判断一下预付订单是否成功的
   if ("SUCCESS".equals(return_code)) {
      /**
       * 分析预付单信息
       */
      String prepay_id = (String) map.get("prepay_id");
      //前端需要的参数:nonceStr、package、timeStamp、paySign、appid,所以我们只需要关心这几个
      response.put("nonceStr", nonce_str);
      response.put("package", "prepay_id=" + prepay_id);
      Long timeStamp = System.currentTimeMillis() / 1000;
      /**
       * 这边要将返回的时间戳转化成字符串,不然小程序端调用wx.requestPayment方法会报签名错误
       */
      response.put("timeStamp", timeStamp + "");
      // 拼接签名需要的参数
      String stringSignTemp = "appId=" + GlobalProperties.APPID + "&nonceStr=" + nonce_str + "&package=prepay_id="
            + prepay_id + "&signType=MD5&timeStamp=" + timeStamp;
      // 尾处加上商户**再次签名,这个签名用于小程序端调用wx.requesetPayment方法 ,
      //import org.apache.commons.codec.digest.DigestUtils;
      String paySign = DigestUtils.md5Hex(stringSignTemp + "&key=" + GlobalProperties.MCHSECRET).toUpperCase();
      //把签名装进去
      response.put("paySign", paySign);
   }
   response.put("appid", GlobalProperties.APPID);
   //返回给前端去调用微信支付
   return response;
}
/**
 * 获取支付签名,这个getSign 方法在回调验证的时候还会用到,最好封装到工具包里
 * @param parameters  已经按字典排序的map
 * @return
 */
public String getSign(Map<String, String> parameters) {
   StringBuffer sb = new StringBuffer();
   Set<Entry<String, String>> es = parameters.entrySet();
   Iterator<Entry<String, String>> it = es.iterator();
   while (it.hasNext()) {
      Entry<String, String> entry = (Entry<String, String>) it.next();
      String k = entry.getKey();
      String v = entry.getValue();
      //移除key、sign等参数
      if (null != v && !"".equals(v) && !"sign".equals(k) && !"key".equals(k)) {
         sb.append(k + "=" + v + "&");
      }
   }
   /** 支付**必须参与加密,放在字符串最后面 */
   sb.append("key=" + GlobalProperties.MCHSECRET);
   String sign = DigestUtils.md5Hex(sb.toString()).toUpperCase();
   return sign;
}

 

前端拿到appid、timestamp、nonceStr、package、paySign、openid之后可以直接用wx.requestPayment方法了。前端的我就不贴了。

现在我们要处理一下回调方法。

我们肯定是要准备一个controller来让微信服务器访问的。

 

/**
 * 微信支付回调方法
 * 腾讯把支付结果反馈到服务器
 */
@RequestMapping("/api/wxNotify")   //这里要注意一下,和notifyUrl对应好
@Transactional
public void wxNotify(HttpServletRequest request, HttpServletResponse response) throws Exception {
   BufferedReader br = new BufferedReader(new InputStreamReader((ServletInputStream) request.getInputStream()));
   String line = null;
   StringBuilder sb = new StringBuilder();
   while ((line = br.readLine()) != null) {
      sb.append(line);
   }
   // sb为微信返回的xml
   String notityXml = sb.toString();
   String resXml = "";
   //打印日志方便调试
   log.info("接收到的报文:" + notityXml);
   //解析成Map
   Map map = StringUtil.doXMLParse(notityXml);
   
   String returnCode = (String) map.get("return_code");
   //看看是不是支付成功了,实际上支付失败是不会执行回调的。用异常包起来
   try {
      if ("SUCCESS".equals(returnCode)) {
         //验证签名start   
         //下面这段是验证回调是否由微信服务器发送的。防止被篡改。
         // 但是我这里有个判断,active 是application中配置的参数,如果是prod正式环境则需要验证。如果是测试环境或者本地环境   则不需要验证了。因为我自己写了个模拟回调,所以需要判断,如果你不需要模拟回调,就直接走验证
         if(active.equals("prod")){
            //线上环境必须验证其签名
            String sign = map.get("sign").toString();
            if(sign==null){
               //无签名,假的
               return;
            }else{
               //对比签名
               map.remove("sign");
               //直接使用他反给我们的xml中的参数然后去掉sign,然后字典排序,像之前那样加密获得签名,对比。
               map = new TreeMap<String,String>(map);
               // System.out.println(map);
               String mysign = WxpayUtil.getSign(map);
               log.info("签名对比:my_"+mysign+"  -   you_"+sign);
               if(!mysign.equals(sign)){
                  log.info("签名错误");
                  //签名错误
                  return;
               }
            }
         }
         //验证签名end
         /** 此处添加自己的业务逻辑代码start **/
         //提取出订单号 
         String id = map.get("out_trade_no").toString();
         // 支付时间
         String time_end = map.get("time_end").toString();
         Date payTime = fmt.parse(time_end);
         // 微信支付的流水号
         String transactionId = map.get("transaction_id").toString();
 
         // 此处添加自己的业务逻辑代码end
      } else {
         //不做处理
         log.warn(notityXml);
      }
   }catch (Exception e){
      // 本地执行异常
      log.error("error",e);
   }
   // 通知微信服务器已经收到消息,不然微信会继续发回调给你
   resXml = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>"
         + "<return_msg><![CDATA[]]></return_msg>" + "</xml> ";
   log.info("支付结果:"+resXml);
   BufferedOutputStream out = new BufferedOutputStream(response.getOutputStream());
   out.write(resXml.getBytes());
   out.flush();
   out.close();
}

支付流程到这里已经结束了。

下面是我为前端做的一个测试接口。模拟了微信服务器发送回调,让前端好控制支付结果。

只需要订单号就可以模拟,对前端来说很方便

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.alibaba.fastjson.JSONObject;
import com.dingguan.shanyu.bean.ResultBean;
import com.dingguan.shanyu.constants.GlobalProperties;
import com.dingguan.shanyu.util.HttpRequestor;
import com.dingguan.shanyu.util.StringUtil;
import com.dingguan.shanyu.util.WxpayUtil;
import com.dingguan.shanyu.util.XcxUtil;
import com.dingguan.shanyu.web.config.service.ConfigService;
import com.dingguan.shanyu.web.xcx.service.XcxService;

import io.swagger.annotations.*;

/**
 * @author 作者:李荣宗
 * @email 邮箱:[email protected]
 * @company 公司:深圳鼎冠网络科技有限公司
 * @project 项目:springboot
 * @createDate 创建时间:2018/12/10 10:52
 * @function 作用 :
 */

@RestController
@Api(description = "测试专用接口")
@RequestMapping("/test")
// @ApiIgnore
public class TestController {
   
   //从application.yaml或application.properties中获取当前的环境配置
   @Value("#{T(java.lang.String).valueOf('${spring.profiles.active}')}")
   public String active;//获取yaml配置中的当前环境
   DateFormat fmt = new SimpleDateFormat("yyyyMMddHHmmss");

  //Api 开头的注解是swagger的,如果没有swagger就删掉
   @GetMapping("/WxPayCallBack")
   @ApiOperation("模拟微信支付回调")
   @ApiResponses({})
   @ApiImplicitParams({@ApiImplicitParam(name = "indentID", value = "*支付订单号", paramType = "query", dataType = "string")})
   public ResultBean insert(String  indentID) {
      if(indentID==null){
         return new ResultBean(400);
      }
      if(active==null){
         return new ResultBean(404,"无法读取配置文件");
      }
      if(active.equals("prod")){
         //生产环境不能调用
         return new ResultBean(500,"生产环境不能调用");
      }
      //编辑xml
      String xml  = "<xml>";
      //注入支付结果
      xml+="<return_code><![CDATA[SUCCESS]]></return_code>";
      //注入订单号
      xml+="<out_trade_no><![CDATA["+indentID+"]]></out_trade_no>";
      //注入支付时间
      xml+="<time_end><![CDATA["+fmt.format(new Date())+"]]></time_end>";
      //注入流水号
      xml+="<transaction_id><![CDATA[ceshizhifu_"+indentID+"]]></transaction_id>";
      xml+="</xml>";
      try {
         String res = HttpRequestor.postXML(GlobalProperties.NOTIFY_URL,xml);
         // System.out.println(res);
         return new ResultBean(200,res,"模拟成功");
      } catch (Exception e) {
         // e.printStackTrace();
      }
      return new ResultBean(500,"模拟失败");
   }
}

然后下面是我的StringUtil和HttpRequestor 工具包。分享给大家

import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;

public class StringUtil {

   // 文本转码
   public static String turnCharSet(String str, String charsetSrc, String charserDist) {
      // 转码
      String string = null;
      try {
         string = new String(str.getBytes(charsetSrc), charserDist);
      } catch (UnsupportedEncodingException e) {
         e.printStackTrace();
      }
      return string;
   }

   // 获得全球唯一标识
   public static String getUUID() {
      return UUID.randomUUID().toString().replaceAll("-", "");
   }

   // 获得加密字符串
   public static String getMd5Hex(String src) {
      return org.apache.commons.codec.digest.DigestUtils.md5Hex(src);
   }

   /**
    * 进行sha256加密
    * @param str  要加密的字符串
    *
    * @return  加密的结果
    */
   public static String getSHA256(String str) {
      MessageDigest messageDigest;
      String encodeStr = "";
      try {
         messageDigest = MessageDigest.getInstance("SHA-256");
         messageDigest.update(str.getBytes("UTF-8"));
         StringBuffer stringBuffer = new StringBuffer();
         String temp = null;
         byte[] bytes = messageDigest.digest();
         for (int i = 0; i < bytes.length; i++) {
            temp = Integer.toHexString(bytes[i] & 0xFF);
            if (temp.length() == 1) {
               // 1得到一位的进行补0操作
               stringBuffer.append("0");
            }
            stringBuffer.append(temp);
         }
         encodeStr = stringBuffer.toString();
      } catch (NoSuchAlgorithmException e) {
         e.printStackTrace();
      } catch (UnsupportedEncodingException e) {
         e.printStackTrace();
      }
      return encodeStr;
   }

   // 获得十六进制码,至少10位
   public static String getShareCode(int length) {
      return ShareCodeUtils.toSerialCode(System.currentTimeMillis(), length);
   }

   // 获得指定长度不重复随机数字串至少14位
   public static String getLengthNumber(int length) {
      synchronized (StringUtil.class) {
         long time = System.currentTimeMillis();
         Random random = new Random();
         String res = time + "";
         for (int i = 13; i < length; i++) {
            res += "" + random.nextInt(10);
         }
         return res;
      }
   }

   // public static void main(String[] args) {
   //     System.out.println(getSHA256("new"));
   // }

   // 获得字符串长度
   public static int getStringLength(String string) {
      int length = string.length();
      return length;
   }

   // XML转Map
   public static Map<String, Object> doXMLParse(String xml) throws Exception {
      Map<String, Object> map = new HashMap<String, Object>();
      Document doc;
      try {
         doc = DocumentHelper.parseText(xml);
         Element el = doc.getRootElement();
         map = recGetXmlElementValue(el, map);
      } catch (DocumentException e) {
         e.printStackTrace();
      }
      return map;
   }

   // xml转Map的辅助方法
   @SuppressWarnings({ "unchecked" })
   private static Map<String, Object> recGetXmlElementValue(Element ele, Map<String, Object> map) {
      List<Element> eleList = ele.elements();
      if (eleList.size() == 0) {
         map.put(ele.getName(), ele.getTextTrim());
         return map;
      } else {
         for (Iterator<Element> iter = eleList.iterator(); iter.hasNext();) {
            Element innerEle = iter.next();
            recGetXmlElementValue(innerEle, map);
         }
         return map;
      }
   }
}
import java.io.*;
import java.net.*;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

/**
 *
 */
public class HttpRequestor {

	private static String charset = "utf-8";
	private Integer connectTimeout = null;
	private Integer socketTimeout = null;
	private static String proxyHost = null;
	private static Integer proxyPort = null;

	static String textFieldName = "textField";

	/**
	 * Do GET request
	 * 
	 * @param url
	 * @return
	 * @throws Exception
	 * @throws IOException
	 */
	public static String doGet(String url) throws Exception {

		URL localURL = new URL(url);

		URLConnection connection = openConnection(localURL);
		HttpURLConnection httpURLConnection = (HttpURLConnection) connection;

		httpURLConnection.setRequestProperty("Accept-Charset", charset);
		httpURLConnection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");

		InputStream inputStream = null;
		InputStreamReader inputStreamReader = null;
		BufferedReader reader = null;
		StringBuffer resultBuffer = new StringBuffer();
		String tempLine = null;

		if (httpURLConnection.getResponseCode() >= 300) {
			throw new Exception("HTTP Request is not success, Response code is " + httpURLConnection.getResponseCode());
		}

		try {
			inputStream = httpURLConnection.getInputStream();
			inputStreamReader = new InputStreamReader(inputStream);
			reader = new BufferedReader(inputStreamReader);

			while ((tempLine = reader.readLine()) != null) {
				resultBuffer.append(tempLine);
			}

		} finally {

			if (reader != null) {
				reader.close();
			}

			if (inputStreamReader != null) {
				inputStreamReader.close();
			}

			if (inputStream != null) {
				inputStream.close();
			}

		}

		return resultBuffer.toString();
	}

	public static String doGet(String url, Map parameterMap) throws Exception {

		/* Translate parameter map to parameter date string */
		StringBuffer parameterBuffer = new StringBuffer();
		if (parameterMap != null) {
			parameterBuffer.append("?");
			Iterator iterator = parameterMap.keySet().iterator();
			String key = null;
			String value = null;
			while (iterator.hasNext()) {
				key = (String) iterator.next();
				if (parameterMap.get(key) != null) {
					value = (String) parameterMap.get(key);
				} else {
					value = "";
				}

				parameterBuffer.append(key).append("=").append(value);
				if (iterator.hasNext()) {
					parameterBuffer.append("&");
				}
			}
		}

		URL localURL = new URL(url + parameterBuffer.toString());

		URLConnection connection = openConnection(localURL);
		HttpURLConnection httpURLConnection = (HttpURLConnection) connection;

		httpURLConnection.setRequestProperty("Accept-Charset", charset);
		httpURLConnection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded;charset=utf-8");

		InputStream inputStream = null;
		InputStreamReader inputStreamReader = null;
		BufferedReader reader = null;
		StringBuffer resultBuffer = new StringBuffer();
		String tempLine = null;

		if (httpURLConnection.getResponseCode() >= 300) {
			throw new Exception("HTTP Request is not success, Response code is " + httpURLConnection.getResponseCode());
		}

		try {
			inputStream = httpURLConnection.getInputStream();
			inputStreamReader = new InputStreamReader(inputStream);
			reader = new BufferedReader(inputStreamReader);

			while ((tempLine = reader.readLine()) != null) {
				resultBuffer.append(tempLine);
			}

		} finally {

			if (reader != null) {
				reader.close();
			}

			if (inputStreamReader != null) {
				inputStreamReader.close();
			}

			if (inputStream != null) {
				inputStream.close();
			}

		}

		return resultBuffer.toString();
	}

	public static String doPost(String url, String parameter) throws Exception {

		URL localURL = new URL(url);
		// System.out.println(localURL);
		URLConnection connection = openConnection(localURL);
		HttpURLConnection httpURLConnection = (HttpURLConnection) connection;

		httpURLConnection.setDoOutput(true);
		httpURLConnection.setRequestMethod("POST");
		httpURLConnection.setRequestProperty("Accept-Charset", charset);
		httpURLConnection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
		httpURLConnection.setRequestProperty("Content-Length", String.valueOf(parameter.length()));

		OutputStream outputStream = null;
		OutputStreamWriter outputStreamWriter = null;
		InputStream inputStream = null;
		InputStreamReader inputStreamReader = null;
		BufferedReader reader = null;
		StringBuffer resultBuffer = new StringBuffer();
		String tempLine = null;

		try {
			outputStream = httpURLConnection.getOutputStream();
			outputStreamWriter = new OutputStreamWriter(outputStream);

			outputStreamWriter.write(parameter);
			outputStreamWriter.flush();

			if (httpURLConnection.getResponseCode() >= 300) {
				throw new Exception(
						"HTTP Request is not success, Response code is " + httpURLConnection.getResponseCode());
			}

			inputStream = httpURLConnection.getInputStream();
			inputStreamReader = new InputStreamReader(inputStream);
			reader = new BufferedReader(inputStreamReader);

			while ((tempLine = reader.readLine()) != null) {
				resultBuffer.append(tempLine);
			}

		} finally {

			if (outputStreamWriter != null) {
				outputStreamWriter.close();
			}

			if (outputStream != null) {
				outputStream.close();
			}

			if (reader != null) {
				reader.close();
			}

			if (inputStreamReader != null) {
				inputStreamReader.close();
			}

			if (inputStream != null) {
				inputStream.close();
			}

		}

		return resultBuffer.toString();
	}

	/**
	 * Do POST request
	 * 
	 * @param url
	 * @param parameterMap
	 * @return
	 * @throws Exception
	 */
	public static String doPost(String url, Map parameterMap) throws Exception {

		/* Translate parameter map to parameter date string */
		StringBuffer parameterBuffer = new StringBuffer();
		if (parameterMap != null) {
			Iterator iterator = parameterMap.keySet().iterator();
			String key = null;
			String value = null;
			while (iterator.hasNext()) {
				key = (String) iterator.next();
				if (parameterMap.get(key) != null) {
					value = (String) parameterMap.get(key);
				} else {
					value = "";
				}

				parameterBuffer.append(key).append("=").append(value);
				if (iterator.hasNext()) {
					parameterBuffer.append("&");
				}
			}
		}

		// System.out.println("POST parameter : " + parameterBuffer.toString());

		URL localURL = new URL(url);

		URLConnection connection = openConnection(localURL);
		HttpURLConnection httpURLConnection = (HttpURLConnection) connection;

		httpURLConnection.setDoOutput(true);
		httpURLConnection.setRequestMethod("POST");
		httpURLConnection.setRequestProperty("Accept-Charset", charset);
		httpURLConnection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
		httpURLConnection.setRequestProperty("Content-Length", String.valueOf(parameterBuffer.length()));

		OutputStream outputStream = null;
		OutputStreamWriter outputStreamWriter = null;
		InputStream inputStream = null;
		InputStreamReader inputStreamReader = null;
		BufferedReader reader = null;
		StringBuffer resultBuffer = new StringBuffer();
		String tempLine = null;

		try {
			outputStream = httpURLConnection.getOutputStream();
			outputStreamWriter = new OutputStreamWriter(outputStream);

			outputStreamWriter.write(parameterBuffer.toString());
			outputStreamWriter.flush();

			if (httpURLConnection.getResponseCode() >= 300) {
				throw new Exception(
						"HTTP Request is not success, Response code is " + httpURLConnection.getResponseCode());
			}

			inputStream = httpURLConnection.getInputStream();
			inputStreamReader = new InputStreamReader(inputStream);
			reader = new BufferedReader(inputStreamReader);

			while ((tempLine = reader.readLine()) != null) {
				resultBuffer.append(tempLine);
			}

		} finally {

			if (outputStreamWriter != null) {
				outputStreamWriter.close();
			}

			if (outputStream != null) {
				outputStream.close();
			}

			if (reader != null) {
				reader.close();
			}

			if (inputStreamReader != null) {
				inputStreamReader.close();
			}

			if (inputStream != null) {
				inputStream.close();
			}

		}

		return resultBuffer.toString();
	}

	private static URLConnection openConnection(URL localURL) throws IOException {
		URLConnection connection;
		if (proxyHost != null && proxyPort != null) {
			Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyHost, proxyPort));
			connection = localURL.openConnection(proxy);
		} else {
			connection = localURL.openConnection();
		}
		return connection;
	}

	/**
	 * Render request according setting
	 * 
	 * @param connection
	 */
	private void renderRequest(URLConnection connection) {

		if (connectTimeout != null) {
			connection.setConnectTimeout(connectTimeout);
		}

		if (socketTimeout != null) {
			connection.setReadTimeout(socketTimeout);
		}

	}

	/*
	 * Getter & Setter
	 */
	public Integer getConnectTimeout() {
		return connectTimeout;
	}

	public void setConnectTimeout(Integer connectTimeout) {
		this.connectTimeout = connectTimeout;
	}

	public Integer getSocketTimeout() {
		return socketTimeout;
	}

	public void setSocketTimeout(Integer socketTimeout) {
		this.socketTimeout = socketTimeout;
	}

	public String getProxyHost() {
		return proxyHost;
	}

	public void setProxyHost(String proxyHost) {
		this.proxyHost = proxyHost;
	}

	public Integer getProxyPort() {
		return proxyPort;
	}

	public void setProxyPort(Integer proxyPort) {
		this.proxyPort = proxyPort;
	}

	public String getCharset() {
		return charset;
	}

	public void setCharset(String charset) {
		this.charset = charset;
	}

	public static String getIpFromRequest(HttpServletRequest request) {
		String ip = request.getHeader("x-forwarded-for");
		if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
			ip = request.getHeader("Proxy-Client-IP");
		}
		if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
			ip = request.getHeader("WL-Proxy-Client-IP");
		}
		if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
			ip = request.getRemoteAddr();
		}
		return ip.equals("0:0:0:0:0:0:0:1") ? "127.0.0.1" : ip;
	}


	/**
	 *
	 * 获取B类小程序码
	 * @param token   小程序的token
	 * @param scene   要带的参数
	 * @param page     小程序的页面
	 * @param projectName  工程名
	 *
	 * @return
	 */
	public static String getWxQrcode(String token, String scene,String page,String projectName){
		Map map = new HashMap();
		map.put("token",token);
		map.put("scene",scene);
		map.put("page",page);
		map.put("projectName",projectName);
		try {
			return  doGet("https://www.dingguan.com/dingguan/genQRCode/wxQrcode");
		}catch (Exception e){
			return "";
		}
	}


	/**
	 * 使用post 方式发生json数据
	 * @param strUrl  请求路径
	 * @param json    参数,json字符串
	 *
	 * @return   请求的返回结果
	 */
	public static String postJson(String strUrl,String json){
		BufferedReader reader = null;
		try {
			URL url = new URL(strUrl);
			HttpURLConnection connection = (HttpURLConnection) url.openConnection();
			connection.setDoOutput(true);
			connection.setDoInput(true);
			connection.setUseCaches(false);
			connection.setInstanceFollowRedirects(true);
			connection.setRequestMethod("POST");
			connection.setRequestProperty("Content-Type","application/json");
			connection.connect();
			OutputStreamWriter out = new OutputStreamWriter(connection.getOutputStream(),"UTF-8");
			out.append(json);
			out.flush();
			out.close();
			reader = new BufferedReader(new InputStreamReader(connection.getInputStream(),"UTF-8"));
			String line = "";
			String res = "";
			while((line=reader.readLine())!=null){
				res+=line;
			}
			reader.close();
			return res;
		}catch (Exception e){
			e.printStackTrace();
		}
		return null;
	}

	/**
	 * 使用post 方式发送xml数据
	 * @param strUrl  请求路径
	 * @param xml    参数
	 *
	 * @return   请求的返回结果
	 */
	public static String postXML(String strUrl,String xml){
		BufferedReader reader = null;
		try {
			URL url = new URL(strUrl);
			HttpURLConnection connection = (HttpURLConnection) url.openConnection();
			connection.setDoOutput(true);
			connection.setDoInput(true);
			connection.setUseCaches(false);
			connection.setInstanceFollowRedirects(true);
			connection.setRequestMethod("POST");
			connection.setRequestProperty("Content-Type","text/xml");
			// connection.connect();
			connection.setRequestProperty("Content-length",String.valueOf(xml.getBytes().length));
			OutputStreamWriter out = new OutputStreamWriter(connection.getOutputStream(),"UTF-8");
			out.append(new String(xml.getBytes("ISO-8859-1")));
			out.flush();
			out.close();
			reader = new BufferedReader(new InputStreamReader(connection.getInputStream(),"UTF-8"));
			String line = "";
			String res = "";
			while((line=reader.readLine())!=null){
				res+=line;
			}
			reader.close();
			return res;
		}catch (Exception e){
			e.printStackTrace();
		}
		return null;
	}


}