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;
}
}