2021-10-30

springboot整合wechat支付

1、需要的jar包

    <dependency>
        <groupId>com.github.wxpay</groupId>
        <artifactId>wxpay-sdk</artifactId>
        <version>0.0.3</version>
    </dependency>

2、封装的wetchat工具类

import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import java.io.InputStream;
import java.io.Serializable;

@Component
@Configuration
@Data
public class WeChatPayConfig implements Serializable {

    @Value("${wechat.appID}")
    private String account;

    @Value("${wechat.secretKey}")
    private String secretKey;

    @Value("${wechat.mchID}")
    private String merchant;

    @Value("${wechat.payNotifyUrl}")
    private String callBackAddr;

    @Value("${wechat.certPath}")
    private String certificateAddr;

    // 读取resource下的文件为inputStream
    public InputStream getCertInputStream(){ return getClass().getClassLoader().getResourceAsStream(certificateAddr); }
}
import org.springframework.stereotype.Component;

@Component
public class WechatOperation {

    //微信  统一下单
    public static final String UNIFIED_ORDER_URL = "https://api.mch.weixin.qq.com/pay/unifiedorder";

    //微信  查询订单
    public static final String SEL_ORDER_URL = "https://api.mch.weixin.qq.com/pay/orderquery";

    //微信  关闭订单
    public static final String CLOSE_ORDER_URL = "https://api.mch.weixin.qq.com/pay/closeorder";

    //微信  申请退款
    public static final String APPLICATION_DRAWBACK_URL = "https://api.mch.weixin.qq.com/secapi/pay/refund";

    //微信  查询退款
    public static final String SEL_DRAWBACK_URL = "https://api.mch.weixin.qq.com/pay/refundquery";

    //微信  下载交易账单
    public static final String DOWNLOAD_BILL_URL = "https://api.mch.weixin.qq.com/pay/downloadbill";

    //微信  下载资金账单
    public static final String DOWNLOAD_FUND_URL = "https://api.mch.weixin.qq.com/pay/downloadfundflow";


    public class TradeType{

        //交易类型
        public static final String JSAPI_TYPE = "JSAPI";
    }
}
import com.github.wxpay.sdk.WXPayConstants;
import com.github.wxpay.sdk.WXPayConstants.SignType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.parsers.DocumentBuilder;
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.ByteArrayInputStream;
import java.io.InputStream;
import java.io.StringWriter;
import java.security.MessageDigest;
import java.security.SecureRandom;
import java.util.*;


public class WXPayUtil {

    private static final String SYMBOLS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";

    private static final Random RANDOM = new SecureRandom();

    /**
     * XML格式字符串转换为Map
     *
     * @param strXML XML字符串
     * @return XML数据转换后的Map
     * @throws Exception
     */
    public static Map<String, String> xmlToMap(String strXML) throws Exception {
        try {
            Map<String, String> data = new HashMap<String, String>();
            DocumentBuilder documentBuilder = WXPayXmlUtil.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) {
            // com.github.wxpay.sdk.WXPayUtil.getLogger().warn("Invalid XML, can not convert to map. Error message: {}. XML content: {}", ex.getMessage(), strXML);
            throw ex;
        }

    }

    /**
     * 将Map转换为XML格式的字符串
     *
     * @param data Map类型数据
     * @return XML格式的字符串
     * @throws Exception
     */
    public static String mapToXml(Map<String, String> data) throws Exception {
        org.w3c.dom.Document document = WXPayXmlUtil.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();
        try {
            writer.close();
        }
        catch (Exception ex) {
        }
        return output;
    }


    /**
     * 生成带有 sign 的 XML 格式字符串
     *
     * @param data Map类型数据
     * @param key API密钥
     * @return 含有sign字段的XML
     */
    public static String generateSignedXml(final Map<String, String> data, String key) throws Exception {
        return generateSignedXml(data, key, SignType.MD5);
    }

    /**
     * 生成带有 sign 的 XML 格式字符串
     *
     * @param data Map类型数据
     * @param key API密钥
     * @param signType 签名类型
     * @return 含有sign字段的XML
     */
    public static String generateSignedXml(final Map<String, String> data, String key, SignType signType) throws Exception {
        String sign = generateSignature(data, key, signType);
        data.put(WXPayConstants.FIELD_SIGN, sign);
        return mapToXml(data);
    }


    /**
     * 判断签名是否正确
     *
     * @param xmlStr XML格式数据
     * @param key API密钥
     * @return 签名是否正确
     * @throws Exception
     */
    public static boolean isSignatureValid(String xmlStr, String key) throws Exception {
        Map<String, String> data = xmlToMap(xmlStr);
        if (!data.containsKey(WXPayConstants.FIELD_SIGN) ) {
            return false;
        }
        String sign = data.get(WXPayConstants.FIELD_SIGN);
        return generateSignature(data, key).equals(sign);
    }

    /**
     * 判断签名是否正确,必须包含sign字段,否则返回false。使用MD5签名。
     *
     * @param data Map类型数据
     * @param key API密钥
     * @return 签名是否正确
     * @throws Exception
     */
    public static boolean isSignatureValid(Map<String, String> data, String key) throws Exception {
        return isSignatureValid(data, key, SignType.MD5);
    }

    /**
     * 判断签名是否正确,必须包含sign字段,否则返回false。
     *
     * @param data Map类型数据
     * @param key API密钥
     * @param signType 签名方式
     * @return 签名是否正确
     * @throws Exception
     */
    public static boolean isSignatureValid(Map<String, String> data, String key, SignType signType) throws Exception {
        if (!data.containsKey(WXPayConstants.FIELD_SIGN) ) {
            return false;
        }
        String sign = data.get(WXPayConstants.FIELD_SIGN);
        return generateSignature(data, key, signType).equals(sign);
    }

    /**
     * 生成签名
     *
     * @param data 待签名数据
     * @param key API密钥
     * @return 签名
     */
    public static String generateSignature(final Map<String, String> data, String key) throws Exception {
        return generateSignature(data, key, SignType.MD5);
    }

    /**
     * 生成签名. 注意,若含有sign_type字段,必须和signType参数保持一致。
     *
     * @param data 待签名数据
     * @param key API密钥
     * @param signType 签名方式
     * @return 签名
     */
    public static String generateSignature(final Map<String, String> data, String key, SignType signType) throws Exception {
        Set<String> keySet = data.keySet();
        String[] keyArray = keySet.toArray(new String[keySet.size()]);
        Arrays.sort(keyArray);
        StringBuilder sb = new StringBuilder();
        for (String k : keyArray) {
            if (k.equals(WXPayConstants.FIELD_SIGN)) {
                continue;
            }
            if (data.get(k).trim().length() > 0) // 参数值为空,则不参与签名
                sb.append(k).append("=").append(data.get(k).trim()).append("&");
        }
        sb.append("key=").append(key);
        if (SignType.MD5.equals(signType)) {
            return MD5(sb.toString()).toUpperCase();
        }
        else if (SignType.HMACSHA256.equals(signType)) {
            return HMACSHA256(sb.toString(), key);
        }
        else {
            throw new Exception(String.format("Invalid sign_type: %s", signType));
        }
    }


    /**
     * 获取随机字符串 Nonce Str
     *
     * @return String 随机字符串
     */
    public static String generateNonceStr() {
        char[] nonceChars = new char[32];
        for (int index = 0; index < nonceChars.length; ++index) {
            nonceChars[index] = SYMBOLS.charAt(RANDOM.nextInt(SYMBOLS.length()));
        }
        return new String(nonceChars);
    }


    /**
     * 生成 MD5
     *
     * @param data 待处理数据
     * @return MD5结果
     */
    public static String MD5(String data) throws Exception {
        java.security.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();
    }

    /**
     * 日志
     * @return
     */
    public static Logger getLogger() {
        Logger logger = LoggerFactory.getLogger("wxpay java sdk");
        return logger;
    }

    /**
     * 获取当前时间戳,单位秒
     * @return
     */
    public static long getCurrentTimestamp() {
        return System.currentTimeMillis()/1000;
    }

    /**
     * 获取当前时间戳,单位毫秒
     * @return
     */
    public static long getCurrentTimestampMs() {
        return System.currentTimeMillis();
    }
}
import org.w3c.dom.Document;

import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

/**
 * 2018/7/3
 */
public final class WXPayXmlUtil {
    public static DocumentBuilder newDocumentBuilder() throws ParserConfigurationException {
        DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
        documentBuilderFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
        documentBuilderFactory.setFeature("http://xml.org/sax/features/external-general-entities", false);
        documentBuilderFactory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
        documentBuilderFactory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
        documentBuilderFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
        documentBuilderFactory.setXIncludeAware(false);
        documentBuilderFactory.setExpandEntityReferences(false);

        return documentBuilderFactory.newDocumentBuilder();
    }

    public static Document newDocument() throws ParserConfigurationException {
        return newDocumentBuilder().newDocument();
    }
}

3、需要的yml文件

wechat:
  #微信appid
  appID: 
  #商户号
  mchID: 
  secretKey: 
  certPath: static/cert/wxpay/apiclient_cert.p12 # 从微信商户平台下载的安全证书存放的路径、我放在resources下面,切记一定要看看target目录下的class文件下有没有打包apiclient_cert.p12文件
  payNotifyUrl:   # 微信支付成功的异步通知接口 这里引入你的回调接口。

4、封装的方法

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;

public interface WechatPayService {

    /**
     *
     * JSAPI
     *
     * @param shopDesc 商品信息描述
     * @param orderNo  订单编号
     * @param totalPrice  总金额 单位 分
     * @param userOpenid 用户微信唯一 openid
     */
    void unifiedOrder(String shopDesc, String shopDetail, String orderNo, int totalPrice, String userOpenid, HttpServletRequest request) throws Exception;

    /**
     * 根据订单号 查询订单
     * @param transactionId
     * @return
     * @throws Exception
     */
    Map<String,String> orderQuery(String transactionId) throws Exception;

    /**
     * 关闭微信订单
     * @param transaction
     */
    void closeWechatOrder(String transaction) throws Exception;

    /**
     * 微信支付 退款操作接口
     * @param transaction
     * @param totalPrice
     * @param refundPrice
     */
    void callBackDraw(String transaction,Integer totalPrice,Integer refundPrice,HttpServletResponse response) throws Exception;

    /**
     * 查询退款 偏移量查询
     * @param transaction
     * @param offset
     */
    void refundDrawInfo(String transaction, Integer offset) throws Exception;

    /**
     * 下载资金账单
     * @param billData
     * @param tar
     * @param tarVal
     */
    void downloadBill(String billData,Boolean tar,String tarVal,String billType) throws Exception;

    /**
     * 下载资金变动单
     * @param billDate
     * @param accountType
     * @param tar
     * @param tarType
     */
    void downloadFundFlow(String billDate,String accountType,Boolean tar,String tarType) throws Exception;

    /**
     * 微信支付回调信息
     * @param request
     * @param response
     */
    Map notifyCallback(HttpServletRequest request, HttpServletResponse response);
}
import com.github.wxpay.sdk.WXPayConstants;
import common.projects.develop.config.WeChatPayConfig;
import common.projects.develop.config.WechatOperation;
import common.projects.develop.service.WechatPayService;
import common.projects.develop.utils.AcquireIpUtils;
import common.projects.develop.utils.HttpClient;
import common.projects.develop.utils.WXPayUtil;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.DefaultHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.BasicHttpClientConnectionManager;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;

import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.security.KeyStore;
import java.security.SecureRandom;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

@Service
public class WechatPayServiceImpl implements WechatPayService {


    @Autowired
    private WeChatPayConfig weChatPayConfig;

    @Autowired
    private RequestParams requestParams;

    @Autowired
    RequestUtils requestUtils = new RequestUtils(weChatPayConfig);

    @Override
    public void unifiedOrder(String shopDesc, String shopDetail, String orderNo, int totalPrice, String userOpenid, HttpServletRequest request) throws Exception {
        String payXml = requestParams.getPayXml(shopDesc, shopDetail, orderNo, totalPrice, userOpenid, request);
        HttpClient.Response<String> stringResponse = requestParams.postXml(payXml, WechatOperation.UNIFIED_ORDER_URL);
        Map<String, String> map = WXPayUtil.xmlToMap(stringResponse.getBody());
    }

    @Override
    public Map<String, String> orderQuery(String transactionId) throws Exception {
        System.out.println("查询订单");
        String selOrder = requestParams.getSelOrder(transactionId);
        HttpClient.Response<String> stringResponse = requestParams.postXml(selOrder,WechatOperation.SEL_ORDER_URL);
        System.out.println(stringResponse);
        return null;
    }

    @Override
    public void closeWechatOrder(String transaction) throws Exception {
        System.out.println("关闭订单");
        HttpClient.Response<String> stringResponse = requestParams.postXml(requestParams.getCloseOrder(transaction),WechatOperation.CLOSE_ORDER_URL);
        System.out.println(stringResponse);
    }

    @Override
    public void callBackDraw(String transaction, Integer totalPrice, Integer refundPrice,HttpServletResponse response) throws Exception {
        System.out.println("退款操作");
        String xmlStr = requestUtils.requestOnce(WechatOperation.APPLICATION_DRAWBACK_URL, requestParams.drawbackData(transaction, totalPrice, refundPrice), 3000, 3000, true);
        System.out.println("返回结果集: "+xmlStr);
        Map resMap = new HashMap();
        String resXml = "";
        if (xmlStr.indexOf("SUCCESS") != -1) {
            Map<String, String> map = WXPayUtil.xmlToMap(xmlStr);//XML格式字符串转换为Map
            if (map.get("return_code").equals("SUCCESS")) {
                resMap.put("success", true);//此步说明退款成功
                resMap.put("data", "退款成功");
                System.out.println("退款成功");
                String complete = "已取消";
                try {
                    //告诉微信服务器收到信息了,不要在调用回调action了========这里很重要回复微信服务器信息用流发送一个xml即可
                    resXml = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>"
                            + "<return_msg><![CDATA[OK]]></return_msg>" + "</xml> ";
                    BufferedOutputStream out = new BufferedOutputStream(
                            response.getOutputStream());
                    out.write(resXml.getBytes());
                    out.flush();
                    out.close();
                    System.err.println("返回给微信的值:" + resXml.getBytes());
                } catch (Exception e) {
                    resMap.put("fail", "订单状态修改失败");
                }
            }
        }

    }

    @Override
    public void refundDrawInfo(String transaction, Integer offset) throws Exception {
        System.out.println("查询订单退款操作");
        HttpClient.Response<String> stringResponse = requestParams.postXml(requestParams.refundData(transaction, offset),WechatOperation.SEL_DRAWBACK_URL);
        System.out.println(stringResponse);
    }

    @Override
    public void downloadBill(String billData, Boolean tar, String tarVal,String billType) throws Exception {
        System.out.println("下载交易账单操作");
        System.out.println(requestParams.getDownloadBillData(billData,tar,tarVal,billType));
        HttpClient.Response<String> gzip = requestParams.postXml(requestParams.getDownloadBillData(billData,tar,tarVal,billType), WechatOperation.DOWNLOAD_BILL_URL);
        System.out.println(gzip);
    }

    @Override
    public void downloadFundFlow(String billDate, String accountType, Boolean tar, String tarType) throws Exception {
        System.out.println("下载自定账单");
        System.out.println(requestParams.getFundFlow(billDate, accountType, tar, tarType));
        String s = requestUtils.requestOnce(WechatOperation.DOWNLOAD_FUND_URL, requestParams.getFundFlow(billDate, accountType, tar, tarType),3000,3000,true);
        System.out.println(s);
    }

    /**
     * 微信支付回调 告诉微信已经支付成功
     * @param request
     * @param response
     * @return
     */
    @Override
    public Map notifyCallback(HttpServletRequest request, HttpServletResponse response) {
        Map result = new HashMap();
        InputStream is = null;
        String resXml = "";
        try {
            is = request.getInputStream();//获取请求的流信息(这里是微信发的xml格式所有只能使用流来读)
            String xml = requestUtils.convertToString(is);
            Map<String, String> notifyMap = WXPayUtil.xmlToMap(xml);//将微信发的xml转map
            if(notifyMap.get("return_code").equals("SUCCESS")){
                String ordersNum = notifyMap.get("out_trade_no").toString();//商户订单号
                try {
                    result.put("1","支付回调成功,修改订单状态为支付成功");
                    //告诉微信服务器收到信息了,不要在调用回调action了========这里很重要回复微信服务器信息用流发送一个xml即可
                    resXml = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>"
                            + "<return_msg><![CDATA[OK]]></return_msg>" + "</xml> ";
                    BufferedOutputStream out = new BufferedOutputStream(
                            response.getOutputStream());
                    out.write(resXml.getBytes());
                    out.flush();
                    out.close();
                    System.err.println("返回给微信的值:"+resXml.getBytes());
                    is.close();
                }catch (Exception e){
                    result.put("2","订单状态修改失败");
                }
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }


    @Component
    public class RequestParams{

        /**
         * xml封装 网络请求
         * @param wxPayXml
         * @param path
         * @return
         */
        public HttpClient.Response<String> postXml(String wxPayXml,String path){
            HttpClient.Request request = HttpClient.buildHttpClient().buildRequest(path);
            request.setMethod(HttpClient.Method.POST);
            request.setParam(HttpClient.Params.ofXml(wxPayXml,Charset.defaultCharset()));
            return request.execute(HttpClient.BodyHandlers.ofString(Charset.defaultCharset()));
        }

        /**
         * jsapi支付请求需要数据封装
         * @param shopDesc
         * @param shopDetail
         * @param orderNo
         * @param totalPrice
         * @param userOpenid
         * @param request
         * @return
         * @throws Exception
         */
        public String getPayXml(String shopDesc, String shopDetail,String orderNo, int totalPrice, String userOpenid, HttpServletRequest request) throws Exception {
            Map<String, String> unifiedOrderMap = new HashMap();
            unifiedOrderMap.put("appid",weChatPayConfig.getAccount());
            unifiedOrderMap.put("mch_id",weChatPayConfig.getMerchant());
            unifiedOrderMap.put("nonce_str",WXPayUtil.generateNonceStr());
            unifiedOrderMap.put("body",shopDetail);
            unifiedOrderMap.put("out_trade_no",orderNo);
            unifiedOrderMap.put("total_fee",String.valueOf(totalPrice));
            unifiedOrderMap.put("spbill_create_ip", AcquireIpUtils.getIpaddress(request));
            unifiedOrderMap.put("notify_url",weChatPayConfig.getCallBackAddr());
            unifiedOrderMap.put("trade_type", WechatOperation.TradeType.JSAPI_TYPE);
            unifiedOrderMap.put("sub_openid",userOpenid);
            unifiedOrderMap.put("device_info","WEB");
            unifiedOrderMap.put("sign_type","MD5");
            unifiedOrderMap.put("attach",shopDesc);
            unifiedOrderMap.put("sign",WXPayUtil.generateSignature(unifiedOrderMap,weChatPayConfig.getSecretKey()));
            return WXPayUtil.mapToXml(unifiedOrderMap);
        }

        /**
         * 查询订单封装 请求数据
         * @param transactionId
         * @return
         * @throws Exception
         */
        public String getSelOrder(String transactionId) throws Exception {
            Map<String,String> orderData = new HashMap<>();
            orderData.put("appid",weChatPayConfig.getAccount());
            orderData.put("mch_id",weChatPayConfig.getMerchant());
            orderData.put("out_trade_no",transactionId);
            orderData.put("nonce_str",WXPayUtil.generateNonceStr());
            orderData.put("sign",WXPayUtil.generateSignature(orderData,weChatPayConfig.getSecretKey()));
            return WXPayUtil.mapToXml(orderData);
        }

        /**
         * 关闭订单借口操作
         * @param transaction
         * @return
         * @throws Exception
         */
        public String getCloseOrder(String transaction) throws Exception {
            Map<String,String> closeOrder = new HashMap<>();
            closeOrder.put("appid",weChatPayConfig.getAccount());
            closeOrder.put("mch_id",weChatPayConfig.getMerchant());
            closeOrder.put("out_trade_no",transaction);
            closeOrder.put("nonce_str",WXPayUtil.generateNonceStr());
            closeOrder.put("sign",WXPayUtil.generateSignature(closeOrder,weChatPayConfig.getSecretKey()));
            return WXPayUtil.mapToXml(closeOrder);
        }

        /**
         * 微信订单退款操作需要参数
         * @param transaction
         * @param totalPrice
         * @param refundPrice
         * @return
         * @throws Exception
         */
        public String drawbackData(String transaction,Integer totalPrice,Integer refundPrice) throws Exception {
            Map<String,String> drawback = new HashMap<String,String>();
            drawback.put("appid",weChatPayConfig.getAccount());
            drawback.put("mch_id",weChatPayConfig.getMerchant());
            drawback.put("nonce_str",WXPayUtil.generateNonceStr());
            drawback.put("sign_type","MD5");
            drawback.put("out_trade_no",transaction);
            drawback.put("out_refund_no",transaction);
            drawback.put("total_fee",String.valueOf(totalPrice));
            drawback.put("refund_fee",String.valueOf(refundPrice));
            drawback.put("sign",WXPayUtil.generateSignature(drawback,weChatPayConfig.getSecretKey()));
            return WXPayUtil.mapToXml(drawback);
        }

        /**
         * 查询退款参数封装 转 xml
         * @param transaction
         * @param offset
         * @return
         * @throws Exception
         */
        public String refundData(String transaction,Integer offset) throws Exception {
            Map<String,String> refund = new HashMap<>();
            refund.put("appid",weChatPayConfig.getAccount());
            refund.put("mch_id",weChatPayConfig.getMerchant());
            refund.put("nonce_str",WXPayUtil.generateNonceStr());
            refund.put("out_trade_no",transaction);
            refund.put("offset",String.valueOf(offset));
            refund.put("sign",WXPayUtil.generateSignature(refund,weChatPayConfig.getSecretKey()));
            return WXPayUtil.mapToXml(refund);
        }

        /**
         * 根据日期下载账单
         * @param billData
         * @param tar
         * @param tarVal   GZIP
         * @Param billType 账单类型
         * @return
         * @throws Exception
         */
        public String getDownloadBillData(String billData,Boolean tar,String tarVal,String billType) throws Exception {
            Map<String,String> download = new HashMap<>();
            download.put("appid",weChatPayConfig.getAccount());
            download.put("mch_id",weChatPayConfig.getMerchant());
            download.put("nonce_str",WXPayUtil.generateNonceStr());
            download.put("sign_type","MD5");
            download.put("bill_date",billData);
            download.put("bill_type",billType);
            if(tar){
                download.put("tar_type",tarVal);
            }
            download.put("sign",WXPayUtil.generateSignature(download,weChatPayConfig.getSecretKey()));
            return WXPayUtil.mapToXml(download);
        }

        /**
         * 下载资金账单Basic
         * @param billDate
         * @param accountType   Basic 基本账户 Operation 运营账户 Fees 手续费账户
         * @param tar
         * @param tarType
         * @return
         * @throws Exception
         */
        public String getFundFlow(String billDate,String accountType,Boolean tar,String tarType) throws Exception {
            Map<String,String> fundFlow = new HashMap<>();
            fundFlow.put("appid",weChatPayConfig.getAccount());
            fundFlow.put("mch_id",weChatPayConfig.getMerchant());
            fundFlow.put("nonce_str",WXPayUtil.generateNonceStr());
            fundFlow.put("bill_date",billDate);
            fundFlow.put("account_type",accountType);
            if(tar){
                fundFlow.put("tar_type",tarType);
            }
            fundFlow.put("sign", WXPayUtil.generateSignature(fundFlow,weChatPayConfig.getSecretKey(), WXPayConstants.SignType.HMACSHA256));
            return WXPayUtil.mapToXml(fundFlow);
        }
    }


    @Component
    public class RequestUtils{

        private WeChatPayConfig config;

        //构造器
        public RequestUtils(WeChatPayConfig config){
            this.config = config;
        }

        public static final String WXPAYSDK_VERSION = "WXPaySDK/3.0.9";

        public  final String USER_AGENT = WXPAYSDK_VERSION +
                " (" + System.getProperty("os.arch") + " " + System.getProperty("os.name") + " " + System.getProperty("os.version") +
                ") Java/" + System.getProperty("java.version") + " HttpClient/" + org.apache.http.client.HttpClient.class.getPackage().getImplementationVersion();

        /**
         * 请求,只请求一次,不做重试
         * @param data
         * @param connectTimeoutMs
         * @param readTimeoutMs
         * @param useCert 是否使用证书,针对退款、撤销等操作
         * @return
         * @throws Exception
         */
        private String requestOnce(final String url, String data, int connectTimeoutMs, int readTimeoutMs, boolean useCert) throws Exception {
            BasicHttpClientConnectionManager connManager;
            if (useCert) {
                // 证书
                char[] password = config.getMerchant().toCharArray();
                InputStream certStream = config.getCertInputStream();
                KeyStore ks = KeyStore.getInstance("PKCS12");
                ks.load(certStream, password);

                // 实例化密钥库 & 初始化密钥工厂
                KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
                kmf.init(ks, password);

                // 创建 SSLContext
                SSLContext sslContext = SSLContext.getInstance("TLS");
                sslContext.init(kmf.getKeyManagers(), null, new SecureRandom());

                SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(
                        sslContext,
                        new String[]{"TLSv1"},
                        null,
                        new DefaultHostnameVerifier());

                connManager = new BasicHttpClientConnectionManager(
                        RegistryBuilder.<ConnectionSocketFactory>create()
                                .register("http", PlainConnectionSocketFactory.getSocketFactory())
                                .register("https", sslConnectionSocketFactory)
                                .build(),
                        null,
                        null,
                        null
                );
            }
            else {
                connManager = new BasicHttpClientConnectionManager(
                        RegistryBuilder.<ConnectionSocketFactory>create()
                                .register("http", PlainConnectionSocketFactory.getSocketFactory())
                                .register("https", SSLConnectionSocketFactory.getSocketFactory())
                                .build(),
                        null,
                        null,
                        null
                );
            }

            org.apache.http.client.HttpClient httpClient = HttpClientBuilder.create()
                    .setConnectionManager(connManager)
                    .build();

            HttpPost httpPost = new HttpPost(url);

            RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(readTimeoutMs).setConnectTimeout(connectTimeoutMs).build();
            httpPost.setConfig(requestConfig);

            StringEntity postEntity = new StringEntity(data, "UTF-8");
            httpPost.addHeader("Content-Type", "text/xml");
            httpPost.addHeader("User-Agent", USER_AGENT + " " + config.getMerchant());
            httpPost.setEntity(postEntity);

            HttpResponse httpResponse = httpClient.execute(httpPost);
            HttpEntity httpEntity = httpResponse.getEntity();
            return EntityUtils.toString(httpEntity, "UTF-8");
        }

        /**
         * 输入流转换为xml字符串
         * @param inputStream
         * @return
         */
        public String convertToString(InputStream inputStream) throws IOException, IOException {
            ByteArrayOutputStream outSteam = new ByteArrayOutputStream();
            byte[] buffer = new byte[1024];
            int len = 0;
            while ((len = inputStream.read(buffer)) != -1) {
                outSteam.write(buffer, 0, len);
            }
            outSteam.close();
            inputStream.close();
            return new String(outSteam.toByteArray(), "utf-8");
        }

    }
}

5、封装网络请求工具类

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.net.ssl.*;
import java.io.*;
import java.net.*;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ThreadLocalRandom;
import java.util.zip.GZIPInputStream;

/*
使用方法:
HttpClient client = HttpClient.buildHttpClient();
Request request = client.buildRequest("http://localhost:8881/charge/testGet?key1=0111&value=shaines.cn");
// Request request = client.buildRequest("http://localhost:8881/charge/testPost").setMethod(Method.POST);
// Request request = client.buildRequest("http://localhost:8881/charge/testPut").setMethod(Method.PUT);
// Request request = client.buildRequest("http://localhost:8881/charge/testFile").setMethod(Method.POST);
request.setParam(Params.ofFormData().add("key1", "1111").add("key3", "2222")
                         .addFile("key2", new File("C:\\Users\\houyu\\Desktop\\1.txt"))
                         .addFile("key4", new File("C:\\Users\\houyu\\Desktop\\2.png")));
Response<String> response = request.execute(BodyHandlers.ofString());
System.out.println("response.getUrl() = " + response.getUrl());
System.out.println("response.getBody() = " + response.getBody());
 */

/**
 * http 客户端
 * @author for.houyu@qq.com
 * @createTime 2019/10/11 22:31
 */
public class HttpClient {

    /** 域对象 */
    private Session session;

    private HttpClient() {}

    public static HttpClient buildHttpClient() {
        HttpClient client = new HttpClient();
        client.session = new Session();
        return client;
    }

    public Request buildRequest(String url) {
        return new Request(url, this);
    }

    public <T> Response<T> execute(Request request, BodyHandler<T> bodyHandler) {
        return Executor.build(request).execute(bodyHandler);
    }

    public Session session() {
        return this.session;
    }

    @Override
    public String toString() {
        final StringBuilder builder = new StringBuilder("HttpClient{");
        builder.append("session=").append(this.session);
        builder.append('}');
        return builder.toString();
    }
    /***/
    /** 内部类 start +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 内部类 start */
    /***/
    // =============================================== Session (域对象) ========================================================== //

    /**
     * 域对象
     */
    public static class Session {

        private static final Logger log = LoggerFactory.getLogger(Session.class);

        /** 请求方法 */
        private final Method method = Method.GET;
        /** 是否编码URL */
        private volatile boolean ifEncodeUrl = false;
        /** 是否缓存 */
        private volatile boolean ifCache = false;
        /** 超时时间 (单位:毫秒) 1分钟 */
        private volatile int timeout = 60000;
        /** 是否稳定重定向 */
        private volatile boolean ifStableRedirection = true;
        /** 是否处理https */
        private volatile boolean ifHandleHttps = true;
        /** 是否启用默认主机名验证程序 */
        private volatile boolean ifEnableDefaultHostnameVerifier = false;
        /** 推荐(上一个网页地址) */
        private volatile String referer;
        /** cookie */
        private final Map<String, String> cookie = new ConcurrentHashMap<>(16);
        /** 代理 */
        private volatile Proxy proxy;
        /** 参数编码 */
        private volatile Charset charset = StandardCharsets.UTF_8;
        /** 主机名验证程序 */
        private HostnameVerifier hostnameVerifier;
        /** SocketFactory */
        private SSLSocketFactory sslSocketFactory;
        /** 携带参数(可使用于响应之后的操作) */
        private final Map<String, Object> extra = new ConcurrentHashMap<>(16);
        /** 请求头信息 (默认的请求头信息) */
        private final Map<String, Object> header = new ConcurrentHashMap<>(8);

        /* -------------------------------- constructor -------------------------- start */

        protected Session() {
            this.header.put("Accept", "text/html,application/xhtml+xml,application/xml,application/json;q=0.9,*/*;q=0.8");
            // "Mozilla/5.0 (Linux; Android 8.1.0; 1809-A01 Build/OPM1; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/62.0.3202.97 Mobile Safari/537.36"
            this.header.put("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36");
            this.header.put("Accept-Encoding", "gzip");
            this.header.put("Accept-Language", "zh-CN,zh;q=0.9,zh-TW;q=0.8,en-US;q=0.7,en;q=0.6");
            // this.header.put("Content-Type", "application/x-www-form-urlencoded");
            // this.header = Collections.unmodifiableMap(header);
            // 初始化全局主机名验证程序
            this.hostnameVerifier = (s, sslSession) -> true;
            // 初始化全局主机名验证程序
            final X509TrustManager x509TrustManager = new X509TrustManager() {
                @Override
                public void checkClientTrusted(X509Certificate[] x509Certificates, String s) {
                }

                @Override
                public void checkServerTrusted(X509Certificate[] x509Certificates, String s) {
                }

                @Override
                public X509Certificate[] getAcceptedIssuers() {
                    // return new X509Certificate[0];
                    return null;
                }
            };
            try {
                SSLContext sslContext = SSLContext.getInstance("TLS");
                sslContext.init(null, new TrustManager[] {x509TrustManager}, new SecureRandom());
                this.sslSocketFactory = sslContext.getSocketFactory();
            } catch(NoSuchAlgorithmException | KeyManagementException e) {
                log.warn("init SSLContext has exception ", e);
            }
        }

        /* -------------------------------- constructor -------------------------- end */


        /* ---------------------------------- setter ----------------------------- start */

        public Session setIfEncodeUrl(boolean ifEncodeUrl) {
            this.ifEncodeUrl = ifEncodeUrl;
            return this;
        }

        public Session setIfCache(boolean ifCache) {
            this.ifCache = ifCache;
            return this;
        }

        public Session setTimeout(int timeout) {
            this.timeout = timeout < 0 ? this.timeout : timeout;
            return this;
        }

        public Session setIfStableRedirection(boolean ifStableRedirection) {
            this.ifStableRedirection = ifStableRedirection;
            return this;
        }

        public Session setIfHandleHttps(boolean ifHandleHttps) {
            this.ifHandleHttps = ifHandleHttps;
            return this;
        }

        public Session setIfEnableDefaultHostnameVerifier(boolean ifEnableDefaultHostnameVerifier) {
            this.ifEnableDefaultHostnameVerifier = ifEnableDefaultHostnameVerifier;
            return this;
        }

        public Session setReferer(String referer) {
            this.referer = Util.nullOfDefault(referer, this.referer);
            return this;
        }

        public Session addCookie(Map<String, String> cookie) {
            if(Util.isNotEmpty(cookie)) {
                this.cookie.putAll(cookie);
            }
            return this;
        }

        /**
         * 覆盖之前的所有 cookie
         */
        public Session setCookie(String cookie) {
            if(Util.isNotEmpty(cookie)) {
                String[] split = cookie.split(Constant.COOKIE_SPLIT);
                for(String cookieObject : split) {
                    String[] keyAndVal = cookieObject.split(Constant.EQU, 2);
                    this.cookie.put(keyAndVal[0], keyAndVal[1]);
                }
            }
            return this;
        }

        public Session setProxy(Proxy proxy) {
            this.proxy = Util.nullOfDefault(proxy, this.proxy);
            return this;
        }

        public Session setCharset(Charset charset) {
            this.charset = Util.nullOfDefault(charset, this.charset);
            return this;
        }

        public Session setHostnameVerifier(HostnameVerifier hostnameVerifier) {
            this.hostnameVerifier = Util.nullOfDefault(hostnameVerifier, this.hostnameVerifier);
            return this;
        }

        public Session setSslSocketFactory(SSLSocketFactory sslSocketFactory) {
            this.sslSocketFactory = Util.nullOfDefault(sslSocketFactory, this.sslSocketFactory);
            return this;
        }

        public Session addExtra(Map<String, Object> extra) {
            if(Util.isNotEmpty(extra)) {
                this.extra.putAll(extra);
            }
            return this;
        }

        public Session addExtra(String key, Object val) {
            if(Util.isNotEmpty(key) && Util.isNotEmpty(val)) {
                this.extra.put(key, val);
            }
            return this;
        }

        /**
         * 覆盖之前的所有 extra
         * @param extra 如果不为 null ,那就覆盖之前所有的 extra
         */
        public Session setExtra(Map<String, Object> extra) {
            if(Objects.nonNull(extra)) {
                this.extra.clear();
                this.extra.putAll(extra);
            }
            return this;
        }

        public Session addHeader(Map<String, Object> header) {
            if(Util.isNotEmpty(header)) {
                this.header.putAll(header);
            }
            return this;
        }

        public Session addHeader(String key, Object val) {
            if(Util.isNotEmpty(key) && Util.isNotEmpty(val)) {
                this.header.put(key, val);
            }
            return this;
        }

        /**
         * 覆盖之前的所有 header
         * @param header 如果不为 null ,那就覆盖之前所有的 header
         */
        public Session setHeader(Map<String, Object> header) {
            if(Objects.nonNull(header)) {
                this.header.clear();
                this.header.putAll(header);
            }
            return this;
        }

        /* ---------------------------------- setter ----------------------------- end */

        /* ---------------------------------- getter ----------------------------- start */

        protected Method getMethod() {
            return this.method;
        }

        protected boolean getIfEncodeUrl() {
            return this.ifEncodeUrl;
        }

        protected boolean getIfCache() {
            return this.ifCache;
        }

        protected int getTimeout() {
            return this.timeout;
        }

        protected boolean getIfStableRedirection() {
            return this.ifStableRedirection;
        }

        protected boolean getIfHandleHttps() {
            return this.ifHandleHttps;
        }

        protected boolean getIfEnableDefaultHostnameVerifier() {
            return this.ifEnableDefaultHostnameVerifier;
        }

        protected String getReferer() {
            return this.referer;
        }

        protected String getCookie() {
            // cookie ex:key2=val2; key1=val1
            StringBuilder builder = new StringBuilder(128);
            for(Entry<String, String> entry : this.cookie.entrySet()) {
                builder.append(entry.getKey()).append("=").append(entry.getValue()).append("; ");
            }
            return builder.length() > 0 ? builder.delete(builder.length() - 2, builder.length()).toString() : "";
        }

        protected Proxy getProxy() {
            return this.proxy == null ? null : Proxy.of(this.proxy.getHost(), this.proxy.getPort(), this.proxy.getUsername(), this.proxy.getPassword());
        }

        protected Charset getCharset() {
            return Charset.forName(this.charset.name());
        }

        protected HostnameVerifier getHostnameVerifier() {
            return this.hostnameVerifier;
        }

        protected SSLSocketFactory getSslSocketFactory() {
            return this.sslSocketFactory;
        }

        protected Map<String, Object> getExtra() {
            return new HashMap<>(this.extra);
        }

        protected Map<String, Object> getHeader() {
            return new HashMap<>(this.header);
        }

        /* ---------------------------------- getter ----------------------------- end */

        /* ---------------------------------- toString ----------------------------- start */

        @Override
        public String toString() {
            final StringBuilder builder = new StringBuilder("Session{");
            builder.append("method=").append(this.method);
            builder.append(", ifEncodeUrl=").append(this.ifEncodeUrl);
            builder.append(", ifCache=").append(this.ifCache);
            builder.append(", timeout=").append(this.timeout);
            builder.append(", ifStableRedirection=").append(this.ifStableRedirection);
            builder.append(", ifHandleHttps=").append(this.ifHandleHttps);
            builder.append(", ifEnableDefaultHostnameVerifier=").append(this.ifEnableDefaultHostnameVerifier);
            builder.append(", referer='").append(this.referer).append('\'');
            builder.append(", cookie=").append(this.cookie);
            builder.append(", proxy=").append(this.proxy);
            builder.append(", charset=").append(this.charset);
            builder.append(", hostnameVerifier=").append(this.hostnameVerifier);
            builder.append(", sslSocketFactory=").append(this.sslSocketFactory);
            builder.append(", extra=").append(this.extra);
            builder.append(", header=").append(this.header);
            builder.append('}');
            return builder.toString();
        }

        /* ---------------------------------- toString ----------------------------- end */

    }
    // =============================================== Executor (执行对象) ======================================================= //

    /**
     * 执行对象
     */
    public static class Executor {

        private static final Logger log = LoggerFactory.getLogger(Executor.class);

        /** 请求对象 */
        private Request request;
        /** 重定向的url列表 */
        private List<String> redirectUrlList;
        /** HttpURLConnection对象 */
        private HttpURLConnection http;

        protected Executor(Request request) {
            this.request = request;
        }

        /* ---------------------------------- getter ----------------------------- start */

        protected Request getRequest() {
            return this.request;
        }

        protected List<String> getRedirectUrlList() {
            return this.redirectUrlList;
        }

        protected HttpURLConnection getHttp() {
            return this.http;
        }

        /* ---------------------------------- getter ----------------------------- end   */

        protected static Executor build(Request request) {
            return new Executor(request);
        }

        protected <T> Response<T> execute(BodyHandler<T> handler) {
            try {
                return handleHttpConnection(handler);
            } catch(Throwable e) {
                log.warn("handleHttpConnection has exception, use error response. message info: {}", e.getMessage());
                return (Response<T>) Response.getErrorResponse(this.request, e);
            }
        }

        private <T> Response<T> handleHttpConnection(BodyHandler<T> handler) {
            // 处理URL参数问题
            this.handleUrlParam();
            // 初始化连接
            this.initConnection();
            // 发送数据包裹
            this.send();
            // 处理重定向
            boolean ifRedirect = this.handleRedirect();
            if(ifRedirect) {
                // 递归实现重定向
                return this.handleHttpConnection(handler);
            }
            // 返回响应
            return new Response<>(this, handler);
        }

        private boolean handleRedirect() {
            if(this.request.getIfStableRedirection()) {
                // 采用稳定重定向方式, 需要处理重定向问题
                int responseCode;
                try {
                    responseCode = this.http.getResponseCode();
                } catch(IOException var3) {
                    throw new RuntimeException(String.format("%s get response code has exception", this.request.getUrl()), var3);
                }
                if(Constant.REDIRECT_CODES.contains(responseCode)) {
                    String redirectURL = this.http.getHeaderField(Constant.LOCATION);
                    try {
                        redirectURL = processRedirectURL(redirectURL);
                    } catch(MalformedURLException e) {
                        throw new RuntimeException(String.format("%s processRedirectURL has exception", this.request.getUrl()), e);
                    }
                    this.request.setUrl(redirectURL);
                    this.redirectUrlList = Util.nullOfDefault(this.redirectUrlList, new ArrayList<String>(8));
                    this.redirectUrlList.add(this.request.getUrl());
                    if(this.redirectUrlList.size() < 8) {
                        // 断开本次连接, 然后重新请求
                        this.http.disconnect();
                        log.debug("{} request redirecting ", this.request.getUrl());
                        return true;
                    }
                }
            } else {
                // 使用默认的重定向规则处理, 无序手动处理, 但是有可能出现重定向失败
                // do non thing
            }
            return false;
        }

        private String processRedirectURL(String redirectURL) throws MalformedURLException {
            URL previousURL = new URL(this.request.getUrl());
            if(redirectURL.startsWith("/")) {
                // 重定向的URL 是一个绝对路径 https://www.baodu.com/test        https://www.baidu.com:10086/test
                StringBuilder builder = new StringBuilder(previousURL.getProtocol());
                builder.append("://");
                builder.append(previousURL.getHost());
                builder.append(previousURL.getPort() == -1 ? "" : (":" + previousURL.getPort()));
                builder.append(redirectURL);
                redirectURL = builder.toString();
                // } else if(redirectURL.startsWith("./")) {
                //     // 重定向的URL 是一个相对路径 TODO 暂时不处理, 后面有需求再弄
            }
            return redirectURL;
        }

        /** 发送数据 */
        private void send() {
            try {
                if(Method.GET.equals(this.request.getMethod())) {
                    this.http.connect();
                } else {
                    // POST...
                    this.handleContentTypeAndBody();
                }
            } catch(IOException e) {
                throw new RuntimeException(String.format("%s send data has exception", this.request.getUrl()), e);
            }
        }

        /** 处理 ContentType 和 传输内容 */
        private void handleContentTypeAndBody() throws IOException {
            if(!Method.GET.equals(this.request.getMethod())) {
                // non GET
                /* handle ContentType 有可能多个content-type, 大小写不一致的问题 */
                if(this.request.getParam() == null) {
                    this.request.setParam(Params.ofForm());
                }
                this.request.removeHeader("content-type");
                Object tempContentType = this.request.getHeader(Constant.CONTENT_TYPE);
                String contentType = tempContentType == null ? this.request.getParam().contentType : String.valueOf(tempContentType);
                this.addAndRefreshHead(Constant.CONTENT_TYPE, contentType);
                /* handle body */
                // 非GET 所有的请求头必须在调用getOutputStream()之前设置好, 这里相当于GET的connect();
                byte[] body = this.request.getParam().ok().body;
                if(Util.isNotEmpty(body)) {
                    try(OutputStream outputStream = this.http.getOutputStream()) {
                        // 使用 try-with-resource 方式处理流, 无需手动关闭流操作
                        outputStream.write(body);
                        outputStream.flush();
                    }
                }
            }
        }

        /** 刷新 请求头信息 */
        private void addAndRefreshHead(String key, Object value) {
            if(Util.isNotEmpty(key) && Util.isNotEmpty(value)) {
                this.request.addHeader(key, value);
                this.http.setRequestProperty(key, String.valueOf(value));
            }
        }

        /** 初始化连接 */
        private void initConnection() throws RuntimeException {
            URL url;
            try {
                url = new URL(this.request.getUrl());
            } catch(MalformedURLException e) {
                throw new RuntimeException(String.format("%s create URL has exception", this.request.getUrl()), e);
            }
            //
            try {
                this.http = this.openConnection(url, this.request.getProxy());
                //
                if(this.request.getTimeout() > 0) {
                    // 设置超时
                    this.http.setConnectTimeout(this.request.getTimeout());
                    this.http.setReadTimeout(this.request.getTimeout());
                }
                // 设置请求方法
                this.http.setRequestMethod(this.request.getMethod().name());
            } catch(IOException e) {
                throw new RuntimeException(String.format("%s open connection has exception", this.request.getUrl()), e);
            }
            //
            this.http.setDoInput(true);
            if(!Method.GET.equals(this.request.getMethod())) {
                // 非GET方法需要设置可输入
                http.setDoOutput(true);
                http.setUseCaches(false);
            }
            // 设置cookie
            this.setCookie();
            // 设置请求头到连接中
            this.request.getHeader().forEach((k, v) -> this.http.setRequestProperty(k, String.valueOf(v)));
            // 设置缓存
            if(this.request.getIfCache() && !Method.GET.equals(this.request.getMethod())) {
                this.http.setUseCaches(true);
            }
            // 设置是否自动重定向
            this.http.setInstanceFollowRedirects(!(this.request.getIfStableRedirection()));
        }

        private void setCookie() {
            if(Util.isNotEmpty(this.request.getCookie())) {
                log.debug("{} set cookie {}", this.request.getUrl(), this.request.getCookie());
                this.request.removeHeader("cookie");
                this.request.addHeader(Constant.REQUEST_COOKIE, this.request.getCookie());
            }
        }

        /** 打开连接 */
        private HttpURLConnection openConnection(URL url, Proxy proxy) throws IOException {
            URLConnection connection;
            if(this.request.getProxy() == null) {
                connection = url.openConnection();
            } else if(Util.isNotEmpty(proxy.getUsername())) {
                // 设置代理服务器
                java.net.Proxy javaNetProxy = new java.net.Proxy(java.net.Proxy.Type.HTTP, new InetSocketAddress(proxy.getHost(), proxy.getPort()));
                connection = url.openConnection(javaNetProxy);
                String authString = proxy.getUsername() + ":" + proxy.getPassword();
                String auth = "Basic " + Base64.getEncoder().encodeToString(authString.getBytes(this.request.getClient().session().getCharset()));
                connection.setRequestProperty(Constant.PROXY_AUTHORIZATION, auth);
                log.debug("{} do proxy server ", this.request.getUrl());
            } else if(Util.isNotEmpty(proxy.getHost())) {
                // 设置代理主机和端口
                java.net.Proxy javaNetProxy = new java.net.Proxy(java.net.Proxy.Type.HTTP, new InetSocketAddress(proxy.getHost(), proxy.getPort()));
                connection = url.openConnection(javaNetProxy);
                log.debug("{} do proxy ", this.request.getUrl());
            } else {
                // 不设置代理
                connection = url.openConnection();
            }
            if(this.request.getIfHandleHttps() && connection instanceof HttpsURLConnection) {
                HttpsURLConnection httpsConn = (HttpsURLConnection) connection;
                // 设置主机名验证程序
                if(this.request.getIfEnableDefaultHostnameVerifier()) {
                    httpsConn.setHostnameVerifier(this.request.getHostnameVerifier());
                }
                // 设置ssl factory
                httpsConn.setSSLSocketFactory(this.request.getSslSocketFactory());
            }
            return (HttpURLConnection) connection;
        }

        /**
         * 设置 url 参数问题
         */
        private void handleUrlParam() {
            // 处理url中的query进行url编码
            int indexOf;
            if(this.request.getIfEncodeUrl() && (indexOf = this.request.getUrl().indexOf(Constant.queryFlag)) > -1) {
                String query = this.request.getUrl().substring(indexOf);
                query = Util.urlEncode(query, request.getClient().session().getCharset());
                query = query.replace("%3F", "?").replace("%2F", "/").replace("%3A", ":").replace("%3D", "=").replace("%26", "&").replace("%23", "#");
                this.request.setUrl(this.request.getUrl().substring(0, indexOf) + query);
            }
        }

    }
    // =============================================== Request (请求对象) ======================================================== //

    /**
     * 请求对象
     */
    public static class Request {

        /** 请求网站地址 */
        private String url;
        /** 请求方法 */
        private Method method;
        /** 请求头 */
        private Map<String, Object> header;
        /** 请求参数 */
        private Param param;
        /** 携带参数(可使用于响应之后的操作) */
        private Map<String, Object> extra;
        /** 代理 */
        private Proxy proxy;
        /** 是否编码URL */
        private boolean ifEncodeUrl;
        /** 是否缓存 */
        private boolean ifCache;
        /** 连接超时(单位:毫秒) */
        private int timeout;
        /** 携带cookie(优先) ex: key1=val1; key2=val2 */
        private String cookie;
        /** 是否稳定重定向 */
        private boolean ifStableRedirection;
        /** 是否处理https */
        private boolean ifHandleHttps;
        /** 是否启用默认主机名验证程序 */
        private boolean ifEnableDefaultHostnameVerifier;
        /** 主机名验证程序 */
        private HostnameVerifier hostnameVerifier;
        /** SocketFactory */
        private SSLSocketFactory sslSocketFactory;
        /** 客户端 */
        private HttpClient client;

        protected Request(String url, HttpClient client) {
            this.setUrl(url);
            this.client = client;
            this.init();
        }

        private void init() {
            Session session = client.session();
            this.setMethod(session.getMethod());
            this.setHeader(session.getHeader());
            if(Util.isNotEmpty(session.getReferer())) {
                this.addHeader(Constant.REFERER, session.getReferer());
            }
            this.setExtra(session.getExtra());
            this.setProxy(session.getProxy());
            this.setIfEncodeUrl(session.getIfEncodeUrl());
            this.setIfCache(session.getIfCache());
            this.setTimeout(session.getTimeout());
            this.setCookie(session.getCookie());
            this.setIfStableRedirection(session.getIfStableRedirection());
            this.setIfHandleHttps(session.getIfHandleHttps());
            this.setIfEnableDefaultHostnameVerifier(session.getIfEnableDefaultHostnameVerifier());
            this.setHostnameVerifier(session.getHostnameVerifier());
            this.setSslSocketFactory(session.getSslSocketFactory());
        }

        public <T> Response<T> execute(BodyHandler<T> bodyHandler) {
            return this.client.execute(this, bodyHandler);
        }

        /* ---------------------------- setter ---------------------------------- start */

        protected Request setUrl(String url) {
            // 校验url地址
            this.url = String.valueOf(url);
            if (url.startsWith("//")) {
                this.url = "http:" + this.url;
            } else if (url.startsWith("://")) {
                this.url = "http" + this.url;
            } else if(!this.url.toLowerCase().startsWith(Constant.HTTP)) {
                this.url = Constant.HTTP + "://" + this.url;
            }
            return this;
        }

        public Request setMethod(Method method) {
            this.method = Util.nullOfDefault(method, Method.GET);
            return this;
        }

        public Request GET() {
            return this.setMethod(Method.GET);
        }

        public Request POST() {
            return this.setMethod(Method.POST);
        }

        public Request PUT() {
            return this.setMethod(Method.PUT);
        }

        public Request DELETE() {
            return this.setMethod(Method.DELETE);
        }

        /**
         * 调用这个方法会覆盖之前所有的请求头
         * @param header 如果 header 不为 null ,那就覆盖之前的所有 header
         */
        public Request setHeader(Map<String, Object> header) {
            this.header = Util.nullOfDefault(header, this.header);
            return this;
        }

        public Request addHeader(String key, Object val) {
            if(Util.isNotEmpty(key) && Util.isNotEmpty(val)) {
                // 这里 header 不可以能为 null
                this.header.put(key, val);
            }
            return this;
        }

        public Request removeHeader(String key) {
            if(Util.isNotEmpty(key)) {
                // 这里 header 不可以能为 null
                this.header.remove(key);
            }
            return this;
        }

        public Request setParam(Param param) {
            this.param = Util.nullOfDefault(param, this.param);
            return this;
        }

        /**
         * 调用这个方法会覆盖之前所有的extra
         * @param extra 如果 extra 不为 null ,那就覆盖之前的所有 extra
         */
        public Request setExtra(Map<String, Object> extra) {
            this.extra = Util.nullOfDefault(extra, this.extra);
            return this;
        }

        public Request addExtra(String key, Object val) {
            if(Util.isNotEmpty(key) && Util.isNotEmpty(val)) {
                // 这里 extra 不可以能为 null
                this.extra.put(key, val);
            }
            return this;
        }

        public Request setProxy(Proxy proxy) {
            this.proxy = Util.nullOfDefault(proxy, this.proxy);
            return this;
        }

        public Request setIfEncodeUrl(boolean ifEncodeUrl) {
            this.ifEncodeUrl = ifEncodeUrl;
            return this;
        }

        public Request setIfCache(boolean ifCache) {
            this.ifCache = ifCache;
            return this;
        }

        public Request setTimeout(int timeout) {
            this.timeout = timeout < 0 ? this.timeout : timeout;
            return this;
        }

        public Request setCookie(String cookie) {
            this.cookie = Util.nullOfDefault(cookie, this.cookie);
            return this;
        }

        public Request setIfStableRedirection(boolean ifStableRedirection) {
            this.ifStableRedirection = ifStableRedirection;
            return this;
        }

        public Request setIfHandleHttps(boolean ifHandleHttps) {
            this.ifHandleHttps = ifHandleHttps;
            return this;
        }

        public Request setIfEnableDefaultHostnameVerifier(boolean ifEnableDefaultHostnameVerifier) {
            this.ifEnableDefaultHostnameVerifier = ifEnableDefaultHostnameVerifier;
            return this;
        }

        public Request setHostnameVerifier(HostnameVerifier hostnameVerifier) {
            this.hostnameVerifier = Util.nullOfDefault(hostnameVerifier, this.hostnameVerifier);
            return this;
        }

        public Request setSslSocketFactory(SSLSocketFactory sslSocketFactory) {
            this.sslSocketFactory = Util.nullOfDefault(sslSocketFactory, this.sslSocketFactory);
            return this;
        }

        /* ---------------------------- setter ---------------------------------- end */

        /* ---------------------------- getter ---------------------------------- start */

        protected Map<String, Object> getExtra() {
            return this.extra;
        }

        protected Proxy getProxy() {
            return this.proxy;
        }

        protected String getCookie() {
            return this.cookie;
        }

        protected String getUrl() {
            return this.url;
        }

        protected Method getMethod() {
            return this.method;
        }

        protected Map<String, Object> getHeader() {
            return this.header;
        }

        protected Object getHeader(String key) {
            return Util.isNotEmpty(key) ? this.header.get(key) : null;
        }

        protected Param getParam() {
            return this.param;
        }

        protected boolean getIfEncodeUrl() {
            return this.ifEncodeUrl;
        }

        protected boolean getIfCache() {
            return this.ifCache;
        }

        protected int getTimeout() {
            return this.timeout;
        }

        protected boolean getIfStableRedirection() {
            return this.ifStableRedirection;
        }

        protected boolean getIfHandleHttps() {
            return this.ifHandleHttps;
        }

        protected boolean getIfEnableDefaultHostnameVerifier() {
            return this.ifEnableDefaultHostnameVerifier;
        }

        protected HostnameVerifier getHostnameVerifier() {
            return this.hostnameVerifier;
        }

        protected SSLSocketFactory getSslSocketFactory() {
            return this.sslSocketFactory;
        }

        protected HttpClient getClient() {
            return this.client;
        }

        /* ---------------------------- getter ---------------------------------- end */


        /* ---------------------------- toString ---------------------------------- start */

        @Override
        public String toString() {
            final StringBuilder builder = new StringBuilder("Request{");
            builder.append("url='").append(this.url).append('\'');
            builder.append(", method=").append(this.method);
            builder.append(", header=").append(this.header);
            builder.append(", param=").append(this.param);
            builder.append(", extra=").append(this.extra);
            builder.append(", proxy=").append(this.proxy);
            builder.append(", ifEncodeUrl=").append(this.ifEncodeUrl);
            builder.append(", ifCache=").append(this.ifCache);
            builder.append(", timeout=").append(this.timeout);
            builder.append(", cookie='").append(this.cookie).append('\'');
            builder.append(", ifStableRedirection=").append(this.ifStableRedirection);
            builder.append(", ifHandleHttps=").append(this.ifHandleHttps);
            builder.append(", ifEnableDefaultHostnameVerifier=").append(this.ifEnableDefaultHostnameVerifier);
            builder.append(", hostnameVerifier=").append(this.hostnameVerifier);
            builder.append(", sslSocketFactory=").append(this.sslSocketFactory);
            // builder.append(", client=").append(this.client);
            builder.append('}');
            return builder.toString();
        }

        /* ---------------------------- toString ---------------------------------- end   */

    }
    // =============================================== Param (请求参数) ========================================================== //

    /**
     * 请求参数
     */
    public static abstract class Param {

        /** 请求头 内容类型 */
        String contentType;
        /** 请求内容 */
        byte[] body;

        Param() {}

        Param(String contentType, byte[] body) {
            this.contentType = contentType;
            this.body = body;
        }

        /**
         * 获取 param 之前会先调用 ok() 方法, 确保准备完毕
         */
        public abstract Param ok();

        /* ---------------------------------- toString ----------------------------- start */

        @Override
        public String toString() {
            final StringBuilder builder = new StringBuilder("BaseParam{");
            builder.append("contentType='").append(this.contentType).append('\'');
            builder.append(", body=").append(new String(this.body));
            builder.append('}');
            return builder.toString();
        }

        /* ---------------------------------- toString ----------------------------- end */
    }
    // =============================================== Params (请求参数工具) ===================================================== //

    /**
     * 请求参数工具
     */
    public static abstract class Params {

        /* ---------------------------------- 实现 Param 的内部类 ----------------------------- start */

        public static class ParamJson extends Param {

            ParamJson(String jsonString, Charset charset) {
                super(Constant.CONTENT_TYPE_WITH_JSON + charset.name(), jsonString.getBytes(charset));
            }

            @Override
            public Param ok() {
                return this;
            }
        }


        public static class ParamXml extends Param {

            ParamXml(String xmlString,Charset charset) {
                super(Constant.CONTENT_TYPE_WITH_XML,xmlString.getBytes(charset));
            }

            @Override
            public Param ok() {
                return this;
            }
        }

        public static class ParamForm extends Param {

            Map<String, Object> paramMap;
            Charset charset;

            ParamForm(Charset charset) {
                this.charset = charset;
                this.paramMap = new HashMap<>(8);
            }

            public ParamForm add(String key, Object val) {
                if(Util.isNotEmpty(key) && Util.isNotEmpty(val)) {
                    this.paramMap.put(key, val);
                }
                return this;
            }

            public ParamForm add(Map<String, Object> paramMap) {
                if(Util.isNotEmpty(paramMap)) {
                    paramMap.forEach(this::add);
                }
                return this;
            }

            @Override
            public Param ok() {
                this.contentType = Constant.CONTENT_TYPE_WITH_FORM + this.charset.name();
                this.body = Util.paramMapAsString(this.paramMap, this.charset).getBytes(this.charset);
                return this;
            }
        }

        public static class ParamFormData extends Param {

            @Override
            public Param ok() {
                this.body = this.fillData();
                return this;
            }

            @Override
            public String toString() {
                final StringBuilder builder = new StringBuilder("ParamFormData{").append("\n");
                builder.append("contentType='").append("\n").append(contentType).append('\'').append("\n");
                builder.append("body=").append("\n").append(new String(this.body, this.charset)).append("\n");
                builder.append("charset=").append("\n").append(charset).append("\n");
                builder.append('}');
                return builder.toString();
            }

            /* ------------------------------------------------------------------------------------------- */

            public static class Resource {

                public File file;
                public Charset charset;

                public Resource(File file, Charset charset) {
                    this.file = file;
                    this.charset = Util.nullOfDefault(charset, Constant.defaultCharset);
                }

                public File getFile() {
                    return this.file;
                }

                public Charset getCharset() {
                    return this.charset;
                }
            }

            private static final String horizontalLine = "--------------------------";
            private static final String lineFeed = System.lineSeparator();
            private static final String fileFormat = "Content-Disposition: form-data; name=\"%s\"; filename=\"%s\"\nContent-Type: %s";
            private static final String textFormat = "Content-Disposition: form-data; name=\"%s\"";

            Charset charset;
            String separator;
            String endFlag;
            Map<String, Object> tempMap;

            protected ParamFormData(Charset charset) {
                this.charset = charset;
                this.init();
            }

            private void init() {
                long randomNumber = ThreadLocalRandom.current().nextLong();
                contentType = Constant.CONTENT_TYPE_WITH_FORM_DATA + horizontalLine + randomNumber;
                separator = "--" + horizontalLine + randomNumber;
                endFlag = separator + "--" + lineFeed;
                tempMap = new LinkedHashMap<>(8);
            }

            public ParamFormData add(String key, Object val) {
                if(Util.isNotEmpty(key) && Util.isNotEmpty(val)) {
                    this.tempMap.put(key, val);
                }
                return this;
            }

            public ParamFormData addFile(String key, File file) {
                return this.addFile(key, file, null);
            }

            public ParamFormData addFile(String key, File file, Charset charset) {
                if(Util.isNotEmpty(key) && file != null) {
                    this.add(key, new Resource(file, charset));
                }
                return this;
            }

            private byte[] fillData() {
                try(ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
                    for(Entry<String, Object> entry : this.tempMap.entrySet()) {
                        String key = entry.getKey();
                        Object value = entry.getValue();
                        if(value instanceof Resource) {
                            this.appendResource(outputStream, key, (Resource) value);
                        } else {
                            this.appendText(outputStream, key, value);
                        }
                    }
                    outputStream.write(this.endFlag.getBytes(this.charset));
                    outputStream.flush();
                    return outputStream.toByteArray();
                } catch(IOException e) {
                    throw new RuntimeException(e);
                }
            }

            private void appendResource(OutputStream outputStream, String key, Resource value) {
                StringBuilder builder = new StringBuilder(1024);
                File file = value.getFile();
                Path path = Paths.get(file.getAbsolutePath());
                try {
                    // append 头部信息
                    builder.append(separator).append(lineFeed);
                    builder.append(String.format(fileFormat, key, file.getName(), this.parseFileType(path))).append(lineFeed);
                    builder.append(lineFeed);
                    outputStream.write(builder.toString().getBytes(value.getCharset()));
                    // append 实体
                    Files.copy(path, outputStream);
                    // append 换行
                    outputStream.write(lineFeed.getBytes(this.charset));
                    outputStream.flush();
                } catch(IOException e) {
                    throw new RuntimeException(e);
                }
            }

            private void appendText(OutputStream outputStream, String key, Object value) {
                StringBuilder builder = new StringBuilder(1024);
                try {
                    // append 头部信息
                    builder.append(separator).append(lineFeed);
                    builder.append(String.format(textFormat, key)).append(lineFeed);
                    builder.append(lineFeed);
                    // append 实体
                    builder.append(value);
                    outputStream.write(builder.toString().getBytes(this.charset));
                    // append 换行
                    outputStream.write(lineFeed.getBytes(this.charset));
                    outputStream.flush();
                } catch(IOException e) {
                    throw new RuntimeException(e);
                }
            }

            private String parseFileType(Path path) throws IOException {
                return Files.probeContentType(path);
            }
        }

        /* ---------------------------------- 实现 Param 的内部类 ----------------------------- end   */

        /* ---------------------------------- 提供快捷实现静态方法 ----------------------------- start   */

        public static ParamJson ofJson(String jsonString, Charset charset) {
            return new ParamJson(jsonString, charset);
        }

        public static ParamXml ofXml(String xmlString,Charset charset){
            return new ParamXml(xmlString,charset);
        }

        public static ParamJson ofJson(String jsonString) {
            return ofJson(jsonString, Constant.defaultCharset);
        }

        public static ParamForm ofForm(Charset charset) {
            return new ParamForm(charset);
        }

        public static ParamForm ofForm() {
            return ofForm(Constant.defaultCharset);
        }

        public static ParamFormData ofFormData(Charset charset) {
            return new ParamFormData(charset);
        }

        public static ParamFormData ofFormData() {
            return ofFormData(Constant.defaultCharset);
        }

        /* ---------------------------------- 提供快捷实现静态方法 ----------------------------- end   */

    }
    // =============================================== Response (响应对象) ======================================================= //

    /**
     * 响应对象
     * @param <T> 响应体类型
     */
    public static class Response<T> {

        /** 执行者 */
        private Executor executor;
        /** 响应处理 */
        private BodyHandler<T> bodyHandler;
        /***/
        /** HttpURLConnection */
        private HttpURLConnection http;
        /** 请求url */
        private String url;
        /** 重定向的url列表 */
        private List<String> redirectUrlList;
        /** http响应状态码(HttpURLConnection.HTTP_OK) */
        private int statusCode = -1;
        /** 响应头信息 */
        private Map<String, List<String>> header;
        /** cookie ex:key2=val2; key1=val1 */
        private Map<String, String> cookie;
        /** 携带参数(可使用于响应之后的操作) */
        private Map<String, Object> extra;
        /** 响应体 */
        private T body;
        /** 错误信息 */
        private String errorMessage;

        private Response() {}

        protected Response(Executor executor, BodyHandler<T> bodyHandler) {
            this.executor = executor;
            this.bodyHandler = bodyHandler;
            this.init();
        }

        protected static Response<Object> getErrorResponse(Request request, Throwable e) {
            Response<Object> errorResponse = new Response<>();
            errorResponse.http = null;
            errorResponse.url = request.getUrl();
            errorResponse.redirectUrlList = Collections.emptyList();
            errorResponse.statusCode = 400;
            errorResponse.header = Collections.emptyMap();
            errorResponse.cookie = Collections.emptyMap();
            errorResponse.extra = new HashMap<>(request.getExtra());
            errorResponse.body = null;
            errorResponse.errorMessage = Util.getThrowableStackTrace(e);
            return errorResponse;
        }

        private void init() {
            try {
                this.http = this.executor.getHttp();
                this.url = this.executor.getRequest().getUrl();
                this.redirectUrlList = this.executor.getRedirectUrlList();
                this.redirectUrlList = this.redirectUrlList == null ? Collections.emptyList() : this.redirectUrlList;
                this.statusCode = this.executor.getHttp().getResponseCode();
                this.header = this.executor.getHttp().getHeaderFields();
                this.cookie = this.parseCookieAsMap();
                this.extra = new HashMap<>(this.executor.getRequest().getExtra());
                this.handleHttpClientSession();
                if(this.bodyHandler != null) {
                    this.body = this.bodyHandler.accept(this.executor.getRequest(), this.http);
                }
            } catch(IOException e) {
                throw new RuntimeException(String.format("%s init HttpResponse has exception", this.url), e);
            } finally {
                this.http.disconnect();
            }
        }

        private void handleHttpClientSession() {
            Session session = this.executor.getRequest().getClient().session();
            session.setReferer(this.url);
            session.addCookie(this.cookie);
            session.addExtra(this.extra);
        }

        /** 获取 cookieMap */
        private Map<String, String> parseCookieAsMap() {
            List<String> cookieList = this.header.get(Constant.RESPONSE_COOKIE);
            Map<String, String> cookieMap = Collections.emptyMap();
            if(Util.isNotEmpty(cookieList)) {
                cookieMap = new HashMap<>(cookieList.size());
                if(Util.isNotEmpty(cookieList)) {
                    for(String cookieObj : cookieList) {
                        String[] split = cookieObj.split(Constant.COOKIE_SPLIT);
                        if(split.length > 0) {
                            String[] keyAndVal = split[0].split(Constant.EQU, 2);
                            cookieMap.put(keyAndVal[0], keyAndVal[1]);
                        }
                    }
                }
            }
            return cookieMap;
        }

        /* ---------------------------------------------- getter ---------------------------------------------- start */

        public String getUrl() {
            return this.url;
        }

        public List<String> getRedirectUrlList() {
            return this.redirectUrlList;
        }

        public HttpURLConnection getHttp() {
            return this.http;
        }

        public int getStatusCode() {
            return this.statusCode;
        }

        public Map<String, List<String>> getHeader() {
            return this.header;
        }

        public Map<String, String> getCookie() {
            return this.cookie;
        }

        public T getBody() {
            return this.body;
        }

        public String getErrorMessage() {
            return errorMessage;
        }

        public Map<String, Object> getExtra() {
            return this.extra;
        }

        /* ---------------------------------------------- getter ---------------------------------------------- end */

        /* ---------------------------------------------- toString -------------------------------------------- start */

        @Override
        public String toString() {
            final StringBuilder builder = new StringBuilder("Response{");
            builder.append("executor=").append(this.executor);
            builder.append(", bodyHandler=").append(this.bodyHandler);
            builder.append(", http=").append(this.http);
            builder.append(", url='").append(this.url).append('\'');
            builder.append(", redirectUrlList=").append(this.redirectUrlList);
            builder.append(", statusCode=").append(this.statusCode);
            builder.append(", header=").append(this.header);
            builder.append(", cookie=").append(this.cookie);
            builder.append(", extra=").append(this.extra);
            builder.append(", body=").append(this.body);
            builder.append('}');
            return builder.toString();
        }

        /* ---------------------------------------------- toString -------------------------------------------- end */
    }
    // =============================================== BodyHandler (响应处理接口) ================================================ //

    /**
     * 响应处理接口
     * @param <T> 响应体类型
     */
    public static interface BodyHandler<T> {

        /**
         * 回调接口
         * @param request Request 对象
         * @param http HttpURLConnection 对象
         * @return
         * @throws IOException
         */
        T accept(Request request, HttpURLConnection http) throws IOException;
    }
    // =============================================== CallbackByteArray (回调 byte[] 接口) ===================================== //

    /**
     * 回调 byte[] 接口
     */
    public static interface CallbackByteArray {

        /**
         * 回调 byte[] 方法
         * @param data byte[] 对象
         * @param index byte[] 的开始下标( 通常是0 )
         * @param length byte[] 结束下标
         */
        void accept(byte[] data, int index, int length) throws IOException;
    }
    // =============================================== BodyHandlers (响应处理工具) =============================================== //

    /**
     * 响应处理工具
     */
    public static abstract class BodyHandlers {

        private static BodyHandler<InputStream> ofInputStream() {
            return (request, http) -> {
                InputStream inputStream = http.getResponseCode() < 400 ? http.getInputStream() : http.getErrorStream();
                // 获取响应头是否有Content-Encoding=gzip
                String gzip = http.getHeaderField(Constant.CONTENT_ENCODING);
                if(Util.isNotEmpty(gzip) && gzip.contains(Constant.GZIP)) {
                    inputStream = new GZIPInputStream(inputStream);
                }
                return inputStream;
            };
        }

        public static BodyHandler<Void> ofCallbackByteArray(CallbackByteArray callback) {
            return (request, http) -> {
                try(InputStream inputStream = ofInputStream().accept(request, http)) {
                    byte[] bytes = new byte[1024 * 3];
                    for(int i; (i = inputStream.read(bytes)) > -1; ) {
                        callback.accept(bytes, 0, i);
                    }
                    return null;
                }
            };
        }

        public static BodyHandler<byte[]> ofByteArray() {
            return (request, http) -> {
                try(ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
                    ofCallbackByteArray((data, index, length) -> {
                        outputStream.write(data, index, length);
                        outputStream.flush();
                    }).accept(request, http);
                    return outputStream.toByteArray();
                }
            };
        }

        public static BodyHandler<String> ofString(Charset... charset) {
            return (request, http) -> {
                byte[] body = ofByteArray().accept(request, http);
                Charset currentCharset = charset != null && charset.length > 0 ? charset[0] : Constant.defaultCharset;
                return new String(body, currentCharset);
            };
        }

        public static BodyHandler<Path> ofFile(Path path) {
            return (request, http) -> {
                try(OutputStream outputStream = new FileOutputStream(path.toFile())) {
                    ofCallbackByteArray(outputStream::write).accept(request, http);
                    return path;
                }
            };
        }

    }
    // =============================================== Method (请求方法) ======================================================== //

    /**
     * 请求方法
     */
    public static enum Method {GET, POST, PUT, DELETE}
    // =============================================== Proxy (代理对象) ========================================================= //

    /**
     * 代理对象
     */
    public static class Proxy {

        /** 主机 */
        private String host;
        /** 端口 */
        private Integer port;
        /** 用户名 */
        private String username;
        /** 密码 */
        private String password;

        protected Proxy(String host, Integer port) {
            this(host, port, null, null);
        }

        protected Proxy(String host, Integer port, String username, String password) {
            this.host = host;
            this.port = port;
            this.username = username;
            this.password = password;
        }

        public static Proxy of(String host, Integer port) {
            return of(host, port, null, null);
        }

        public static Proxy of(String host, Integer port, String username, String password) {
            return new Proxy(host, port, username, password);
        }

        /* ---------------------------------------------- getter ---------------------------------------------- start */

        public String getHost() {
            return this.host;
        }

        public Integer getPort() {
            return this.port;
        }

        public String getUsername() {
            return this.username;
        }

        public String getPassword() {
            return this.password;
        }

        /* ---------------------------------------------- getter ---------------------------------------------- end   */

        /* ---------------------------------------------- toString -------------------------------------------- start */

        @Override
        public String toString() {
            final StringBuilder builder = new StringBuilder("Proxy{");
            builder.append("host='").append(this.host).append('\'');
            builder.append(", port=").append(this.port);
            builder.append(", username='").append(this.username).append('\'');
            builder.append(", password='").append(this.password).append('\'');
            builder.append('}');
            return builder.toString();
        }

        /* ---------------------------------------------- toString -------------------------------------------- end   */
    }
    // =============================================== Constant (常量) ========================================================== //

    /**
     * 常量
     */
    public static interface Constant {

        String CONTENT_LENGTH = "Content-Length";
        String CONTENT_TYPE = "Content-Type";
        /** 获取响应的COOKIE */
        String RESPONSE_COOKIE = "Set-Cookie";
        /** 设置发送的COOKIE */
        String REQUEST_COOKIE = "Cookie";
        String REFERER = "Referer";
        String PROXY_AUTHORIZATION = "Proxy-Authorization";
        String CONTENT_ENCODING = "Content-Encoding";
        String LOCATION = "Location";

        String CONTENT_TYPE_WITH_FORM = "application/x-www-form-urlencoded; charset=";
        String CONTENT_TYPE_WITH_FORM_DATA = "multipart/form-data; boundary=";
        String CONTENT_TYPE_WITH_JSON = "application/json; charset=";
        String CONTENT_TYPE_WITH_XML = "Content-Type,text/xml";
        String GZIP = "gzip";

        int REDIRECT_CODE_301 = 301;
        int REDIRECT_CODE_302 = 302;
        int REDIRECT_CODE_303 = 303;
        Set<Integer> REDIRECT_CODES = new HashSet<>(Arrays.asList(REDIRECT_CODE_301, REDIRECT_CODE_302, REDIRECT_CODE_303));

        String COOKIE_SPLIT = "; ";
        String EQU = "=";
        String HTTP = "http";
        String AND_SIGN = "&";
        String queryFlag = "?";

        Charset defaultCharset = Charset.defaultCharset();
    }
    // =============================================== Util (工具类) ============================================================ //

    /**
     * 工具类
     */
    public static abstract class Util {

        public static boolean isEmpty(Object o) {
            if(o == null) {
                return true;
            }
            if(o instanceof String) {
                return ((String) o).isEmpty();
            } else if(o instanceof Collection) {
                return ((Collection) o).isEmpty();
            } else if(o instanceof Map) {
                return ((Map) o).isEmpty();
            } else if(o instanceof Object[]) {
                return ((Object[]) o).length == 0;
            } else {
                return false;
            }
        }

        public static boolean isNotEmpty(Object o) {
            return !isEmpty(o);
        }

        public static <T> T emptyOfDefault(T t, T defaultValue) {
            return isEmpty(t) ? defaultValue : t;
        }

        public static <T> T nullOfDefault(T t, T defaultValue) {
            return t == null ? defaultValue : t;
        }

        /** url 编码 */
        public static String urlEncode(String text, Charset charset) {
            if(isNotEmpty(text) && isNotEmpty(charset)) {
                // 不为空 并且charset可用
                try {
                    return URLEncoder.encode(text, charset.name());
                } catch(UnsupportedEncodingException e) {
                    // do non thing
                }
            }
            return text;
        }

        /**
         * @description Map => key1=val1&key2=val2
         * @date 2019-08-20 20:42:59
         * @author houyu for.houyu@foxmail.com
         */
        public static String paramMapAsString(Map<String, Object> paramMap, Charset charset) {
            if(isNotEmpty(paramMap)) {
                StringBuilder builder = new StringBuilder(128);
                paramMap.forEach((k, v) -> {
                    // urlEncode : if charset is empty not do Encode
                    builder.append(urlEncode(k, charset));
                    builder.append(Constant.EQU);
                    builder.append(urlEncode(String.valueOf(v), charset));
                    builder.append(Constant.AND_SIGN);
                });
                return builder.delete(builder.length() - 1, builder.length()).toString();
            }
            return "";
        }

        /**
         * 把浏览器的Form字符串转为Map
         */
        public static Map<String, Object> parseFormStringAsMap(String s) {
            String[] split = s.split("\n");
            Map<String, Object> targetMap = new HashMap<>(split.length);
            for(String keyAndVal : split) {
                String[] keyVal = keyAndVal.split(": ", 2);
                targetMap.put(keyVal[0], keyVal[1]);
            }
            return targetMap;
        }

        /**
         * 获取错误的详细信息
         */
        @SuppressWarnings("Duplicates")
        public static String getThrowableStackTrace(Throwable e) {
            try (ByteArrayOutputStream arrayOutputStream = new ByteArrayOutputStream()) {
                e.printStackTrace(new PrintWriter(arrayOutputStream, true));
                return arrayOutputStream.toString();
            } catch(IOException e1) {
                e1.printStackTrace();
                return "";
            }
        }

    }
    /***/
    /** 内部类 end +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 内部类 end */
    /***/
}
import javax.servlet.http.HttpServletRequest;
import java.net.InetAddress;
import java.net.UnknownHostException;
/**
 * 获取用户真实ip地址
 */
public class AcquireIpUtils {

    public static String getIpaddress(HttpServletRequest request) {
        String ipAddress = null;
        try {
            ipAddress = request.getHeader("x-forwarded-for");
            if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
                ipAddress = request.getHeader("Proxy-Client-IP");
            }
            if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
                ipAddress = request.getHeader("WL-Proxy-Client-IP");
            }
            if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
                ipAddress = request.getRemoteAddr();
                if (ipAddress.equals("127.0.0.1")) {
                    // 根据网卡取本机配置的IP
                    InetAddress inet = null;
                    try {
                        inet = InetAddress.getLocalHost();
                    } catch (UnknownHostException e) {
                        e.printStackTrace();
                    }
                    ipAddress = inet.getHostAddress();
                }
            }
            // 对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割
            if (ipAddress != null && ipAddress.length() > 15) { // "***.***.***.***".length()
                // = 15
                if (ipAddress.indexOf(",") > 0) {
                    ipAddress = ipAddress.substring(0, ipAddress.indexOf(","));
                }
            }
        } catch (Exception e) {
            ipAddress="";
        }
        // ipAddress = this.getRequest().getRemoteAddr();
        return ipAddress;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Pursue?????

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值