雅诗兰黛拆单报表、洲际酒店任务元素
parent
aa83695461
commit
7772b404dc
|
@ -1,9 +1,9 @@
|
|||
window.Utils = {
|
||||
class CusUtils {
|
||||
/**
|
||||
* @author youhong.ai
|
||||
* @desc 发起请求
|
||||
*/
|
||||
request: function (url, type = "GET", data, isAsync = true, success = () => {
|
||||
request(url, type = "GET", data, isAsync = true, success = () => {
|
||||
}, error = () => {
|
||||
}, complete = () => {
|
||||
}, contentType = 'application/json', beforeSend = () => {
|
||||
|
@ -24,13 +24,13 @@ window.Utils = {
|
|||
options.data = JSON.stringify(data)
|
||||
}
|
||||
return $.ajax(options)
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* @author youhong.ai
|
||||
* @desc 发起请求
|
||||
*/
|
||||
api: function (requestOptions = {
|
||||
api(requestOptions = {
|
||||
url: "",
|
||||
type: "GET",
|
||||
data: "",
|
||||
|
@ -61,13 +61,13 @@ window.Utils = {
|
|||
}
|
||||
}, requestOptions)
|
||||
return $.ajax(options)
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* @author youhong.ai
|
||||
* @desc 获取react组件实例
|
||||
*/
|
||||
findReact: function (dom, traverseUp = 0) {
|
||||
findReact(dom, traverseUp = 0) {
|
||||
const key = Object.keys(dom).find(key => {
|
||||
return key.startsWith("__reactFiber$") // react 17+
|
||||
|| key.startsWith("__reactInternalInstance$")
|
||||
|
@ -96,7 +96,7 @@ window.Utils = {
|
|||
compFiber = GetCompFiber(compFiber);
|
||||
}
|
||||
return compFiber.stateNode;
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
|
@ -104,7 +104,7 @@ window.Utils = {
|
|||
* @param fieldName 字段名称
|
||||
* @returns {*|string}
|
||||
*/
|
||||
convertNameToIdUtil: function (fieldName) {
|
||||
convertNameToIdUtil(fieldName) {
|
||||
let fieldIds = [];
|
||||
if (fieldName instanceof Array) {
|
||||
fieldName.forEach(item => {
|
||||
|
@ -113,19 +113,19 @@ window.Utils = {
|
|||
return fieldIds.join(',')
|
||||
}
|
||||
return Utils.convertNameObjToId(fieldName)
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* 将字段名称转为字段id
|
||||
* @param fieldObj 字段名称对象 {string|object}
|
||||
* @returns {*}
|
||||
*/
|
||||
convertNameObjToId: function (fieldObj = {fieldName: '', table: 'main'}) {
|
||||
convertNameObjToId(fieldObj = {fieldName: '', table: 'main'}) {
|
||||
if (typeof fieldObj === 'object') {
|
||||
return WfForm.convertFieldNameToId(fieldObj.fieldName, fieldObj.table)
|
||||
}
|
||||
return WfForm.convertFieldNameToId(fieldObj)
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据字段名称查询字段值
|
||||
|
@ -133,17 +133,18 @@ window.Utils = {
|
|||
* @param rowIndex 明细行下表(明细获取才传递)
|
||||
* @returns {*} 字段值
|
||||
*/
|
||||
getFiledValueByName: function (fieldName, rowIndex) {
|
||||
getFiledValueByName(fieldName, rowIndex) {
|
||||
return WfForm.getFieldValue(Utils.convertNameObjToId(fieldName) + (rowIndex ? '_' + rowIndex : ''))
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过字段名称修改字段值
|
||||
* @param fieldName 字段名称
|
||||
* @param value 值
|
||||
*/
|
||||
changeFieldValueByName: function (fieldName, value) {
|
||||
changeFieldValueByName(fieldName, value) {
|
||||
WfForm.changeFieldValue(Utils.convertNameObjToId(fieldName), {value})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
window.Utils = new CusUtils()
|
||||
|
|
|
@ -1,3 +1,10 @@
|
|||
const ecodeSDK = {}
|
||||
ecodeSDK.setCom = (id, name, Com) => {
|
||||
}
|
||||
ecodeSDK.imp = (obj) => {
|
||||
}
|
||||
ecodeSDK.exp = (obj) => {
|
||||
}
|
||||
const WfForm = {
|
||||
isMobile: () => {
|
||||
// true表示是eMobile、微信、钉钉等移动终端,false代表PC端
|
||||
|
|
|
@ -681,3 +681,61 @@ $(() => {
|
|||
})
|
||||
|
||||
/* ******************* 明细数据数量统计添加 end ******************* */
|
||||
|
||||
|
||||
/* ******************* 计算年月日 start ******************* */
|
||||
|
||||
$(() => {
|
||||
const config = [{
|
||||
// 源字段
|
||||
sourceField: '',
|
||||
// 目标字段
|
||||
targetField: '',
|
||||
// 日期加月份字段
|
||||
numberField: ''
|
||||
}, {
|
||||
sourceField: '',
|
||||
targetField: '',
|
||||
numberField: ''
|
||||
}]
|
||||
|
||||
runJs();
|
||||
|
||||
function runJs() {
|
||||
config.forEach(item => {
|
||||
bindAction(item)
|
||||
})
|
||||
}
|
||||
|
||||
function bindAction(configItem) {
|
||||
let fieldId = WfForm.convertFieldNameToId(configItem.numberField)
|
||||
WfForm.bindFieldChangeEvent(fieldId, (obj, id, value) => {
|
||||
if ("" == value) {
|
||||
WfForm.changeFieldValue(WfForm.convertFieldNameToId(configItem.targetField, {value: ""}))
|
||||
return
|
||||
}
|
||||
let sourceValue = WfForm.getFieldValue(WfForm.convertFieldNameToId(configItem.sourceField));
|
||||
let date = new Date(sourceValue)
|
||||
date.setMonth(date.getMonth() + +value)
|
||||
let objectDate = new Date();
|
||||
|
||||
|
||||
let day = objectDate.getDate();
|
||||
let month = objectDate.getMonth() + 1;
|
||||
let year = objectDate.getFullYear();
|
||||
WfForm.changeFieldValue(WfForm.convertFieldNameToId(configItem.targetField, {
|
||||
value: `${year}-${fullNum(month)}-${fullNum(day)}`
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
||||
function fullNum(i) {
|
||||
if (i <= 9) {
|
||||
return '0' + i
|
||||
} else {
|
||||
return i
|
||||
}
|
||||
}
|
||||
|
||||
})
|
||||
/* ******************* 计算年月日 end ******************* */
|
|
@ -0,0 +1,25 @@
|
|||
package aiyh.utils;
|
||||
|
||||
import aiyh.utils.tool.org.apache.commons.jexl3.*;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* <h1>脚本工具</h1>
|
||||
*
|
||||
* <p>create: 2023/3/3 23:03</p>
|
||||
*
|
||||
* @author youHong.ai
|
||||
*/
|
||||
public class ScriptUtil {
|
||||
private static final JexlEngine jexl = new JexlBuilder().create();
|
||||
|
||||
public static Object invokeScript(String script, Map<String, Object> params) {
|
||||
JexlContext jc = new MapContext();
|
||||
for (Map.Entry<String, Object> entry : params.entrySet()) {
|
||||
jc.set(entry.getKey(), entry.getValue());
|
||||
}
|
||||
JexlExpression expression = jexl.createExpression(script);
|
||||
return expression.evaluate(jc);
|
||||
}
|
||||
}
|
|
@ -3751,6 +3751,32 @@ public class Util extends weaver.general.Util {
|
|||
return pathParamMap;
|
||||
}
|
||||
|
||||
public static <T> T getClassInstance(String classPath, Class<T> t) {
|
||||
Class<?> aClass;
|
||||
try {
|
||||
aClass = Class.forName(classPath);
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw new IllegalArgumentException("未能找到自定义接口:" + classPath);
|
||||
}
|
||||
if (!t.isAssignableFrom(aClass)) {
|
||||
throw new IllegalArgumentException("自定义接口:" + classPath + " 不是"
|
||||
+ t.getName() + "的子类或实现类!");
|
||||
}
|
||||
Constructor<?> constructor;
|
||||
try {
|
||||
constructor = aClass.getConstructor();
|
||||
} catch (NoSuchMethodException e) {
|
||||
throw new IllegalArgumentException(classPath + "没有空参构造方法,无法获取构造方法对象!");
|
||||
}
|
||||
T o;
|
||||
try {
|
||||
o = (T) constructor.newInstance();
|
||||
} catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
|
||||
throw new IllegalArgumentException("无法构造" + classPath + "对象!");
|
||||
}
|
||||
return o;
|
||||
}
|
||||
|
||||
public static Object executeActionProcess(String process, RequestInfo requestInfo) {
|
||||
if (StringUtils.isNullOrEmpty(process)) {
|
||||
return null;
|
||||
|
|
|
@ -569,7 +569,9 @@ public class ResultMapper {
|
|||
cassociationValue = paramType.get(declaredField.getType()).apply(String.valueOf(cassociationValue));
|
||||
}
|
||||
try {
|
||||
if (Objects.nonNull(value)) {
|
||||
propertyDescriptor.getWriteMethod().invoke(o, cassociationValue);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Util.getLogger().error("实体数据写入报错:" + fieldName + " => " + value);
|
||||
if (value != null) {
|
||||
|
@ -584,7 +586,9 @@ public class ResultMapper {
|
|||
if (collectionMapping != null) {
|
||||
Object collection = collection(rs, collectionMapping, method);
|
||||
try {
|
||||
if (Objects.nonNull(value)) {
|
||||
propertyDescriptor.getWriteMethod().invoke(o, collection);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Util.getLogger().error("实体数据写入报错:" + fieldName + " => " + value);
|
||||
if (value != null) {
|
||||
|
|
|
@ -0,0 +1,161 @@
|
|||
package aiyh.utils.tool.cn.hutool.http;
|
||||
|
||||
import aiyh.utils.tool.cn.hutool.core.util.StrUtil;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
/**
|
||||
* 常用Content-Type类型枚举
|
||||
*
|
||||
* @author looly
|
||||
* @since 4.0.11
|
||||
*/
|
||||
public enum ContentType {
|
||||
|
||||
/**
|
||||
* 标准表单编码,当action为get时候,浏览器用x-www-form-urlencoded的编码方式把form数据转换成一个字串(name1=value1&name2=value2…)
|
||||
*/
|
||||
FORM_URLENCODED("application/x-www-form-urlencoded"),
|
||||
/**
|
||||
* 文件上传编码,浏览器会把整个表单以控件为单位分割,并为每个部分加上Content-Disposition,并加上分割符(boundary)
|
||||
*/
|
||||
MULTIPART("multipart/form-data"),
|
||||
/**
|
||||
* Rest请求JSON编码
|
||||
*/
|
||||
JSON("application/json"),
|
||||
/**
|
||||
* Rest请求XML编码
|
||||
*/
|
||||
XML("application/xml"),
|
||||
/**
|
||||
* text/plain编码
|
||||
*/
|
||||
TEXT_PLAIN("text/plain"),
|
||||
/**
|
||||
* Rest请求text/xml编码
|
||||
*/
|
||||
TEXT_XML("text/xml"),
|
||||
/**
|
||||
* text/html编码
|
||||
*/
|
||||
TEXT_HTML("text/html"),
|
||||
/**
|
||||
* application/octet-stream编码
|
||||
*/
|
||||
OCTET_STREAM("application/octet-stream");
|
||||
|
||||
private final String value;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param value ContentType值
|
||||
*/
|
||||
ContentType(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取value值
|
||||
*
|
||||
* @return value值
|
||||
* @since 5.2.6
|
||||
*/
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* 输出Content-Type字符串,附带编码信息
|
||||
*
|
||||
* @param charset 编码
|
||||
* @return Content-Type字符串
|
||||
*/
|
||||
public String toString(Charset charset) {
|
||||
return build(this.value, charset);
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否为默认Content-Type,默认包括{@code null}和application/x-www-form-urlencoded
|
||||
*
|
||||
* @param contentType 内容类型
|
||||
* @return 是否为默认Content-Type
|
||||
* @since 4.1.5
|
||||
*/
|
||||
public static boolean isDefault(String contentType) {
|
||||
return null == contentType || isFormUrlEncode(contentType);
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否为application/x-www-form-urlencoded
|
||||
*
|
||||
* @param contentType 内容类型
|
||||
* @return 是否为application/x-www-form-urlencoded
|
||||
*/
|
||||
public static boolean isFormUrlEncode(String contentType) {
|
||||
return StrUtil.startWithIgnoreCase(contentType, FORM_URLENCODED.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* 从请求参数的body中判断请求的Content-Type类型,支持的类型有:
|
||||
*
|
||||
* <pre>
|
||||
* 1. application/json
|
||||
* 1. application/xml
|
||||
* </pre>
|
||||
*
|
||||
* @param body 请求参数体
|
||||
* @return Content-Type类型,如果无法判断返回null
|
||||
*/
|
||||
public static ContentType get(String body) {
|
||||
ContentType contentType = null;
|
||||
if (StrUtil.isNotBlank(body)) {
|
||||
char firstChar = body.charAt(0);
|
||||
switch (firstChar) {
|
||||
case '{':
|
||||
case '[':
|
||||
// JSON请求体
|
||||
contentType = JSON;
|
||||
break;
|
||||
case '<':
|
||||
// XML请求体
|
||||
contentType = XML;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return contentType;
|
||||
}
|
||||
|
||||
/**
|
||||
* 输出Content-Type字符串,附带编码信息
|
||||
*
|
||||
* @param contentType Content-Type类型
|
||||
* @param charset 编码
|
||||
* @return Content-Type字符串
|
||||
* @since 4.5.4
|
||||
*/
|
||||
public static String build(String contentType, Charset charset) {
|
||||
return StrUtil.format("{};charset={}", contentType, charset.name());
|
||||
}
|
||||
|
||||
/**
|
||||
* 输出Content-Type字符串,附带编码信息
|
||||
*
|
||||
* @param contentType Content-Type 枚举类型
|
||||
* @param charset 编码
|
||||
* @return Content-Type字符串
|
||||
* @since 5.7.15
|
||||
*/
|
||||
public static String build(ContentType contentType, Charset charset) {
|
||||
return build(contentType.getValue(), charset);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,229 @@
|
|||
package aiyh.utils.tool.cn.hutool.http;
|
||||
|
||||
import aiyh.utils.tool.cn.hutool.core.collection.CollectionUtil;
|
||||
import aiyh.utils.tool.cn.hutool.core.map.MapUtil;
|
||||
import aiyh.utils.tool.cn.hutool.core.util.StrUtil;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
/**
|
||||
* 全局头部信息<br>
|
||||
* 所有Http请求将共用此全局头部信息,除非在{@link HttpRequest}中自定义头部信息覆盖之
|
||||
*
|
||||
* @author looly
|
||||
*/
|
||||
public enum GlobalHeaders {
|
||||
INSTANCE;
|
||||
|
||||
/**
|
||||
* 存储头信息
|
||||
*/
|
||||
final Map<String, List<String>> headers = new HashMap<>();
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*/
|
||||
GlobalHeaders() {
|
||||
putDefault(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 加入默认的头部信息
|
||||
*
|
||||
* @param isReset 是否重置所有头部信息(删除自定义保留默认)
|
||||
* @return this
|
||||
*/
|
||||
public GlobalHeaders putDefault(boolean isReset) {
|
||||
// 解决HttpURLConnection中无法自定义Host等头信息的问题
|
||||
// https://stackoverflow.com/questions/9096987/how-to-overwrite-http-header-host-in-a-httpurlconnection/9098440
|
||||
System.setProperty("sun.net.http.allowRestrictedHeaders", "true");
|
||||
|
||||
// 解决server certificate change is restricted during renegotiation问题
|
||||
System.setProperty("jdk.tls.allowUnsafeServerCertChange", "true");
|
||||
System.setProperty("sun.security.ssl.allowUnsafeRenegotiation", "true");
|
||||
|
||||
if (isReset) {
|
||||
this.headers.clear();
|
||||
}
|
||||
|
||||
header(aiyh.utils.tool.cn.hutool.http.Header.ACCEPT, "text/html,application/json,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", true);
|
||||
header(aiyh.utils.tool.cn.hutool.http.Header.ACCEPT_ENCODING, "gzip, deflate", true);
|
||||
header(aiyh.utils.tool.cn.hutool.http.Header.ACCEPT_LANGUAGE, "zh-CN,zh;q=0.8", true);
|
||||
// 此Header只有在post请求中有用,因此在HttpRequest的method方法中设置此头信息,此处去掉
|
||||
// header(Header.CONTENT_TYPE, ContentType.FORM_URLENCODED.toString(CharsetUtil.CHARSET_UTF_8), true);
|
||||
header(aiyh.utils.tool.cn.hutool.http.Header.USER_AGENT, "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36 Hutool", true);
|
||||
return this;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- Headers start
|
||||
|
||||
/**
|
||||
* 根据name获取头信息
|
||||
*
|
||||
* @param name Header名
|
||||
* @return Header值
|
||||
*/
|
||||
public String header(String name) {
|
||||
final List<String> values = headerList(name);
|
||||
if (CollectionUtil.isEmpty(values)) {
|
||||
return null;
|
||||
}
|
||||
return values.get(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据name获取头信息列表
|
||||
*
|
||||
* @param name Header名
|
||||
* @return Header值
|
||||
* @since 3.1.1
|
||||
*/
|
||||
public List<String> headerList(String name) {
|
||||
if (StrUtil.isBlank(name)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return headers.get(name.trim());
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据name获取头信息
|
||||
*
|
||||
* @param name Header名
|
||||
* @return Header值
|
||||
*/
|
||||
public String header(aiyh.utils.tool.cn.hutool.http.Header name) {
|
||||
if (null == name) {
|
||||
return null;
|
||||
}
|
||||
return header(name.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置一个header<br>
|
||||
* 如果覆盖模式,则替换之前的值,否则加入到值列表中
|
||||
*
|
||||
* @param name Header名
|
||||
* @param value Header值
|
||||
* @param isOverride 是否覆盖已有值
|
||||
* @return this
|
||||
*/
|
||||
synchronized public GlobalHeaders header(String name, String value, boolean isOverride) {
|
||||
if (null != name && null != value) {
|
||||
final List<String> values = headers.get(name.trim());
|
||||
if (isOverride || CollectionUtil.isEmpty(values)) {
|
||||
final ArrayList<String> valueList = new ArrayList<>();
|
||||
valueList.add(value);
|
||||
headers.put(name.trim(), valueList);
|
||||
} else {
|
||||
values.add(value.trim());
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置一个header<br>
|
||||
* 如果覆盖模式,则替换之前的值,否则加入到值列表中
|
||||
*
|
||||
* @param name Header名
|
||||
* @param value Header值
|
||||
* @param isOverride 是否覆盖已有值
|
||||
* @return this
|
||||
*/
|
||||
public GlobalHeaders header(aiyh.utils.tool.cn.hutool.http.Header name, String value, boolean isOverride) {
|
||||
return header(name.toString(), value, isOverride);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置一个header<br>
|
||||
* 覆盖模式,则替换之前的值
|
||||
*
|
||||
* @param name Header名
|
||||
* @param value Header值
|
||||
* @return this
|
||||
*/
|
||||
public GlobalHeaders header(aiyh.utils.tool.cn.hutool.http.Header name, String value) {
|
||||
return header(name.toString(), value, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置一个header<br>
|
||||
* 覆盖模式,则替换之前的值
|
||||
*
|
||||
* @param name Header名
|
||||
* @param value Header值
|
||||
* @return this
|
||||
*/
|
||||
public GlobalHeaders header(String name, String value) {
|
||||
return header(name, value, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置请求头<br>
|
||||
* 不覆盖原有请求头
|
||||
*
|
||||
* @param headers 请求头
|
||||
* @return this
|
||||
*/
|
||||
public GlobalHeaders header(Map<String, List<String>> headers) {
|
||||
if (MapUtil.isEmpty(headers)) {
|
||||
return this;
|
||||
}
|
||||
|
||||
String name;
|
||||
for (Entry<String, List<String>> entry : headers.entrySet()) {
|
||||
name = entry.getKey();
|
||||
for (String value : entry.getValue()) {
|
||||
this.header(name, StrUtil.nullToEmpty(value), false);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除一个头信息
|
||||
*
|
||||
* @param name Header名
|
||||
* @return this
|
||||
*/
|
||||
synchronized public GlobalHeaders removeHeader(String name) {
|
||||
if (name != null) {
|
||||
headers.remove(name.trim());
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除一个头信息
|
||||
*
|
||||
* @param name Header名
|
||||
* @return this
|
||||
*/
|
||||
public GlobalHeaders removeHeader(Header name) {
|
||||
return removeHeader(name.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取headers
|
||||
*
|
||||
* @return Headers Map
|
||||
*/
|
||||
public Map<String, List<String>> headers() {
|
||||
return Collections.unmodifiableMap(headers);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除所有头信息,包括全局头信息
|
||||
*
|
||||
* @return this
|
||||
* @since 5.7.13
|
||||
*/
|
||||
synchronized public GlobalHeaders clearHeaders() {
|
||||
this.headers.clear();
|
||||
return this;
|
||||
}
|
||||
// ---------------------------------------------------------------- Headers end
|
||||
|
||||
}
|
|
@ -0,0 +1,94 @@
|
|||
package aiyh.utils.tool.cn.hutool.http;
|
||||
|
||||
/**
|
||||
* 全局的拦截器<br>
|
||||
* 包括请求拦截器和响应拦截器
|
||||
*
|
||||
* @author looly
|
||||
* @since 5.8.0
|
||||
*/
|
||||
public enum GlobalInterceptor {
|
||||
INSTANCE;
|
||||
|
||||
private final HttpInterceptor.Chain<aiyh.utils.tool.cn.hutool.http.HttpRequest> requestInterceptors = new HttpInterceptor.Chain<>();
|
||||
private final HttpInterceptor.Chain<aiyh.utils.tool.cn.hutool.http.HttpResponse> responseInterceptors = new HttpInterceptor.Chain<>();
|
||||
|
||||
/**
|
||||
* 设置拦截器,用于在请求前重新编辑请求
|
||||
*
|
||||
* @param interceptor 拦截器实现
|
||||
* @return this
|
||||
*/
|
||||
synchronized public GlobalInterceptor addRequestInterceptor(HttpInterceptor<aiyh.utils.tool.cn.hutool.http.HttpRequest> interceptor) {
|
||||
this.requestInterceptors.addChain(interceptor);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置拦截器,用于在响应读取后完成编辑或读取
|
||||
*
|
||||
* @param interceptor 拦截器实现
|
||||
* @return this
|
||||
*/
|
||||
synchronized public GlobalInterceptor addResponseInterceptor(HttpInterceptor<aiyh.utils.tool.cn.hutool.http.HttpResponse> interceptor) {
|
||||
this.responseInterceptors.addChain(interceptor);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空请求和响应拦截器
|
||||
*
|
||||
* @return this
|
||||
*/
|
||||
public GlobalInterceptor clear() {
|
||||
clearRequest();
|
||||
clearResponse();
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空请求拦截器
|
||||
*
|
||||
* @return this
|
||||
*/
|
||||
synchronized public GlobalInterceptor clearRequest() {
|
||||
requestInterceptors.clear();
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空响应拦截器
|
||||
*
|
||||
* @return this
|
||||
*/
|
||||
synchronized public GlobalInterceptor clearResponse() {
|
||||
responseInterceptors.clear();
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 复制请求过滤器列表
|
||||
*
|
||||
* @return {@link HttpInterceptor.Chain}
|
||||
*/
|
||||
HttpInterceptor.Chain<aiyh.utils.tool.cn.hutool.http.HttpRequest> getCopiedRequestInterceptor() {
|
||||
final HttpInterceptor.Chain<aiyh.utils.tool.cn.hutool.http.HttpRequest> copied = new HttpInterceptor.Chain<>();
|
||||
for (HttpInterceptor<HttpRequest> interceptor : this.requestInterceptors) {
|
||||
copied.addChain(interceptor);
|
||||
}
|
||||
return copied;
|
||||
}
|
||||
|
||||
/**
|
||||
* 复制响应过滤器列表
|
||||
*
|
||||
* @return {@link HttpInterceptor.Chain}
|
||||
*/
|
||||
HttpInterceptor.Chain<aiyh.utils.tool.cn.hutool.http.HttpResponse> getCopiedResponseInterceptor() {
|
||||
final HttpInterceptor.Chain<aiyh.utils.tool.cn.hutool.http.HttpResponse> copied = new HttpInterceptor.Chain<>();
|
||||
for (HttpInterceptor<HttpResponse> interceptor : this.responseInterceptors) {
|
||||
copied.addChain(interceptor);
|
||||
}
|
||||
return copied;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,539 @@
|
|||
package aiyh.utils.tool.cn.hutool.http;
|
||||
|
||||
import aiyh.utils.tool.cn.hutool.core.lang.Console;
|
||||
import aiyh.utils.tool.cn.hutool.core.map.SafeConcurrentHashMap;
|
||||
import aiyh.utils.tool.cn.hutool.core.util.CharUtil;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* HTML过滤器,用于去除XSS(Cross Site Scripting) 漏洞隐患。
|
||||
*
|
||||
* <p>
|
||||
* 此类中的方法非线程安全
|
||||
* </p>
|
||||
*
|
||||
* <pre>
|
||||
* String clean = new HTMLFilter().filter(input);
|
||||
* </pre>
|
||||
* <p>
|
||||
* 此类来自:http://xss-html-filter.sf.net
|
||||
*
|
||||
* @author Joseph O'Connell
|
||||
* @author Cal Hendersen
|
||||
* @author Michael Semb Wever
|
||||
*/
|
||||
public final class HTMLFilter {
|
||||
|
||||
/**
|
||||
* regex flag union representing /si modifiers in php
|
||||
**/
|
||||
private static final int REGEX_FLAGS_SI = Pattern.CASE_INSENSITIVE | Pattern.DOTALL;
|
||||
private static final Pattern P_COMMENTS = Pattern.compile("<!--(.*?)-->", Pattern.DOTALL);
|
||||
private static final Pattern P_COMMENT = Pattern.compile("^!--(.*)--$", REGEX_FLAGS_SI);
|
||||
private static final Pattern P_TAGS = Pattern.compile("<(.*?)>", Pattern.DOTALL);
|
||||
private static final Pattern P_END_TAG = Pattern.compile("^/([a-z0-9]+)", REGEX_FLAGS_SI);
|
||||
private static final Pattern P_START_TAG = Pattern.compile("^([a-z0-9]+)(.*?)(/?)$", REGEX_FLAGS_SI);
|
||||
private static final Pattern P_QUOTED_ATTRIBUTES = Pattern.compile("([a-z0-9]+)=([\"'])(.*?)\\2", REGEX_FLAGS_SI);
|
||||
private static final Pattern P_UNQUOTED_ATTRIBUTES = Pattern.compile("([a-z0-9]+)(=)([^\"\\s']+)", REGEX_FLAGS_SI);
|
||||
private static final Pattern P_PROTOCOL = Pattern.compile("^([^:]+):", REGEX_FLAGS_SI);
|
||||
private static final Pattern P_ENTITY = Pattern.compile("&#(\\d+);?");
|
||||
private static final Pattern P_ENTITY_UNICODE = Pattern.compile("&#x([0-9a-f]+);?");
|
||||
private static final Pattern P_ENCODE = Pattern.compile("%([0-9a-f]{2});?");
|
||||
private static final Pattern P_VALID_ENTITIES = Pattern.compile("&([^&;]*)(?=(;|&|$))");
|
||||
private static final Pattern P_VALID_QUOTES = Pattern.compile("(>|^)([^<]+?)(<|$)", Pattern.DOTALL);
|
||||
private static final Pattern P_END_ARROW = Pattern.compile("^>");
|
||||
private static final Pattern P_BODY_TO_END = Pattern.compile("<([^>]*?)(?=<|$)");
|
||||
private static final Pattern P_XML_CONTENT = Pattern.compile("(^|>)([^<]*?)(?=>)");
|
||||
private static final Pattern P_STRAY_LEFT_ARROW = Pattern.compile("<([^>]*?)(?=<|$)");
|
||||
private static final Pattern P_STRAY_RIGHT_ARROW = Pattern.compile("(^|>)([^<]*?)(?=>)");
|
||||
private static final Pattern P_AMP = Pattern.compile("&");
|
||||
private static final Pattern P_QUOTE = Pattern.compile("\"");
|
||||
private static final Pattern P_LEFT_ARROW = Pattern.compile("<");
|
||||
private static final Pattern P_RIGHT_ARROW = Pattern.compile(">");
|
||||
private static final Pattern P_BOTH_ARROWS = Pattern.compile("<>");
|
||||
|
||||
// @xxx could grow large... maybe use sesat's ReferenceMap
|
||||
private static final ConcurrentMap<String, Pattern> P_REMOVE_PAIR_BLANKS = new SafeConcurrentHashMap<>();
|
||||
private static final ConcurrentMap<String, Pattern> P_REMOVE_SELF_BLANKS = new SafeConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* set of allowed html elements, along with allowed attributes for each element
|
||||
**/
|
||||
private final Map<String, List<String>> vAllowed;
|
||||
/**
|
||||
* counts of open tags for each (allowable) html element
|
||||
**/
|
||||
private final Map<String, Integer> vTagCounts = new HashMap<>();
|
||||
|
||||
/**
|
||||
* html elements which must always be self-closing (e.g. "<img />")
|
||||
**/
|
||||
private final String[] vSelfClosingTags;
|
||||
/**
|
||||
* html elements which must always have separate opening and closing tags (e.g. "<b></b>")
|
||||
**/
|
||||
private final String[] vNeedClosingTags;
|
||||
/**
|
||||
* set of disallowed html elements
|
||||
**/
|
||||
private final String[] vDisallowed;
|
||||
/**
|
||||
* attributes which should be checked for valid protocols
|
||||
**/
|
||||
private final String[] vProtocolAtts;
|
||||
/**
|
||||
* allowed protocols
|
||||
**/
|
||||
private final String[] vAllowedProtocols;
|
||||
/**
|
||||
* tags which should be removed if they contain no content (e.g. "<b></b>" or "<b />")
|
||||
**/
|
||||
private final String[] vRemoveBlanks;
|
||||
/**
|
||||
* entities allowed within html markup
|
||||
**/
|
||||
private final String[] vAllowedEntities;
|
||||
/**
|
||||
* flag determining whether comments are allowed in input String.
|
||||
*/
|
||||
private final boolean stripComment;
|
||||
private final boolean encodeQuotes;
|
||||
private boolean vDebug = false;
|
||||
/**
|
||||
* flag determining whether to try to make tags when presented with "unbalanced" angle brackets (e.g. "<b text </b>" becomes "<b> text </g>").
|
||||
* If set to false, unbalanced angle brackets will be
|
||||
* html escaped.
|
||||
*/
|
||||
private final boolean alwaysMakeTags;
|
||||
|
||||
/**
|
||||
* Default constructor.
|
||||
*/
|
||||
public HTMLFilter() {
|
||||
vAllowed = new HashMap<>();
|
||||
|
||||
final ArrayList<String> a_atts = new ArrayList<>();
|
||||
a_atts.add("href");
|
||||
a_atts.add("target");
|
||||
vAllowed.put("a", a_atts);
|
||||
|
||||
final ArrayList<String> img_atts = new ArrayList<>();
|
||||
img_atts.add("src");
|
||||
img_atts.add("width");
|
||||
img_atts.add("height");
|
||||
img_atts.add("alt");
|
||||
vAllowed.put("img", img_atts);
|
||||
|
||||
final ArrayList<String> no_atts = new ArrayList<>();
|
||||
vAllowed.put("b", no_atts);
|
||||
vAllowed.put("strong", no_atts);
|
||||
vAllowed.put("i", no_atts);
|
||||
vAllowed.put("em", no_atts);
|
||||
|
||||
vSelfClosingTags = new String[]{"img"};
|
||||
vNeedClosingTags = new String[]{"a", "b", "strong", "i", "em"};
|
||||
vDisallowed = new String[]{};
|
||||
vAllowedProtocols = new String[]{"http", "mailto", "https"}; // no ftp.
|
||||
vProtocolAtts = new String[]{"src", "href"};
|
||||
vRemoveBlanks = new String[]{"a", "b", "strong", "i", "em"};
|
||||
vAllowedEntities = new String[]{"amp", "gt", "lt", "quot"};
|
||||
stripComment = true;
|
||||
encodeQuotes = true;
|
||||
alwaysMakeTags = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set debug flag to true. Otherwise use default settings. See the default constructor.
|
||||
*
|
||||
* @param debug turn debug on with a true argument
|
||||
*/
|
||||
public HTMLFilter(final boolean debug) {
|
||||
this();
|
||||
vDebug = debug;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Map-parameter configurable constructor.
|
||||
*
|
||||
* @param conf map containing configuration. keys match field names.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public HTMLFilter(final Map<String, Object> conf) {
|
||||
|
||||
assert conf.containsKey("vAllowed") : "configuration requires vAllowed";
|
||||
assert conf.containsKey("vSelfClosingTags") : "configuration requires vSelfClosingTags";
|
||||
assert conf.containsKey("vNeedClosingTags") : "configuration requires vNeedClosingTags";
|
||||
assert conf.containsKey("vDisallowed") : "configuration requires vDisallowed";
|
||||
assert conf.containsKey("vAllowedProtocols") : "configuration requires vAllowedProtocols";
|
||||
assert conf.containsKey("vProtocolAtts") : "configuration requires vProtocolAtts";
|
||||
assert conf.containsKey("vRemoveBlanks") : "configuration requires vRemoveBlanks";
|
||||
assert conf.containsKey("vAllowedEntities") : "configuration requires vAllowedEntities";
|
||||
|
||||
vAllowed = Collections.unmodifiableMap((HashMap<String, List<String>>) conf.get("vAllowed"));
|
||||
vSelfClosingTags = (String[]) conf.get("vSelfClosingTags");
|
||||
vNeedClosingTags = (String[]) conf.get("vNeedClosingTags");
|
||||
vDisallowed = (String[]) conf.get("vDisallowed");
|
||||
vAllowedProtocols = (String[]) conf.get("vAllowedProtocols");
|
||||
vProtocolAtts = (String[]) conf.get("vProtocolAtts");
|
||||
vRemoveBlanks = (String[]) conf.get("vRemoveBlanks");
|
||||
vAllowedEntities = (String[]) conf.get("vAllowedEntities");
|
||||
stripComment = conf.containsKey("stripComment") ? (Boolean) conf.get("stripComment") : true;
|
||||
encodeQuotes = conf.containsKey("encodeQuotes") ? (Boolean) conf.get("encodeQuotes") : true;
|
||||
alwaysMakeTags = conf.containsKey("alwaysMakeTags") ? (Boolean) conf.get("alwaysMakeTags") : true;
|
||||
}
|
||||
|
||||
private void reset() {
|
||||
vTagCounts.clear();
|
||||
}
|
||||
|
||||
private void debug(final String msg) {
|
||||
if (vDebug) {
|
||||
Console.log(msg);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
// my versions of some PHP library functions
|
||||
public static String chr(final int decimal) {
|
||||
return String.valueOf((char) decimal);
|
||||
}
|
||||
|
||||
public static String htmlSpecialChars(final String s) {
|
||||
String result = s;
|
||||
result = regexReplace(P_AMP, "&", result);
|
||||
result = regexReplace(P_QUOTE, """, result);
|
||||
result = regexReplace(P_LEFT_ARROW, "<", result);
|
||||
result = regexReplace(P_RIGHT_ARROW, ">", result);
|
||||
return result;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* given a user submitted input String, filter out any invalid or restricted html.
|
||||
*
|
||||
* @param input text (i.e. submitted by a user) than may contain html
|
||||
* @return "clean" version of input, with only valid, whitelisted html elements allowed
|
||||
*/
|
||||
public String filter(final String input) {
|
||||
reset();
|
||||
String s = input;
|
||||
|
||||
debug("************************************************");
|
||||
debug(" INPUT: " + input);
|
||||
|
||||
s = escapeComments(s);
|
||||
debug(" escapeComments: " + s);
|
||||
|
||||
s = balanceHTML(s);
|
||||
debug(" balanceHTML: " + s);
|
||||
|
||||
s = checkTags(s);
|
||||
debug(" checkTags: " + s);
|
||||
|
||||
s = processRemoveBlanks(s);
|
||||
debug("processRemoveBlanks: " + s);
|
||||
|
||||
s = validateEntities(s);
|
||||
debug(" validateEntites: " + s);
|
||||
|
||||
debug("************************************************\n\n");
|
||||
return s;
|
||||
}
|
||||
|
||||
public boolean isAlwaysMakeTags() {
|
||||
return alwaysMakeTags;
|
||||
}
|
||||
|
||||
public boolean isStripComments() {
|
||||
return stripComment;
|
||||
}
|
||||
|
||||
private String escapeComments(final String s) {
|
||||
final Matcher m = P_COMMENTS.matcher(s);
|
||||
final StringBuffer buf = new StringBuffer();
|
||||
if (m.find()) {
|
||||
final String match = m.group(1); // (.*?)
|
||||
m.appendReplacement(buf, Matcher.quoteReplacement("<!--" + htmlSpecialChars(match) + "-->"));
|
||||
}
|
||||
m.appendTail(buf);
|
||||
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
private String balanceHTML(String s) {
|
||||
if (alwaysMakeTags) {
|
||||
//
|
||||
// try and form html
|
||||
//
|
||||
s = regexReplace(P_END_ARROW, "", s);
|
||||
s = regexReplace(P_BODY_TO_END, "<$1>", s);
|
||||
s = regexReplace(P_XML_CONTENT, "$1<$2", s);
|
||||
|
||||
} else {
|
||||
//
|
||||
// escape stray brackets
|
||||
//
|
||||
s = regexReplace(P_STRAY_LEFT_ARROW, "<$1", s);
|
||||
s = regexReplace(P_STRAY_RIGHT_ARROW, "$1$2><", s);
|
||||
|
||||
//
|
||||
// the last regexp causes '<>' entities to appear
|
||||
// (we need to do a lookahead assertion so that the last bracket can
|
||||
// be used in the next pass of the regexp)
|
||||
//
|
||||
s = regexReplace(P_BOTH_ARROWS, "", s);
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
private String checkTags(String s) {
|
||||
Matcher m = P_TAGS.matcher(s);
|
||||
|
||||
final StringBuffer buf = new StringBuffer();
|
||||
while (m.find()) {
|
||||
String replaceStr = m.group(1);
|
||||
replaceStr = processTag(replaceStr);
|
||||
m.appendReplacement(buf, Matcher.quoteReplacement(replaceStr));
|
||||
}
|
||||
m.appendTail(buf);
|
||||
|
||||
// these get tallied in processTag
|
||||
// (remember to reset before subsequent calls to filter method)
|
||||
final StringBuilder sBuilder = new StringBuilder(buf.toString());
|
||||
for (String key : vTagCounts.keySet()) {
|
||||
for (int ii = 0; ii < vTagCounts.get(key); ii++) {
|
||||
sBuilder.append("</").append(key).append(">");
|
||||
}
|
||||
}
|
||||
s = sBuilder.toString();
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
private String processRemoveBlanks(final String s) {
|
||||
String result = s;
|
||||
for (String tag : vRemoveBlanks) {
|
||||
if (!P_REMOVE_PAIR_BLANKS.containsKey(tag)) {
|
||||
P_REMOVE_PAIR_BLANKS.putIfAbsent(tag, Pattern.compile("<" + tag + "(\\s[^>]*)?></" + tag + ">"));
|
||||
}
|
||||
result = regexReplace(P_REMOVE_PAIR_BLANKS.get(tag), "", result);
|
||||
if (!P_REMOVE_SELF_BLANKS.containsKey(tag)) {
|
||||
P_REMOVE_SELF_BLANKS.putIfAbsent(tag, Pattern.compile("<" + tag + "(\\s[^>]*)?/>"));
|
||||
}
|
||||
result = regexReplace(P_REMOVE_SELF_BLANKS.get(tag), "", result);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static String regexReplace(final Pattern regex_pattern, final String replacement, final String s) {
|
||||
Matcher m = regex_pattern.matcher(s);
|
||||
return m.replaceAll(replacement);
|
||||
}
|
||||
|
||||
private String processTag(final String s) {
|
||||
// ending tags
|
||||
Matcher m = P_END_TAG.matcher(s);
|
||||
if (m.find()) {
|
||||
final String name = m.group(1).toLowerCase();
|
||||
if (allowed(name)) {
|
||||
if (!inArray(name, vSelfClosingTags)) {
|
||||
if (vTagCounts.containsKey(name)) {
|
||||
vTagCounts.put(name, vTagCounts.get(name) - 1);
|
||||
return "</" + name + ">";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// starting tags
|
||||
m = P_START_TAG.matcher(s);
|
||||
if (m.find()) {
|
||||
final String name = m.group(1).toLowerCase();
|
||||
final String body = m.group(2);
|
||||
String ending = m.group(3);
|
||||
|
||||
// debug( "in a starting tag, name='" + name + "'; body='" + body + "'; ending='" + ending + "'" );
|
||||
if (allowed(name)) {
|
||||
final StringBuilder params = new StringBuilder();
|
||||
|
||||
final Matcher m2 = P_QUOTED_ATTRIBUTES.matcher(body);
|
||||
final Matcher m3 = P_UNQUOTED_ATTRIBUTES.matcher(body);
|
||||
final List<String> paramNames = new ArrayList<>();
|
||||
final List<String> paramValues = new ArrayList<>();
|
||||
while (m2.find()) {
|
||||
paramNames.add(m2.group(1)); // ([a-z0-9]+)
|
||||
paramValues.add(m2.group(3)); // (.*?)
|
||||
}
|
||||
while (m3.find()) {
|
||||
paramNames.add(m3.group(1)); // ([a-z0-9]+)
|
||||
paramValues.add(m3.group(3)); // ([^\"\\s']+)
|
||||
}
|
||||
|
||||
String paramName, paramValue;
|
||||
for (int ii = 0; ii < paramNames.size(); ii++) {
|
||||
paramName = paramNames.get(ii).toLowerCase();
|
||||
paramValue = paramValues.get(ii);
|
||||
|
||||
// debug( "paramName='" + paramName + "'" );
|
||||
// debug( "paramValue='" + paramValue + "'" );
|
||||
// debug( "allowed? " + vAllowed.get( name ).contains( paramName ) );
|
||||
|
||||
if (allowedAttribute(name, paramName)) {
|
||||
if (inArray(paramName, vProtocolAtts)) {
|
||||
paramValue = processParamProtocol(paramValue);
|
||||
}
|
||||
params.append(CharUtil.SPACE).append(paramName).append("=\"").append(paramValue).append("\"");
|
||||
}
|
||||
}
|
||||
|
||||
if (inArray(name, vSelfClosingTags)) {
|
||||
ending = " /";
|
||||
}
|
||||
|
||||
if (inArray(name, vNeedClosingTags)) {
|
||||
ending = "";
|
||||
}
|
||||
|
||||
if (ending == null || ending.length() < 1) {
|
||||
if (vTagCounts.containsKey(name)) {
|
||||
vTagCounts.put(name, vTagCounts.get(name) + 1);
|
||||
} else {
|
||||
vTagCounts.put(name, 1);
|
||||
}
|
||||
} else {
|
||||
ending = " /";
|
||||
}
|
||||
return "<" + name + params + ending + ">";
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
// comments
|
||||
m = P_COMMENT.matcher(s);
|
||||
if (!stripComment && m.find()) {
|
||||
return "<" + m.group() + ">";
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
private String processParamProtocol(String s) {
|
||||
s = decodeEntities(s);
|
||||
final Matcher m = P_PROTOCOL.matcher(s);
|
||||
if (m.find()) {
|
||||
final String protocol = m.group(1);
|
||||
if (!inArray(protocol, vAllowedProtocols)) {
|
||||
// bad protocol, turn into local anchor link instead
|
||||
s = "#" + s.substring(protocol.length() + 1);
|
||||
if (s.startsWith("#//")) {
|
||||
s = "#" + s.substring(3);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
private String decodeEntities(String s) {
|
||||
StringBuffer buf = new StringBuffer();
|
||||
|
||||
Matcher m = P_ENTITY.matcher(s);
|
||||
while (m.find()) {
|
||||
final String match = m.group(1);
|
||||
final int decimal = Integer.decode(match);
|
||||
m.appendReplacement(buf, Matcher.quoteReplacement(chr(decimal)));
|
||||
}
|
||||
m.appendTail(buf);
|
||||
s = buf.toString();
|
||||
|
||||
buf = new StringBuffer();
|
||||
m = P_ENTITY_UNICODE.matcher(s);
|
||||
while (m.find()) {
|
||||
final String match = m.group(1);
|
||||
final int decimal = Integer.parseInt(match, 16);
|
||||
m.appendReplacement(buf, Matcher.quoteReplacement(chr(decimal)));
|
||||
}
|
||||
m.appendTail(buf);
|
||||
s = buf.toString();
|
||||
|
||||
buf = new StringBuffer();
|
||||
m = P_ENCODE.matcher(s);
|
||||
while (m.find()) {
|
||||
final String match = m.group(1);
|
||||
final int decimal = Integer.parseInt(match, 16);
|
||||
m.appendReplacement(buf, Matcher.quoteReplacement(chr(decimal)));
|
||||
}
|
||||
m.appendTail(buf);
|
||||
s = buf.toString();
|
||||
|
||||
s = validateEntities(s);
|
||||
return s;
|
||||
}
|
||||
|
||||
private String validateEntities(final String s) {
|
||||
StringBuffer buf = new StringBuffer();
|
||||
|
||||
// validate entities throughout the string
|
||||
Matcher m = P_VALID_ENTITIES.matcher(s);
|
||||
while (m.find()) {
|
||||
final String one = m.group(1); // ([^&;]*)
|
||||
final String two = m.group(2); // (?=(;|&|$))
|
||||
m.appendReplacement(buf, Matcher.quoteReplacement(checkEntity(one, two)));
|
||||
}
|
||||
m.appendTail(buf);
|
||||
|
||||
return encodeQuotes(buf.toString());
|
||||
}
|
||||
|
||||
private String encodeQuotes(final String s) {
|
||||
if (encodeQuotes) {
|
||||
StringBuffer buf = new StringBuffer();
|
||||
Matcher m = P_VALID_QUOTES.matcher(s);
|
||||
while (m.find()) {
|
||||
final String one = m.group(1); // (>|^)
|
||||
final String two = m.group(2); // ([^<]+?)
|
||||
final String three = m.group(3); // (<|$)
|
||||
m.appendReplacement(buf, Matcher.quoteReplacement(one + regexReplace(P_QUOTE, """, two) + three));
|
||||
}
|
||||
m.appendTail(buf);
|
||||
return buf.toString();
|
||||
} else {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
private String checkEntity(final String preamble, final String term) {
|
||||
|
||||
return ";".equals(term) && isValidEntity(preamble) ? '&' + preamble : "&" + preamble;
|
||||
}
|
||||
|
||||
private boolean isValidEntity(final String entity) {
|
||||
return inArray(entity, vAllowedEntities);
|
||||
}
|
||||
|
||||
private static boolean inArray(final String s, final String[] array) {
|
||||
for (String item : array) {
|
||||
if (item != null && item.equals(s)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean allowed(final String name) {
|
||||
return (vAllowed.isEmpty() || vAllowed.containsKey(name)) && !inArray(name, vDisallowed);
|
||||
}
|
||||
|
||||
private boolean allowedAttribute(final String name, final String paramName) {
|
||||
return allowed(name) && (vAllowed.isEmpty() || vAllowed.get(name).contains(paramName));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,153 @@
|
|||
package aiyh.utils.tool.cn.hutool.http;
|
||||
|
||||
/**
|
||||
* Http 头域
|
||||
*
|
||||
* @author Looly
|
||||
*/
|
||||
public enum Header {
|
||||
|
||||
//------------------------------------------------------------- 通用头域
|
||||
/**
|
||||
* 提供验证头,例如:
|
||||
* <pre>
|
||||
* Authorization: Basic YWxhZGRpbjpvcGVuc2VzYW1l
|
||||
* </pre>
|
||||
*/
|
||||
AUTHORIZATION("Authorization"),
|
||||
/**
|
||||
* 提供给代理服务器的用于身份验证的凭证,例如:
|
||||
* <pre>
|
||||
* Proxy-Authorization: Basic YWxhZGRpbjpvcGVuc2VzYW1l
|
||||
* </pre>
|
||||
*/
|
||||
PROXY_AUTHORIZATION("Proxy-Authorization"),
|
||||
/**
|
||||
* 提供日期和时间标志,说明报文是什么时间创建的
|
||||
*/
|
||||
DATE("Date"),
|
||||
/**
|
||||
* 允许客户端和服务器指定与请求/响应连接有关的选项
|
||||
*/
|
||||
CONNECTION("Connection"),
|
||||
/**
|
||||
* 给出发送端使用的MIME版本
|
||||
*/
|
||||
MIME_VERSION("MIME-Version"),
|
||||
/**
|
||||
* 如果报文采用了分块传输编码(chunked transfer encoding) 方式,就可以用这个首部列出位于报文拖挂(trailer)部分的首部集合
|
||||
*/
|
||||
TRAILER("Trailer"),
|
||||
/**
|
||||
* 告知接收端为了保证报文的可靠传输,对报文采用了什么编码方式
|
||||
*/
|
||||
TRANSFER_ENCODING("Transfer-Encoding"),
|
||||
/**
|
||||
* 给出了发送端可能想要"升级"使用的新版本和协议
|
||||
*/
|
||||
UPGRADE("Upgrade"),
|
||||
/**
|
||||
* 显示了报文经过的中间节点
|
||||
*/
|
||||
VIA("Via"),
|
||||
/**
|
||||
* 指定请求和响应遵循的缓存机制
|
||||
*/
|
||||
CACHE_CONTROL("Cache-Control"),
|
||||
/**
|
||||
* 用来包含实现特定的指令,最常用的是Pragma:no-cache。在HTTP/1.1协议中,它的含义和Cache- Control:no-cache相同
|
||||
*/
|
||||
PRAGMA("Pragma"),
|
||||
/**
|
||||
* 请求表示提交内容类型或返回返回内容的MIME类型
|
||||
*/
|
||||
CONTENT_TYPE("Content-Type"),
|
||||
|
||||
//------------------------------------------------------------- 请求头域
|
||||
/**
|
||||
* 指定请求资源的Intenet主机和端口号,必须表示请求url的原始服务器或网关的位置。HTTP/1.1请求必须包含主机头域,否则系统会以400状态码返回
|
||||
*/
|
||||
HOST("Host"),
|
||||
/**
|
||||
* 允许客户端指定请求uri的源资源地址,这可以允许服务器生成回退链表,可用来登陆、优化cache等。他也允许废除的或错误的连接由于维护的目的被 追踪。如果请求的uri没有自己的uri地址,Referer不能被发送。如果指定的是部分uri地址,则此地址应该是一个相对地址
|
||||
*/
|
||||
REFERER("Referer"),
|
||||
/**
|
||||
* 指定请求的域
|
||||
*/
|
||||
ORIGIN("Origin"),
|
||||
/**
|
||||
* HTTP客户端运行的浏览器类型的详细信息。通过该头部信息,web服务器可以判断到当前HTTP请求的客户端浏览器类别
|
||||
*/
|
||||
USER_AGENT("User-Agent"),
|
||||
/**
|
||||
* 指定客户端能够接收的内容类型,内容类型中的先后次序表示客户端接收的先后次序
|
||||
*/
|
||||
ACCEPT("Accept"),
|
||||
/**
|
||||
* 指定HTTP客户端浏览器用来展示返回信息所优先选择的语言
|
||||
*/
|
||||
ACCEPT_LANGUAGE("Accept-Language"),
|
||||
/**
|
||||
* 指定客户端浏览器可以支持的web服务器返回内容压缩编码类型
|
||||
*/
|
||||
ACCEPT_ENCODING("Accept-Encoding"),
|
||||
/**
|
||||
* 浏览器可以接受的字符编码集
|
||||
*/
|
||||
ACCEPT_CHARSET("Accept-Charset"),
|
||||
/**
|
||||
* HTTP请求发送时,会把保存在该请求域名下的所有cookie值一起发送给web服务器
|
||||
*/
|
||||
COOKIE("Cookie"),
|
||||
/**
|
||||
* 请求的内容长度
|
||||
*/
|
||||
CONTENT_LENGTH("Content-Length"),
|
||||
|
||||
//------------------------------------------------------------- 响应头域
|
||||
/**
|
||||
* 提供WWW验证响应头
|
||||
*/
|
||||
WWW_AUTHENTICATE("WWW-Authenticate"),
|
||||
/**
|
||||
* Cookie
|
||||
*/
|
||||
SET_COOKIE("Set-Cookie"),
|
||||
/**
|
||||
* Content-Encoding
|
||||
*/
|
||||
CONTENT_ENCODING("Content-Encoding"),
|
||||
/**
|
||||
* Content-Disposition
|
||||
*/
|
||||
CONTENT_DISPOSITION("Content-Disposition"),
|
||||
/**
|
||||
* ETag
|
||||
*/
|
||||
ETAG("ETag"),
|
||||
/**
|
||||
* 重定向指示到的URL
|
||||
*/
|
||||
LOCATION("Location");
|
||||
|
||||
private final String value;
|
||||
|
||||
Header(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取值
|
||||
*
|
||||
* @return 值
|
||||
*/
|
||||
public String getValue() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getValue();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,212 @@
|
|||
package aiyh.utils.tool.cn.hutool.http;
|
||||
|
||||
import aiyh.utils.tool.cn.hutool.core.util.EscapeUtil;
|
||||
import aiyh.utils.tool.cn.hutool.core.util.ReUtil;
|
||||
import aiyh.utils.tool.cn.hutool.core.util.StrUtil;
|
||||
|
||||
/**
|
||||
* HTML工具类
|
||||
*
|
||||
* <p>
|
||||
* 比如我们在使用爬虫爬取HTML页面后,需要对返回页面的HTML内容做一定处理,<br>
|
||||
* 比如去掉指定标签(例如广告栏等)、去除JS、去掉样式等等,这些操作都可以使用此工具类完成。
|
||||
*
|
||||
* @author xiaoleilu
|
||||
*/
|
||||
public class HtmlUtil {
|
||||
|
||||
public static final String NBSP = StrUtil.HTML_NBSP;
|
||||
public static final String AMP = StrUtil.HTML_AMP;
|
||||
public static final String QUOTE = StrUtil.HTML_QUOTE;
|
||||
public static final String APOS = StrUtil.HTML_APOS;
|
||||
public static final String LT = StrUtil.HTML_LT;
|
||||
public static final String GT = StrUtil.HTML_GT;
|
||||
|
||||
public static final String RE_HTML_MARK = "(<[^<]*?>)|(<[\\s]*?/[^<]*?>)|(<[^<]*?/[\\s]*?>)";
|
||||
public static final String RE_SCRIPT = "<[\\s]*?script[^>]*?>.*?<[\\s]*?\\/[\\s]*?script[\\s]*?>";
|
||||
|
||||
private static final char[][] TEXT = new char[256][];
|
||||
|
||||
static {
|
||||
// ascii码值最大的是【0x7f=127】,扩展ascii码值最大的是【0xFF=255】,因为ASCII码使用指定的7位或8位二进制数组合来表示128或256种可能的字符,标准ASCII码也叫基础ASCII码。
|
||||
for (int i = 0; i < 256; i++) {
|
||||
TEXT[i] = new char[]{(char) i};
|
||||
}
|
||||
|
||||
// special HTML characters
|
||||
TEXT['\''] = "'".toCharArray(); // 单引号 (''' doesn't work - it is not by the w3 specs)
|
||||
TEXT['"'] = QUOTE.toCharArray(); // 双引号
|
||||
TEXT['&'] = AMP.toCharArray(); // &符
|
||||
TEXT['<'] = LT.toCharArray(); // 小于号
|
||||
TEXT['>'] = GT.toCharArray(); // 大于号
|
||||
TEXT[' '] = NBSP.toCharArray(); // 不断开空格(non-breaking space,缩写nbsp。ASCII值是32:是用键盘输入的空格;ASCII值是160:不间断空格,即  ,所产生的空格,作用是在页面换行时不被打断)
|
||||
}
|
||||
|
||||
/**
|
||||
* 转义文本中的HTML字符为安全的字符,以下字符被转义:
|
||||
* <ul>
|
||||
* <li>' 替换为 &#039; (&apos; doesn't work in HTML4)</li>
|
||||
* <li>" 替换为 &quot;</li>
|
||||
* <li>& 替换为 &amp;</li>
|
||||
* <li>< 替换为 &lt;</li>
|
||||
* <li>> 替换为 &gt;</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param text 被转义的文本
|
||||
* @return 转义后的文本
|
||||
*/
|
||||
public static String escape(String text) {
|
||||
return encode(text);
|
||||
}
|
||||
|
||||
/**
|
||||
* 还原被转义的HTML特殊字符
|
||||
*
|
||||
* @param htmlStr 包含转义符的HTML内容
|
||||
* @return 转换后的字符串
|
||||
*/
|
||||
public static String unescape(String htmlStr) {
|
||||
if (StrUtil.isBlank(htmlStr)) {
|
||||
return htmlStr;
|
||||
}
|
||||
|
||||
return EscapeUtil.unescapeHtml4(htmlStr);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- encode text
|
||||
|
||||
/**
|
||||
* 清除所有HTML标签,但是不删除标签内的内容
|
||||
*
|
||||
* @param content 文本
|
||||
* @return 清除标签后的文本
|
||||
*/
|
||||
public static String cleanHtmlTag(String content) {
|
||||
return content.replaceAll(RE_HTML_MARK, "");
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除指定HTML标签和被标签包围的内容<br>
|
||||
* 不区分大小写
|
||||
*
|
||||
* @param content 文本
|
||||
* @param tagNames 要清除的标签
|
||||
* @return 去除标签后的文本
|
||||
*/
|
||||
public static String removeHtmlTag(String content, String... tagNames) {
|
||||
return removeHtmlTag(content, true, tagNames);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除指定HTML标签,不包括内容<br>
|
||||
* 不区分大小写
|
||||
*
|
||||
* @param content 文本
|
||||
* @param tagNames 要清除的标签
|
||||
* @return 去除标签后的文本
|
||||
*/
|
||||
public static String unwrapHtmlTag(String content, String... tagNames) {
|
||||
return removeHtmlTag(content, false, tagNames);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除指定HTML标签<br>
|
||||
* 不区分大小写
|
||||
*
|
||||
* @param content 文本
|
||||
* @param withTagContent 是否去掉被包含在标签中的内容
|
||||
* @param tagNames 要清除的标签
|
||||
* @return 去除标签后的文本
|
||||
*/
|
||||
public static String removeHtmlTag(String content, boolean withTagContent, String... tagNames) {
|
||||
String regex;
|
||||
for (String tagName : tagNames) {
|
||||
if (StrUtil.isBlank(tagName)) {
|
||||
continue;
|
||||
}
|
||||
tagName = tagName.trim();
|
||||
// (?i)表示其后面的表达式忽略大小写
|
||||
if (withTagContent) {
|
||||
// 标签及其包含内容
|
||||
regex = StrUtil.format("(?i)<{}(\\s+[^>]*?)?/?>(.*?</{}>)?", tagName, tagName);
|
||||
} else {
|
||||
// 标签不包含内容
|
||||
regex = StrUtil.format("(?i)<{}(\\s+[^>]*?)?/?>|</?{}>", tagName, tagName);
|
||||
}
|
||||
|
||||
content = ReUtil.delAll(regex, content); // 非自闭标签小写
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
/**
|
||||
* 去除HTML标签中的属性,如果多个标签有相同属性,都去除
|
||||
*
|
||||
* @param content 文本
|
||||
* @param attrs 属性名(不区分大小写)
|
||||
* @return 处理后的文本
|
||||
*/
|
||||
public static String removeHtmlAttr(String content, String... attrs) {
|
||||
String regex;
|
||||
for (String attr : attrs) {
|
||||
// (?i) 表示忽略大小写
|
||||
// \s* 属性名前后的空白符去除
|
||||
// [^>]+? 属性值,至少有一个非>的字符,>表示标签结束
|
||||
// \s+(?=>) 表示属性值后跟空格加>,即末尾的属性,此时去掉空格
|
||||
// (?=\s|>) 表示属性值后跟空格(属性后还有别的属性)或者跟>(最后一个属性)
|
||||
regex = StrUtil.format("(?i)(\\s*{}\\s*=[^>]+?\\s+(?=>))|(\\s*{}\\s*=[^>]+?(?=\\s|>))", attr, attr);
|
||||
content = content.replaceAll(regex, StrUtil.EMPTY);
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
/**
|
||||
* 去除指定标签的所有属性
|
||||
*
|
||||
* @param content 内容
|
||||
* @param tagNames 指定标签
|
||||
* @return 处理后的文本
|
||||
*/
|
||||
public static String removeAllHtmlAttr(String content, String... tagNames) {
|
||||
String regex;
|
||||
for (String tagName : tagNames) {
|
||||
regex = StrUtil.format("(?i)<{}[^>]*?>", tagName);
|
||||
content = content.replaceAll(regex, StrUtil.format("<{}>", tagName));
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encoder
|
||||
*
|
||||
* @param text 被编码的文本
|
||||
* @return 编码后的字符
|
||||
*/
|
||||
private static String encode(String text) {
|
||||
int len;
|
||||
if ((text == null) || ((len = text.length()) == 0)) {
|
||||
return StrUtil.EMPTY;
|
||||
}
|
||||
StringBuilder buffer = new StringBuilder(len + (len >> 2));
|
||||
char c;
|
||||
for (int i = 0; i < len; i++) {
|
||||
c = text.charAt(i);
|
||||
if (c < 256) {
|
||||
buffer.append(TEXT[c]);
|
||||
} else {
|
||||
buffer.append(c);
|
||||
}
|
||||
}
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 过滤HTML文本,防止XSS攻击
|
||||
*
|
||||
* @param htmlContent HTML内容
|
||||
* @return 过滤后的内容
|
||||
*/
|
||||
public static String filter(String htmlContent) {
|
||||
return new HTMLFilter().filter(htmlContent);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,357 @@
|
|||
package aiyh.utils.tool.cn.hutool.http;
|
||||
|
||||
import aiyh.utils.tool.cn.hutool.core.collection.CollUtil;
|
||||
import aiyh.utils.tool.cn.hutool.core.collection.CollectionUtil;
|
||||
import aiyh.utils.tool.cn.hutool.core.map.CaseInsensitiveMap;
|
||||
import aiyh.utils.tool.cn.hutool.core.map.MapUtil;
|
||||
import aiyh.utils.tool.cn.hutool.core.util.CharsetUtil;
|
||||
import aiyh.utils.tool.cn.hutool.core.util.StrUtil;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
/**
|
||||
* http基类
|
||||
*
|
||||
* @param <T> 子类类型,方便链式编程
|
||||
* @author Looly
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public abstract class HttpBase<T> {
|
||||
|
||||
/**
|
||||
* 默认的请求编码、URL的encode、decode编码
|
||||
*/
|
||||
protected static final Charset DEFAULT_CHARSET = CharsetUtil.CHARSET_UTF_8;
|
||||
|
||||
/**
|
||||
* HTTP/1.0
|
||||
*/
|
||||
public static final String HTTP_1_0 = "HTTP/1.0";
|
||||
/**
|
||||
* HTTP/1.1
|
||||
*/
|
||||
public static final String HTTP_1_1 = "HTTP/1.1";
|
||||
|
||||
/**
|
||||
* 存储头信息
|
||||
*/
|
||||
protected Map<String, List<String>> headers = new HashMap<>();
|
||||
/**
|
||||
* 编码
|
||||
*/
|
||||
protected Charset charset = DEFAULT_CHARSET;
|
||||
/**
|
||||
* http版本
|
||||
*/
|
||||
protected String httpVersion = HTTP_1_1;
|
||||
/**
|
||||
* 存储主体
|
||||
*/
|
||||
protected byte[] bodyBytes;
|
||||
|
||||
// ---------------------------------------------------------------- Headers start
|
||||
|
||||
/**
|
||||
* 根据name获取头信息<br>
|
||||
* 根据RFC2616规范,header的name不区分大小写
|
||||
*
|
||||
* @param name Header名
|
||||
* @return Header值
|
||||
*/
|
||||
public String header(String name) {
|
||||
final List<String> values = headerList(name);
|
||||
if (CollectionUtil.isEmpty(values)) {
|
||||
return null;
|
||||
}
|
||||
return values.get(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据name获取头信息列表
|
||||
*
|
||||
* @param name Header名
|
||||
* @return Header值
|
||||
* @since 3.1.1
|
||||
*/
|
||||
public List<String> headerList(String name) {
|
||||
if (StrUtil.isBlank(name)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final CaseInsensitiveMap<String, List<String>> headersIgnoreCase = new CaseInsensitiveMap<>(this.headers);
|
||||
return headersIgnoreCase.get(name.trim());
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据name获取头信息
|
||||
*
|
||||
* @param name Header名
|
||||
* @return Header值
|
||||
*/
|
||||
public String header(Header name) {
|
||||
if (null == name) {
|
||||
return null;
|
||||
}
|
||||
return header(name.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置一个header<br>
|
||||
* 如果覆盖模式,则替换之前的值,否则加入到值列表中
|
||||
*
|
||||
* @param name Header名
|
||||
* @param value Header值
|
||||
* @param isOverride 是否覆盖已有值
|
||||
* @return T 本身
|
||||
*/
|
||||
public T header(String name, String value, boolean isOverride) {
|
||||
if (null != name && null != value) {
|
||||
final List<String> values = headers.get(name.trim());
|
||||
if (isOverride || CollectionUtil.isEmpty(values)) {
|
||||
final ArrayList<String> valueList = new ArrayList<>();
|
||||
valueList.add(value);
|
||||
headers.put(name.trim(), valueList);
|
||||
} else {
|
||||
values.add(value.trim());
|
||||
}
|
||||
}
|
||||
return (T) this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置一个header<br>
|
||||
* 如果覆盖模式,则替换之前的值,否则加入到值列表中
|
||||
*
|
||||
* @param name Header名
|
||||
* @param value Header值
|
||||
* @param isOverride 是否覆盖已有值
|
||||
* @return T 本身
|
||||
*/
|
||||
public T header(Header name, String value, boolean isOverride) {
|
||||
return header(name.toString(), value, isOverride);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置一个header<br>
|
||||
* 覆盖模式,则替换之前的值
|
||||
*
|
||||
* @param name Header名
|
||||
* @param value Header值
|
||||
* @return T 本身
|
||||
*/
|
||||
public T header(Header name, String value) {
|
||||
return header(name.toString(), value, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置一个header<br>
|
||||
* 覆盖模式,则替换之前的值
|
||||
*
|
||||
* @param name Header名
|
||||
* @param value Header值
|
||||
* @return T 本身
|
||||
*/
|
||||
public T header(String name, String value) {
|
||||
return header(name, value, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置请求头
|
||||
*
|
||||
* @param headers 请求头
|
||||
* @param isOverride 是否覆盖已有头信息
|
||||
* @return this
|
||||
* @since 4.6.3
|
||||
*/
|
||||
public T headerMap(Map<String, String> headers, boolean isOverride) {
|
||||
if (MapUtil.isEmpty(headers)) {
|
||||
return (T) this;
|
||||
}
|
||||
|
||||
for (Entry<String, String> entry : headers.entrySet()) {
|
||||
this.header(entry.getKey(), StrUtil.nullToEmpty(entry.getValue()), isOverride);
|
||||
}
|
||||
return (T) this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置请求头<br>
|
||||
* 不覆盖原有请求头
|
||||
*
|
||||
* @param headers 请求头
|
||||
* @return this
|
||||
*/
|
||||
public T header(Map<String, List<String>> headers) {
|
||||
return header(headers, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置请求头
|
||||
*
|
||||
* @param headers 请求头
|
||||
* @param isOverride 是否覆盖已有头信息
|
||||
* @return this
|
||||
* @since 4.0.8
|
||||
*/
|
||||
public T header(Map<String, List<String>> headers, boolean isOverride) {
|
||||
if (MapUtil.isEmpty(headers)) {
|
||||
return (T) this;
|
||||
}
|
||||
|
||||
String name;
|
||||
for (Entry<String, List<String>> entry : headers.entrySet()) {
|
||||
name = entry.getKey();
|
||||
for (String value : entry.getValue()) {
|
||||
this.header(name, StrUtil.nullToEmpty(value), isOverride);
|
||||
}
|
||||
}
|
||||
return (T) this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增请求头<br>
|
||||
* 不覆盖原有请求头
|
||||
*
|
||||
* @param headers 请求头
|
||||
* @return this
|
||||
* @since 4.0.3
|
||||
*/
|
||||
public T addHeaders(Map<String, String> headers) {
|
||||
if (MapUtil.isEmpty(headers)) {
|
||||
return (T) this;
|
||||
}
|
||||
|
||||
for (Entry<String, String> entry : headers.entrySet()) {
|
||||
this.header(entry.getKey(), StrUtil.nullToEmpty(entry.getValue()), false);
|
||||
}
|
||||
return (T) this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除一个头信息
|
||||
*
|
||||
* @param name Header名
|
||||
* @return this
|
||||
*/
|
||||
public T removeHeader(String name) {
|
||||
if (name != null) {
|
||||
headers.remove(name.trim());
|
||||
}
|
||||
return (T) this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除一个头信息
|
||||
*
|
||||
* @param name Header名
|
||||
* @return this
|
||||
*/
|
||||
public T removeHeader(Header name) {
|
||||
return removeHeader(name.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取headers
|
||||
*
|
||||
* @return Headers Map
|
||||
*/
|
||||
public Map<String, List<String>> headers() {
|
||||
return Collections.unmodifiableMap(headers);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除所有头信息,包括全局头信息
|
||||
*
|
||||
* @return this
|
||||
* @since 5.7.13
|
||||
*/
|
||||
public T clearHeaders() {
|
||||
this.headers.clear();
|
||||
return (T) this;
|
||||
}
|
||||
// ---------------------------------------------------------------- Headers end
|
||||
|
||||
/**
|
||||
* 返回http版本
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
public String httpVersion() {
|
||||
return httpVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置http版本,此方法不会影响到实际请求的HTTP版本,只用于帮助判断是否connect:Keep-Alive
|
||||
*
|
||||
* @param httpVersion Http版本,{@link HttpBase#HTTP_1_0},{@link HttpBase#HTTP_1_1}
|
||||
* @return this
|
||||
*/
|
||||
public T httpVersion(String httpVersion) {
|
||||
this.httpVersion = httpVersion;
|
||||
return (T) this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取bodyBytes存储字节码
|
||||
*
|
||||
* @return byte[]
|
||||
*/
|
||||
public byte[] bodyBytes() {
|
||||
return this.bodyBytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回字符集
|
||||
*
|
||||
* @return 字符集
|
||||
*/
|
||||
public String charset() {
|
||||
return charset.name();
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置字符集
|
||||
*
|
||||
* @param charset 字符集
|
||||
* @return T 自己
|
||||
* @see CharsetUtil
|
||||
*/
|
||||
public T charset(String charset) {
|
||||
if (StrUtil.isNotBlank(charset)) {
|
||||
charset(Charset.forName(charset));
|
||||
}
|
||||
return (T) this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置字符集
|
||||
*
|
||||
* @param charset 字符集
|
||||
* @return T 自己
|
||||
* @see CharsetUtil
|
||||
*/
|
||||
public T charset(Charset charset) {
|
||||
if (null != charset) {
|
||||
this.charset = charset;
|
||||
}
|
||||
return (T) this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = StrUtil.builder();
|
||||
sb.append("Request Headers: ").append(StrUtil.CRLF);
|
||||
for (Entry<String, List<String>> entry : this.headers.entrySet()) {
|
||||
sb.append(" ").append(
|
||||
entry.getKey()).append(": ").append(CollUtil.join(entry.getValue(), ","))
|
||||
.append(StrUtil.CRLF);
|
||||
}
|
||||
|
||||
sb.append("Request Body: ").append(StrUtil.CRLF);
|
||||
sb.append(" ").append(StrUtil.str(this.bodyBytes, this.charset)).append(StrUtil.CRLF);
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,300 @@
|
|||
package aiyh.utils.tool.cn.hutool.http;
|
||||
|
||||
import aiyh.utils.tool.cn.hutool.core.lang.Assert;
|
||||
import aiyh.utils.tool.cn.hutool.core.net.SSLUtil;
|
||||
|
||||
import javax.net.ssl.HostnameVerifier;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Proxy;
|
||||
|
||||
/**
|
||||
* Http配置项
|
||||
*
|
||||
* @author looly
|
||||
* @since 5.8.0
|
||||
*/
|
||||
public class HttpConfig {
|
||||
|
||||
/**
|
||||
* 创建默认Http配置信息
|
||||
*
|
||||
* @return HttpConfig
|
||||
*/
|
||||
public static HttpConfig create() {
|
||||
return new HttpConfig();
|
||||
}
|
||||
|
||||
/**
|
||||
* 默认连接超时
|
||||
*/
|
||||
int connectionTimeout = HttpGlobalConfig.getTimeout();
|
||||
/**
|
||||
* 默认读取超时
|
||||
*/
|
||||
int readTimeout = HttpGlobalConfig.getTimeout();
|
||||
|
||||
/**
|
||||
* 是否禁用缓存
|
||||
*/
|
||||
boolean isDisableCache;
|
||||
|
||||
/**
|
||||
* 最大重定向次数
|
||||
*/
|
||||
int maxRedirectCount = HttpGlobalConfig.getMaxRedirectCount();
|
||||
|
||||
/**
|
||||
* 代理
|
||||
*/
|
||||
Proxy proxy;
|
||||
|
||||
/**
|
||||
* HostnameVerifier,用于HTTPS安全连接
|
||||
*/
|
||||
HostnameVerifier hostnameVerifier;
|
||||
/**
|
||||
* SSLSocketFactory,用于HTTPS安全连接
|
||||
*/
|
||||
SSLSocketFactory ssf;
|
||||
|
||||
/**
|
||||
* Chuncked块大小,0或小于0表示不设置Chuncked模式
|
||||
*/
|
||||
int blockSize;
|
||||
|
||||
/**
|
||||
* 获取是否忽略响应读取时可能的EOF异常。<br>
|
||||
* 在Http协议中,对于Transfer-Encoding: Chunked在正常情况下末尾会写入一个Length为0的的chunk标识完整结束。<br>
|
||||
* 如果服务端未遵循这个规范或响应没有正常结束,会报EOF异常,此选项用于是否忽略这个异常。
|
||||
*/
|
||||
boolean ignoreEOFError = HttpGlobalConfig.isIgnoreEOFError();
|
||||
/**
|
||||
* 获取是否忽略解码URL,包括URL中的Path部分和Param部分。<br>
|
||||
* 在构建Http请求时,用户传入的URL可能有编码后和未编码的内容混合在一起,如果此参数为{@code true},则会统一解码编码后的参数,<br>
|
||||
* 按照RFC3986规范,在发送请求时,全部编码之。如果为{@code false},则不会解码已经编码的内容,在请求时只编码需要编码的部分。
|
||||
*/
|
||||
boolean decodeUrl = HttpGlobalConfig.isDecodeUrl();
|
||||
|
||||
/**
|
||||
* 请求前的拦截器,用于在请求前重新编辑请求
|
||||
*/
|
||||
final HttpInterceptor.Chain<aiyh.utils.tool.cn.hutool.http.HttpRequest> requestInterceptors = GlobalInterceptor.INSTANCE.getCopiedRequestInterceptor();
|
||||
/**
|
||||
* 响应后的拦截器,用于在响应后处理逻辑
|
||||
*/
|
||||
final HttpInterceptor.Chain<aiyh.utils.tool.cn.hutool.http.HttpResponse> responseInterceptors = GlobalInterceptor.INSTANCE.getCopiedResponseInterceptor();
|
||||
|
||||
/**
|
||||
* 重定向时是否使用拦截器
|
||||
*/
|
||||
boolean interceptorOnRedirect;
|
||||
|
||||
/**
|
||||
* 设置超时,单位:毫秒<br>
|
||||
* 超时包括:
|
||||
*
|
||||
* <pre>
|
||||
* 1. 连接超时
|
||||
* 2. 读取响应超时
|
||||
* </pre>
|
||||
*
|
||||
* @param milliseconds 超时毫秒数
|
||||
* @return this
|
||||
* @see #setConnectionTimeout(int)
|
||||
* @see #setReadTimeout(int)
|
||||
*/
|
||||
public HttpConfig timeout(int milliseconds) {
|
||||
setConnectionTimeout(milliseconds);
|
||||
setReadTimeout(milliseconds);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置连接超时,单位:毫秒
|
||||
*
|
||||
* @param milliseconds 超时毫秒数
|
||||
* @return this
|
||||
*/
|
||||
public HttpConfig setConnectionTimeout(int milliseconds) {
|
||||
this.connectionTimeout = milliseconds;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置连接超时,单位:毫秒
|
||||
*
|
||||
* @param milliseconds 超时毫秒数
|
||||
* @return this
|
||||
*/
|
||||
public HttpConfig setReadTimeout(int milliseconds) {
|
||||
this.readTimeout = milliseconds;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 禁用缓存
|
||||
*
|
||||
* @return this
|
||||
*/
|
||||
public HttpConfig disableCache() {
|
||||
this.isDisableCache = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置最大重定向次数<br>
|
||||
* 如果次数小于1则表示不重定向,大于等于1表示打开重定向
|
||||
*
|
||||
* @param maxRedirectCount 最大重定向次数
|
||||
* @return this
|
||||
*/
|
||||
public HttpConfig setMaxRedirectCount(int maxRedirectCount) {
|
||||
this.maxRedirectCount = Math.max(maxRedirectCount, 0);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置域名验证器<br>
|
||||
* 只针对HTTPS请求,如果不设置,不做验证,所有域名被信任
|
||||
*
|
||||
* @param hostnameVerifier HostnameVerifier
|
||||
* @return this
|
||||
*/
|
||||
public HttpConfig setHostnameVerifier(HostnameVerifier hostnameVerifier) {
|
||||
// 验证域
|
||||
this.hostnameVerifier = hostnameVerifier;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置Http代理
|
||||
*
|
||||
* @param host 代理 主机
|
||||
* @param port 代理 端口
|
||||
* @return this
|
||||
*/
|
||||
public HttpConfig setHttpProxy(String host, int port) {
|
||||
final Proxy proxy = new Proxy(Proxy.Type.HTTP,
|
||||
new InetSocketAddress(host, port));
|
||||
return setProxy(proxy);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置代理
|
||||
*
|
||||
* @param proxy 代理 {@link Proxy}
|
||||
* @return this
|
||||
*/
|
||||
public HttpConfig setProxy(Proxy proxy) {
|
||||
this.proxy = proxy;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置SSLSocketFactory<br>
|
||||
* 只针对HTTPS请求,如果不设置,使用默认的SSLSocketFactory<br>
|
||||
* 默认SSLSocketFactory为:SSLSocketFactoryBuilder.create().build();
|
||||
*
|
||||
* @param ssf SSLScketFactory
|
||||
* @return this
|
||||
*/
|
||||
public HttpConfig setSSLSocketFactory(SSLSocketFactory ssf) {
|
||||
this.ssf = ssf;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置HTTPS安全连接协议,只针对HTTPS请求,可以使用的协议包括:<br>
|
||||
* 此方法调用后{@link #setSSLSocketFactory(SSLSocketFactory)} 将被覆盖。
|
||||
*
|
||||
* <pre>
|
||||
* 1. TLSv1.2
|
||||
* 2. TLSv1.1
|
||||
* 3. SSLv3
|
||||
* ...
|
||||
* </pre>
|
||||
*
|
||||
* @param protocol 协议
|
||||
* @return this
|
||||
* @see SSLUtil#createSSLContext(String)
|
||||
* @see #setSSLSocketFactory(SSLSocketFactory)
|
||||
*/
|
||||
public HttpConfig setSSLProtocol(String protocol) {
|
||||
Assert.notBlank(protocol, "protocol must be not blank!");
|
||||
setSSLSocketFactory(SSLUtil.createSSLContext(protocol).getSocketFactory());
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 采用流方式上传数据,无需本地缓存数据。<br>
|
||||
* HttpUrlConnection默认是将所有数据读到本地缓存,然后再发送给服务器,这样上传大文件时就会导致内存溢出。
|
||||
*
|
||||
* @param blockSize 块大小(bytes数),0或小于0表示不设置Chuncked模式
|
||||
* @return this
|
||||
*/
|
||||
public HttpConfig setBlockSize(int blockSize) {
|
||||
this.blockSize = blockSize;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置是否忽略响应读取时可能的EOF异常。<br>
|
||||
* 在Http协议中,对于Transfer-Encoding: Chunked在正常情况下末尾会写入一个Length为0的的chunk标识完整结束。<br>
|
||||
* 如果服务端未遵循这个规范或响应没有正常结束,会报EOF异常,此选项用于是否忽略这个异常。
|
||||
*
|
||||
* @param ignoreEOFError 是否忽略响应读取时可能的EOF异常。
|
||||
* @return this
|
||||
* @since 5.7.20
|
||||
*/
|
||||
public HttpConfig setIgnoreEOFError(boolean ignoreEOFError) {
|
||||
this.ignoreEOFError = ignoreEOFError;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置是否忽略解码URL,包括URL中的Path部分和Param部分。<br>
|
||||
* 在构建Http请求时,用户传入的URL可能有编码后和未编码的内容混合在一起,如果此参数为{@code true},则会统一解码编码后的参数,<br>
|
||||
* 按照RFC3986规范,在发送请求时,全部编码之。如果为{@code false},则不会解码已经编码的内容,在请求时只编码需要编码的部分。
|
||||
*
|
||||
* @param decodeUrl 是否忽略解码URL
|
||||
* @return this
|
||||
*/
|
||||
public HttpConfig setDecodeUrl(boolean decodeUrl) {
|
||||
this.decodeUrl = decodeUrl;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置拦截器,用于在请求前重新编辑请求
|
||||
*
|
||||
* @param interceptor 拦截器实现
|
||||
* @return this
|
||||
*/
|
||||
public HttpConfig addRequestInterceptor(HttpInterceptor<HttpRequest> interceptor) {
|
||||
this.requestInterceptors.addChain(interceptor);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置拦截器,用于在请求前重新编辑请求
|
||||
*
|
||||
* @param interceptor 拦截器实现
|
||||
* @return this
|
||||
*/
|
||||
public HttpConfig addResponseInterceptor(HttpInterceptor<HttpResponse> interceptor) {
|
||||
this.responseInterceptors.addChain(interceptor);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 重定向时是否使用拦截器
|
||||
*
|
||||
* @param interceptorOnRedirect 重定向时是否使用拦截器
|
||||
* @return this
|
||||
*/
|
||||
public HttpConfig setInterceptorOnRedirect(boolean interceptorOnRedirect) {
|
||||
this.interceptorOnRedirect = interceptorOnRedirect;
|
||||
return this;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,568 @@
|
|||
package aiyh.utils.tool.cn.hutool.http;
|
||||
|
||||
import aiyh.utils.tool.cn.hutool.core.map.MapUtil;
|
||||
import aiyh.utils.tool.cn.hutool.core.util.ObjectUtil;
|
||||
import aiyh.utils.tool.cn.hutool.core.util.ReflectUtil;
|
||||
import aiyh.utils.tool.cn.hutool.core.util.StrUtil;
|
||||
import aiyh.utils.tool.cn.hutool.core.util.URLUtil;
|
||||
import aiyh.utils.tool.cn.hutool.http.ssl.DefaultSSLInfo;
|
||||
|
||||
import javax.net.ssl.HostnameVerifier;
|
||||
import javax.net.ssl.HttpsURLConnection;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.*;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.UnsupportedCharsetException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
/**
|
||||
* http连接对象,对HttpURLConnection的包装
|
||||
*
|
||||
* @author Looly
|
||||
*/
|
||||
public class HttpConnection {
|
||||
|
||||
private final URL url;
|
||||
private final Proxy proxy;
|
||||
private HttpURLConnection conn;
|
||||
|
||||
/**
|
||||
* 创建HttpConnection
|
||||
*
|
||||
* @param urlStr URL
|
||||
* @param proxy 代理,无代理传{@code null}
|
||||
* @return HttpConnection
|
||||
*/
|
||||
public static HttpConnection create(String urlStr, Proxy proxy) {
|
||||
return create(URLUtil.toUrlForHttp(urlStr), proxy);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建HttpConnection
|
||||
*
|
||||
* @param url URL
|
||||
* @param proxy 代理,无代理传{@code null}
|
||||
* @return HttpConnection
|
||||
*/
|
||||
public static HttpConnection create(URL url, Proxy proxy) {
|
||||
return new HttpConnection(url, proxy);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------- Constructor start
|
||||
|
||||
/**
|
||||
* 构造HttpConnection
|
||||
*
|
||||
* @param url URL
|
||||
* @param proxy 代理
|
||||
*/
|
||||
public HttpConnection(URL url, Proxy proxy) {
|
||||
this.url = url;
|
||||
this.proxy = proxy;
|
||||
|
||||
// 初始化Http连接
|
||||
initConn();
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------- Constructor end
|
||||
|
||||
/**
|
||||
* 初始化连接相关信息
|
||||
*
|
||||
* @return HttpConnection
|
||||
* @since 4.4.1
|
||||
*/
|
||||
public HttpConnection initConn() {
|
||||
try {
|
||||
this.conn = openHttp();
|
||||
} catch (IOException e) {
|
||||
throw new HttpException(e);
|
||||
}
|
||||
|
||||
// 默认读取响应内容
|
||||
this.conn.setDoInput(true);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------- Getters And Setters start
|
||||
|
||||
/**
|
||||
* 获取请求方法,GET/POST
|
||||
*
|
||||
* @return 请求方法, GET/POST
|
||||
*/
|
||||
public aiyh.utils.tool.cn.hutool.http.Method getMethod() {
|
||||
return aiyh.utils.tool.cn.hutool.http.Method.valueOf(this.conn.getRequestMethod());
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置请求方法
|
||||
*
|
||||
* @param method 请求方法
|
||||
* @return 自己
|
||||
*/
|
||||
public HttpConnection setMethod(aiyh.utils.tool.cn.hutool.http.Method method) {
|
||||
if (aiyh.utils.tool.cn.hutool.http.Method.POST.equals(method) //
|
||||
|| aiyh.utils.tool.cn.hutool.http.Method.PUT.equals(method)//
|
||||
|| aiyh.utils.tool.cn.hutool.http.Method.PATCH.equals(method)//
|
||||
|| aiyh.utils.tool.cn.hutool.http.Method.DELETE.equals(method)) {
|
||||
this.conn.setUseCaches(false);
|
||||
|
||||
// 增加PATCH方法支持
|
||||
if (aiyh.utils.tool.cn.hutool.http.Method.PATCH.equals(method)) {
|
||||
try {
|
||||
HttpGlobalConfig.allowPatch();
|
||||
} catch (Exception ignore) {
|
||||
// ignore
|
||||
// https://github.com/dromara/hutool/issues/2832
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// method
|
||||
try {
|
||||
this.conn.setRequestMethod(method.toString());
|
||||
} catch (ProtocolException e) {
|
||||
if (aiyh.utils.tool.cn.hutool.http.Method.PATCH.equals(method)) {
|
||||
// 如果全局设置失效,此处针对单独链接重新设置
|
||||
reflectSetMethod(method);
|
||||
} else {
|
||||
throw new HttpException(e);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取请求URL
|
||||
*
|
||||
* @return 请求URL
|
||||
*/
|
||||
public URL getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得代理
|
||||
*
|
||||
* @return {@link Proxy}
|
||||
*/
|
||||
public Proxy getProxy() {
|
||||
return proxy;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取HttpURLConnection对象
|
||||
*
|
||||
* @return HttpURLConnection
|
||||
*/
|
||||
public HttpURLConnection getHttpURLConnection() {
|
||||
return conn;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------- Getters And Setters end
|
||||
|
||||
// ---------------------------------------------------------------- Headers start
|
||||
|
||||
/**
|
||||
* 设置请求头<br>
|
||||
* 当请求头存在时,覆盖之
|
||||
*
|
||||
* @param header 头名
|
||||
* @param value 头值
|
||||
* @param isOverride 是否覆盖旧值
|
||||
* @return HttpConnection
|
||||
*/
|
||||
public HttpConnection header(String header, String value, boolean isOverride) {
|
||||
if (null != this.conn) {
|
||||
if (isOverride) {
|
||||
this.conn.setRequestProperty(header, value);
|
||||
} else {
|
||||
this.conn.addRequestProperty(header, value);
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置请求头<br>
|
||||
* 当请求头存在时,覆盖之
|
||||
*
|
||||
* @param header 头名
|
||||
* @param value 头值
|
||||
* @param isOverride 是否覆盖旧值
|
||||
* @return HttpConnection
|
||||
*/
|
||||
public HttpConnection header(aiyh.utils.tool.cn.hutool.http.Header header, String value, boolean isOverride) {
|
||||
return header(header.toString(), value, isOverride);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置请求头<br>
|
||||
* 不覆盖原有请求头
|
||||
*
|
||||
* @param headerMap 请求头
|
||||
* @param isOverride 是否覆盖
|
||||
* @return this
|
||||
*/
|
||||
public HttpConnection header(Map<String, List<String>> headerMap, boolean isOverride) {
|
||||
if (MapUtil.isNotEmpty(headerMap)) {
|
||||
String name;
|
||||
for (Entry<String, List<String>> entry : headerMap.entrySet()) {
|
||||
name = entry.getKey();
|
||||
for (String value : entry.getValue()) {
|
||||
this.header(name, StrUtil.nullToEmpty(value), isOverride);
|
||||
}
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Http请求头
|
||||
*
|
||||
* @param name Header名
|
||||
* @return Http请求头值
|
||||
*/
|
||||
public String header(String name) {
|
||||
return this.conn.getHeaderField(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Http请求头
|
||||
*
|
||||
* @param name Header名
|
||||
* @return Http请求头值
|
||||
*/
|
||||
public String header(aiyh.utils.tool.cn.hutool.http.Header name) {
|
||||
return header(name.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有Http请求头
|
||||
*
|
||||
* @return Http请求头Map
|
||||
*/
|
||||
public Map<String, List<String>> headers() {
|
||||
return this.conn.getHeaderFields();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- Headers end
|
||||
|
||||
/**
|
||||
* 设置https请求参数<br>
|
||||
* 有些时候htts请求会出现com.sun.net.ssl.internal.www.protocol.https.HttpsURLConnectionOldImpl的实现,此为sun内部api,按照普通http请求处理
|
||||
*
|
||||
* @param hostnameVerifier 域名验证器,非https传入null
|
||||
* @param ssf SSLSocketFactory,非https传入null
|
||||
* @return this
|
||||
* @throws HttpException KeyManagementException和NoSuchAlgorithmException异常包装
|
||||
*/
|
||||
public HttpConnection setHttpsInfo(HostnameVerifier hostnameVerifier, SSLSocketFactory ssf) throws HttpException {
|
||||
final HttpURLConnection conn = this.conn;
|
||||
|
||||
if (conn instanceof HttpsURLConnection) {
|
||||
// Https请求
|
||||
final HttpsURLConnection httpsConn = (HttpsURLConnection) conn;
|
||||
// 验证域
|
||||
httpsConn.setHostnameVerifier(ObjectUtil.defaultIfNull(hostnameVerifier, DefaultSSLInfo.TRUST_ANY_HOSTNAME_VERIFIER));
|
||||
httpsConn.setSSLSocketFactory(ObjectUtil.defaultIfNull(ssf, DefaultSSLInfo.DEFAULT_SSF));
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭缓存
|
||||
*
|
||||
* @return this
|
||||
* @see HttpURLConnection#setUseCaches(boolean)
|
||||
*/
|
||||
public HttpConnection disableCache() {
|
||||
this.conn.setUseCaches(false);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置连接超时
|
||||
*
|
||||
* @param timeout 超时
|
||||
* @return this
|
||||
*/
|
||||
public HttpConnection setConnectTimeout(int timeout) {
|
||||
if (timeout > 0 && null != this.conn) {
|
||||
this.conn.setConnectTimeout(timeout);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置读取超时
|
||||
*
|
||||
* @param timeout 超时
|
||||
* @return this
|
||||
*/
|
||||
public HttpConnection setReadTimeout(int timeout) {
|
||||
if (timeout > 0 && null != this.conn) {
|
||||
this.conn.setReadTimeout(timeout);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置连接和读取的超时时间
|
||||
*
|
||||
* @param timeout 超时时间
|
||||
* @return this
|
||||
*/
|
||||
public HttpConnection setConnectionAndReadTimeout(int timeout) {
|
||||
setConnectTimeout(timeout);
|
||||
setReadTimeout(timeout);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置Cookie
|
||||
*
|
||||
* @param cookie Cookie
|
||||
* @return this
|
||||
*/
|
||||
public HttpConnection setCookie(String cookie) {
|
||||
if (cookie != null) {
|
||||
header(Header.COOKIE, cookie, true);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 采用流方式上传数据,无需本地缓存数据。<br>
|
||||
* HttpUrlConnection默认是将所有数据读到本地缓存,然后再发送给服务器,这样上传大文件时就会导致内存溢出。
|
||||
*
|
||||
* @param blockSize 块大小(bytes数),0或小于0表示不设置Chuncked模式
|
||||
* @return this
|
||||
*/
|
||||
public HttpConnection setChunkedStreamingMode(int blockSize) {
|
||||
if (blockSize > 0) {
|
||||
conn.setChunkedStreamingMode(blockSize);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置自动HTTP 30X跳转
|
||||
*
|
||||
* @param isInstanceFollowRedirects 是否自定跳转
|
||||
* @return this
|
||||
*/
|
||||
public HttpConnection setInstanceFollowRedirects(boolean isInstanceFollowRedirects) {
|
||||
conn.setInstanceFollowRedirects(isInstanceFollowRedirects);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 连接
|
||||
*
|
||||
* @return this
|
||||
* @throws IOException IO异常
|
||||
*/
|
||||
public HttpConnection connect() throws IOException {
|
||||
if (null != this.conn) {
|
||||
this.conn.connect();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 静默断开连接。不抛出异常
|
||||
*
|
||||
* @return this
|
||||
* @since 4.6.0
|
||||
*/
|
||||
public HttpConnection disconnectQuietly() {
|
||||
try {
|
||||
disconnect();
|
||||
} catch (Throwable e) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 断开连接
|
||||
*
|
||||
* @return this
|
||||
*/
|
||||
public HttpConnection disconnect() {
|
||||
if (null != this.conn) {
|
||||
this.conn.disconnect();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得输入流对象<br>
|
||||
* 输入流对象用于读取数据
|
||||
*
|
||||
* @return 输入流对象
|
||||
* @throws IOException IO异常
|
||||
*/
|
||||
public InputStream getInputStream() throws IOException {
|
||||
if (null != this.conn) {
|
||||
return this.conn.getInputStream();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 当返回错误代码时,获得错误内容流
|
||||
*
|
||||
* @return 错误内容
|
||||
*/
|
||||
public InputStream getErrorStream() {
|
||||
if (null != this.conn) {
|
||||
return this.conn.getErrorStream();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取输出流对象 输出流对象用于发送数据
|
||||
*
|
||||
* @return OutputStream
|
||||
* @throws IOException IO异常
|
||||
*/
|
||||
public OutputStream getOutputStream() throws IOException {
|
||||
if (null == this.conn) {
|
||||
throw new IOException("HttpURLConnection has not been initialized.");
|
||||
}
|
||||
|
||||
final aiyh.utils.tool.cn.hutool.http.Method method = getMethod();
|
||||
|
||||
// 当有写出需求时,自动打开之
|
||||
this.conn.setDoOutput(true);
|
||||
final OutputStream out = this.conn.getOutputStream();
|
||||
|
||||
// 解决在Rest请求中,GET请求附带body导致GET请求被强制转换为POST
|
||||
// 在sun.net.www.protocol.http.HttpURLConnection.getOutputStream0方法中,会把GET方法
|
||||
// 修改为POST,而且无法调用setRequestMethod方法修改,因此此处使用反射强制修改字段属性值
|
||||
// https://stackoverflow.com/questions/978061/http-get-with-request-body/983458
|
||||
if (method == aiyh.utils.tool.cn.hutool.http.Method.GET && method != getMethod()) {
|
||||
reflectSetMethod(method);
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取响应码
|
||||
*
|
||||
* @return 响应码
|
||||
* @throws IOException IO异常
|
||||
*/
|
||||
public int responseCode() throws IOException {
|
||||
if (null != this.conn) {
|
||||
return this.conn.getResponseCode();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得字符集编码<br>
|
||||
* 从Http连接的头信息中获得字符集<br>
|
||||
* 从ContentType中获取
|
||||
*
|
||||
* @return 字符集编码
|
||||
*/
|
||||
public String getCharsetName() {
|
||||
return HttpUtil.getCharset(conn);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取字符集编码<br>
|
||||
* 从Http连接的头信息中获得字符集<br>
|
||||
* 从ContentType中获取
|
||||
*
|
||||
* @return {@link Charset}编码
|
||||
* @since 3.0.9
|
||||
*/
|
||||
public Charset getCharset() {
|
||||
Charset charset = null;
|
||||
final String charsetName = getCharsetName();
|
||||
if (StrUtil.isNotBlank(charsetName)) {
|
||||
try {
|
||||
charset = Charset.forName(charsetName);
|
||||
} catch (UnsupportedCharsetException e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
return charset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = StrUtil.builder();
|
||||
sb.append("Request URL: ").append(this.url).append(StrUtil.CRLF);
|
||||
sb.append("Request Method: ").append(this.getMethod()).append(StrUtil.CRLF);
|
||||
// sb.append("Request Headers: ").append(StrUtil.CRLF);
|
||||
// for (Entry<String, List<String>> entry : this.conn.getHeaderFields().entrySet()) {
|
||||
// sb.append(" ").append(entry).append(StrUtil.CRLF);
|
||||
// }
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------- Private Method start
|
||||
|
||||
/**
|
||||
* 初始化http或https请求参数<br>
|
||||
* 有些时候https请求会出现com.sun.net.ssl.internal.www.protocol.https.HttpsURLConnectionOldImpl的实现,此为sun内部api,按照普通http请求处理
|
||||
*
|
||||
* @return {@link HttpURLConnection},https返回{@link HttpsURLConnection}
|
||||
*/
|
||||
private HttpURLConnection openHttp() throws IOException {
|
||||
final URLConnection conn = openConnection();
|
||||
if (!(conn instanceof HttpURLConnection)) {
|
||||
// 防止其它协议造成的转换异常
|
||||
throw new HttpException("'{}' of URL [{}] is not a http connection, make sure URL is format for http.", conn.getClass().getName(), this.url);
|
||||
}
|
||||
|
||||
return (HttpURLConnection) conn;
|
||||
}
|
||||
|
||||
/**
|
||||
* 建立连接
|
||||
*
|
||||
* @return {@link URLConnection}
|
||||
* @throws IOException IO异常
|
||||
*/
|
||||
private URLConnection openConnection() throws IOException {
|
||||
return (null == this.proxy) ? url.openConnection() : url.openConnection(this.proxy);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过反射设置方法名,首先设置HttpURLConnection本身的方法名,再检查是否为代理类,如果是,设置带路对象的方法名。
|
||||
*
|
||||
* @param method 方法名
|
||||
*/
|
||||
private void reflectSetMethod(Method method) {
|
||||
ReflectUtil.setFieldValue(this.conn, "method", method.name());
|
||||
|
||||
// HttpsURLConnectionImpl实现中,使用了代理类,需要修改被代理类的method方法
|
||||
final Object delegate = ReflectUtil.getFieldValue(this.conn, "delegate");
|
||||
if (null != delegate) {
|
||||
ReflectUtil.setFieldValue(delegate, "method", method.name());
|
||||
}
|
||||
}
|
||||
// --------------------------------------------------------------- Private Method end
|
||||
}
|
|
@ -0,0 +1,122 @@
|
|||
package aiyh.utils.tool.cn.hutool.http;
|
||||
|
||||
import aiyh.utils.tool.cn.hutool.core.io.FastByteArrayOutputStream;
|
||||
import aiyh.utils.tool.cn.hutool.core.io.StreamProgress;
|
||||
import aiyh.utils.tool.cn.hutool.core.lang.Assert;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
/**
|
||||
* 下载封装,下载统一使用{@code GET}请求,默认支持30x跳转
|
||||
*
|
||||
* @author looly
|
||||
* @since 5.6.4
|
||||
*/
|
||||
public class HttpDownloader {
|
||||
|
||||
/**
|
||||
* 下载远程文本
|
||||
*
|
||||
* @param url 请求的url
|
||||
* @param customCharset 自定义的字符集,可以使用{@code CharsetUtil#charset} 方法转换
|
||||
* @param streamPress 进度条 {@link StreamProgress}
|
||||
* @return 文本
|
||||
*/
|
||||
public static String downloadString(String url, Charset customCharset, StreamProgress streamPress) {
|
||||
final FastByteArrayOutputStream out = new FastByteArrayOutputStream();
|
||||
download(url, out, true, streamPress);
|
||||
return null == customCharset ? out.toString() : out.toString(customCharset);
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载远程文件数据,支持30x跳转
|
||||
*
|
||||
* @param url 请求的url
|
||||
* @return 文件数据
|
||||
*/
|
||||
public static byte[] downloadBytes(String url) {
|
||||
return requestDownload(url, -1).bodyBytes();
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载远程文件
|
||||
*
|
||||
* @param url 请求的url
|
||||
* @param targetFileOrDir 目标文件或目录,当为目录时,取URL中的文件名,取不到使用编码后的URL做为文件名
|
||||
* @param timeout 超时,单位毫秒,-1表示默认超时
|
||||
* @param streamProgress 进度条
|
||||
* @return 文件大小
|
||||
*/
|
||||
public static long downloadFile(String url, File targetFileOrDir, int timeout, StreamProgress streamProgress) {
|
||||
return requestDownload(url, timeout).writeBody(targetFileOrDir, streamProgress);
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载文件-避免未完成的文件<br>
|
||||
* 来自:https://gitee.com/dromara/hutool/pulls/407<br>
|
||||
* 此方法原理是先在目标文件同级目录下创建临时文件,下载之,等下载完毕后重命名,避免因下载错误导致的文件不完整。
|
||||
*
|
||||
* @param url 请求的url
|
||||
* @param targetFileOrDir 目标文件或目录,当为目录时,取URL中的文件名,取不到使用编码后的URL做为文件名
|
||||
* @param tempFileSuffix 临时文件后缀,默认".temp"
|
||||
* @param timeout 超时,单位毫秒,-1表示默认超时
|
||||
* @param streamProgress 进度条
|
||||
* @return 下载大小
|
||||
* @since 5.7.12
|
||||
*/
|
||||
public long downloadFile(String url, File targetFileOrDir, String tempFileSuffix, int timeout, StreamProgress streamProgress) {
|
||||
return requestDownload(url, timeout).writeBody(targetFileOrDir, tempFileSuffix, streamProgress);
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载远程文件,返回文件
|
||||
*
|
||||
* @param url 请求的url
|
||||
* @param targetFileOrDir 目标文件或目录,当为目录时,取URL中的文件名,取不到使用编码后的URL做为文件名
|
||||
* @param timeout 超时,单位毫秒,-1表示默认超时
|
||||
* @param streamProgress 进度条
|
||||
* @return 文件
|
||||
*/
|
||||
public static File downloadForFile(String url, File targetFileOrDir, int timeout, StreamProgress streamProgress) {
|
||||
return requestDownload(url, timeout).writeBodyForFile(targetFileOrDir, streamProgress);
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载远程文件
|
||||
*
|
||||
* @param url 请求的url
|
||||
* @param out 将下载内容写到输出流中 {@link OutputStream}
|
||||
* @param isCloseOut 是否关闭输出流
|
||||
* @param streamProgress 进度条
|
||||
* @return 文件大小
|
||||
*/
|
||||
public static long download(String url, OutputStream out, boolean isCloseOut, StreamProgress streamProgress) {
|
||||
Assert.notNull(out, "[out] is null !");
|
||||
|
||||
return requestDownload(url, -1).writeBody(out, isCloseOut, streamProgress);
|
||||
}
|
||||
|
||||
/**
|
||||
* 请求下载文件
|
||||
*
|
||||
* @param url 请求下载文件地址
|
||||
* @param timeout 超时时间
|
||||
* @return HttpResponse
|
||||
* @since 5.4.1
|
||||
*/
|
||||
private static aiyh.utils.tool.cn.hutool.http.HttpResponse requestDownload(String url, int timeout) {
|
||||
Assert.notBlank(url, "[url] is blank !");
|
||||
|
||||
final HttpResponse response = HttpUtil.createGet(url, true)
|
||||
.timeout(timeout)
|
||||
.executeAsync();
|
||||
|
||||
if (response.isOk()) {
|
||||
return response;
|
||||
}
|
||||
|
||||
throw new HttpException("Server response error with status code: [{}]", response.getStatus());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
package aiyh.utils.tool.cn.hutool.http;
|
||||
|
||||
import aiyh.utils.tool.cn.hutool.core.util.StrUtil;
|
||||
|
||||
/**
|
||||
* HTTP异常
|
||||
*
|
||||
* @author xiaoleilu
|
||||
*/
|
||||
public class HttpException extends RuntimeException {
|
||||
private static final long serialVersionUID = 8247610319171014183L;
|
||||
|
||||
public HttpException(Throwable e) {
|
||||
super(e.getMessage(), e);
|
||||
}
|
||||
|
||||
public HttpException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public HttpException(String messageTemplate, Object... params) {
|
||||
super(StrUtil.format(messageTemplate, params));
|
||||
}
|
||||
|
||||
public HttpException(String message, Throwable throwable) {
|
||||
super(message, throwable);
|
||||
}
|
||||
|
||||
public HttpException(String message, Throwable throwable, boolean enableSuppression, boolean writableStackTrace) {
|
||||
super(message, throwable, enableSuppression, writableStackTrace);
|
||||
}
|
||||
|
||||
public HttpException(Throwable throwable, String messageTemplate, Object... params) {
|
||||
super(StrUtil.format(messageTemplate, params), throwable);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,214 @@
|
|||
package aiyh.utils.tool.cn.hutool.http;
|
||||
|
||||
import aiyh.utils.tool.cn.hutool.core.util.ArrayUtil;
|
||||
import aiyh.utils.tool.cn.hutool.core.util.RandomUtil;
|
||||
import aiyh.utils.tool.cn.hutool.core.util.ReflectUtil;
|
||||
import aiyh.utils.tool.cn.hutool.http.cookie.GlobalCookieManager;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.Field;
|
||||
import java.net.CookieManager;
|
||||
import java.net.HttpURLConnection;
|
||||
|
||||
/**
|
||||
* HTTP 全局参数配置
|
||||
*
|
||||
* @author Looly
|
||||
* @since 4.6.2
|
||||
*/
|
||||
public class HttpGlobalConfig implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* -1: 含义,永不超时。
|
||||
* 如果:设置timeout = 3s(3000 ms), 那一次请求最大超时:就是:6s
|
||||
* 官方含义:timeout of zero is interpreted as an infinite timeout. (0的超时被解释为无限超时。)
|
||||
* 这里实际项目一定要进行修改,防止把系统拖死.
|
||||
* 底层调用:{@link HttpURLConnection#setReadTimeout(int)} 同时设置: 读取超时
|
||||
* 底层调用:{@link HttpURLConnection#setConnectTimeout(int)} 同时设置: 连接超时
|
||||
*/
|
||||
private static int timeout = -1;
|
||||
private static boolean isAllowPatch = false;
|
||||
private static String boundary = "--------------------Hutool_" + RandomUtil.randomString(16);
|
||||
private static int maxRedirectCount = 0;
|
||||
private static boolean ignoreEOFError = true;
|
||||
private static boolean decodeUrl = false;
|
||||
|
||||
/**
|
||||
* 获取全局默认的超时时长
|
||||
*
|
||||
* @return 全局默认的超时时长
|
||||
*/
|
||||
public static int getTimeout() {
|
||||
return timeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置默认的连接和读取超时时长<br>
|
||||
* -1: 含义,永不超时。<br>
|
||||
* 如果:设置timeout = 3s(3000 ms), 那一次请求最大超时:就是:6s<br>
|
||||
* 官方含义:timeout of zero is interpreted as an infinite timeout. (0的超时被解释为无限超时。)<br>
|
||||
* 这里实际项目一定要进行修改,防止把系统拖死.<br>
|
||||
* 底层调用:{@link HttpURLConnection#setReadTimeout(int)} 同时设置: 读取超时<br>
|
||||
* 底层调用:{@link HttpURLConnection#setConnectTimeout(int)} 同时设置: 连接超时
|
||||
*
|
||||
* @param customTimeout 超时时长
|
||||
*/
|
||||
synchronized public static void setTimeout(int customTimeout) {
|
||||
timeout = customTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取全局默认的Multipart边界
|
||||
*
|
||||
* @return 全局默认的Multipart边界
|
||||
* @since 5.7.17
|
||||
*/
|
||||
public static String getBoundary() {
|
||||
return boundary;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置默认的Multipart边界
|
||||
*
|
||||
* @param customBoundary 自定义Multipart边界
|
||||
* @since 5.7.17
|
||||
*/
|
||||
synchronized public static void setBoundary(String customBoundary) {
|
||||
boundary = customBoundary;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取全局默认的最大重定向次数,如设置0表示不重定向<br>
|
||||
* 如果设置为1,表示重定向一次,即请求两次
|
||||
*
|
||||
* @return 全局默认的最大重定向次数
|
||||
* @since 5.7.19
|
||||
*/
|
||||
public static int getMaxRedirectCount() {
|
||||
return maxRedirectCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置默认全局默认的最大重定向次数,如设置0表示不重定向<br>
|
||||
* 如果设置为1,表示重定向一次,即请求两次
|
||||
*
|
||||
* @param customMaxRedirectCount 全局默认的最大重定向次数
|
||||
* @since 5.7.19
|
||||
*/
|
||||
synchronized public static void setMaxRedirectCount(int customMaxRedirectCount) {
|
||||
maxRedirectCount = customMaxRedirectCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取是否忽略响应读取时可能的EOF异常。<br>
|
||||
* 在Http协议中,对于Transfer-Encoding: Chunked在正常情况下末尾会写入一个Length为0的的chunk标识完整结束。<br>
|
||||
* 如果服务端未遵循这个规范或响应没有正常结束,会报EOF异常,此选项用于是否忽略这个异常。
|
||||
*
|
||||
* @return 是否忽略响应读取时可能的EOF异常
|
||||
* @since 5.7.20
|
||||
*/
|
||||
public static boolean isIgnoreEOFError() {
|
||||
return ignoreEOFError;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置是否忽略响应读取时可能的EOF异常。<br>
|
||||
* 在Http协议中,对于Transfer-Encoding: Chunked在正常情况下末尾会写入一个Length为0的的chunk标识完整结束。<br>
|
||||
* 如果服务端未遵循这个规范或响应没有正常结束,会报EOF异常,此选项用于是否忽略这个异常。
|
||||
*
|
||||
* @param customIgnoreEOFError 是否忽略响应读取时可能的EOF异常。
|
||||
* @since 5.7.20
|
||||
*/
|
||||
synchronized public static void setIgnoreEOFError(boolean customIgnoreEOFError) {
|
||||
ignoreEOFError = customIgnoreEOFError;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取是否忽略解码URL,包括URL中的Path部分和Param部分。<br>
|
||||
* 在构建Http请求时,用户传入的URL可能有编码后和未编码的内容混合在一起,如果此参数为{@code true},则会统一解码编码后的参数,<br>
|
||||
* 按照RFC3986规范,在发送请求时,全部编码之。如果为{@code false},则不会解码已经编码的内容,在请求时只编码需要编码的部分。
|
||||
*
|
||||
* @return 是否忽略解码URL
|
||||
* @since 5.7.22
|
||||
*/
|
||||
public static boolean isDecodeUrl() {
|
||||
return decodeUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置是否忽略解码URL,包括URL中的Path部分和Param部分。<br>
|
||||
* 在构建Http请求时,用户传入的URL可能有编码后和未编码的内容混合在一起,如果此参数为{@code true},则会统一解码编码后的参数,<br>
|
||||
* 按照RFC3986规范,在发送请求时,全部编码之。如果为{@code false},则不会解码已经编码的内容,在请求时只编码需要编码的部分。
|
||||
*
|
||||
* @param customDecodeUrl 是否忽略解码URL
|
||||
* @since 5.7.22
|
||||
*/
|
||||
synchronized public static void setDecodeUrl(boolean customDecodeUrl) {
|
||||
decodeUrl = customDecodeUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Cookie管理器,用于自定义Cookie管理
|
||||
*
|
||||
* @return {@link CookieManager}
|
||||
* @see GlobalCookieManager#getCookieManager()
|
||||
* @since 4.1.0
|
||||
*/
|
||||
public static CookieManager getCookieManager() {
|
||||
return GlobalCookieManager.getCookieManager();
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义{@link CookieManager}
|
||||
*
|
||||
* @param customCookieManager 自定义的{@link CookieManager}
|
||||
* @see GlobalCookieManager#setCookieManager(CookieManager)
|
||||
* @since 4.5.14
|
||||
*/
|
||||
synchronized public static void setCookieManager(CookieManager customCookieManager) {
|
||||
GlobalCookieManager.setCookieManager(customCookieManager);
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭Cookie
|
||||
*
|
||||
* @see GlobalCookieManager#setCookieManager(CookieManager)
|
||||
* @since 4.1.9
|
||||
*/
|
||||
synchronized public static void closeCookie() {
|
||||
GlobalCookieManager.setCookieManager(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 增加支持的METHOD方法<br>
|
||||
* 此方法通过注入方式修改{@link HttpURLConnection}中的methods静态属性,增加PATCH方法<br>
|
||||
* see: <a href="https://stackoverflow.com/questions/25163131/httpurlconnection-invalid-http-method-patch">https://stackoverflow.com/questions/25163131/httpurlconnection-invalid-http-method-patch</a>
|
||||
*
|
||||
* @since 5.7.4
|
||||
*/
|
||||
synchronized public static void allowPatch() {
|
||||
if (isAllowPatch) {
|
||||
return;
|
||||
}
|
||||
final Field methodsField = ReflectUtil.getField(HttpURLConnection.class, "methods");
|
||||
if (null == methodsField) {
|
||||
throw new HttpException("None static field [methods] with Java version: [{}]", System.getProperty("java.version"));
|
||||
}
|
||||
|
||||
// 去除final修饰
|
||||
ReflectUtil.removeFinalModify(methodsField);
|
||||
final String[] methods = {
|
||||
"GET", "POST", "HEAD", "OPTIONS", "PUT", "DELETE", "TRACE", "PATCH"
|
||||
};
|
||||
ReflectUtil.setFieldValue(null, methodsField, methods);
|
||||
|
||||
// 检查注入是否成功
|
||||
final Object staticFieldValue = ReflectUtil.getStaticFieldValue(methodsField);
|
||||
if (!ArrayUtil.equals(methods, staticFieldValue)) {
|
||||
throw new HttpException("Inject value to field [methods] failed!");
|
||||
}
|
||||
|
||||
isAllowPatch = true;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,108 @@
|
|||
package aiyh.utils.tool.cn.hutool.http;
|
||||
|
||||
import aiyh.utils.tool.cn.hutool.core.util.StrUtil;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
import java.util.zip.Inflater;
|
||||
import java.util.zip.InflaterInputStream;
|
||||
|
||||
/**
|
||||
* HTTP输入流,此流用于包装Http请求响应内容的流,用于解析各种压缩、分段的响应流内容
|
||||
*
|
||||
* @author Looly
|
||||
*
|
||||
*/
|
||||
public class HttpInputStream extends InputStream {
|
||||
|
||||
/** 原始流 */
|
||||
private InputStream in;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param response 响应对象
|
||||
*/
|
||||
public HttpInputStream(aiyh.utils.tool.cn.hutool.http.HttpResponse response) {
|
||||
init(response);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
return this.in.read();
|
||||
}
|
||||
|
||||
@SuppressWarnings("NullableProblems")
|
||||
@Override
|
||||
public int read(byte[] b, int off, int len) throws IOException {
|
||||
return this.in.read(b, off, len);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long skip(long n) throws IOException {
|
||||
return this.in.skip(n);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int available() throws IOException {
|
||||
return this.in.available();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
this.in.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void mark(int readlimit) {
|
||||
this.in.mark(readlimit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void reset() throws IOException {
|
||||
this.in.reset();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean markSupported() {
|
||||
return this.in.markSupported();
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化流
|
||||
*
|
||||
* @param response 响应对象
|
||||
*/
|
||||
private void init(HttpResponse response) {
|
||||
try {
|
||||
this.in = (response.status < HttpStatus.HTTP_BAD_REQUEST) ? response.httpConnection.getInputStream() : response.httpConnection.getErrorStream();
|
||||
} catch (IOException e) {
|
||||
if (false == (e instanceof FileNotFoundException)) {
|
||||
throw new HttpException(e);
|
||||
}
|
||||
// 服务器无返回内容,忽略之
|
||||
}
|
||||
|
||||
// 在一些情况下,返回的流为null,此时提供状态码说明
|
||||
if (null == this.in) {
|
||||
this.in = new ByteArrayInputStream(StrUtil.format("Error request, response status: {}", response.status).getBytes());
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.isGzip() && false == (response.in instanceof GZIPInputStream)) {
|
||||
// Accept-Encoding: gzip
|
||||
try {
|
||||
this.in = new GZIPInputStream(this.in);
|
||||
} catch (IOException e) {
|
||||
// 在类似于Head等方法中无body返回,此时GZIPInputStream构造会出现错误,在此忽略此错误读取普通数据
|
||||
// ignore
|
||||
}
|
||||
} else if (response.isDeflate() && false == (this.in instanceof InflaterInputStream)) {
|
||||
// Accept-Encoding: defalte
|
||||
this.in = new InflaterInputStream(this.in, new Inflater(true));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
package aiyh.utils.tool.cn.hutool.http;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Http拦截器接口,通过实现此接口,完成请求发起前或结束后对请求的编辑工作
|
||||
*
|
||||
* @param <T> 过滤参数类型,HttpRequest或者HttpResponse
|
||||
* @author looly
|
||||
* @since 5.7.16
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface HttpInterceptor<T extends aiyh.utils.tool.cn.hutool.http.HttpBase<T>> {
|
||||
|
||||
/**
|
||||
* 处理请求
|
||||
*
|
||||
* @param httpObj 请求或响应对象
|
||||
*/
|
||||
void process(T httpObj);
|
||||
|
||||
/**
|
||||
* 拦截器链
|
||||
*
|
||||
* @param <T> 过滤参数类型,HttpRequest或者HttpResponse
|
||||
* @author looly
|
||||
* @since 5.7.16
|
||||
*/
|
||||
class Chain<T extends HttpBase<T>> implements aiyh.utils.tool.cn.hutool.core.lang.Chain<HttpInterceptor<T>, Chain<T>> {
|
||||
private final List<HttpInterceptor<T>> interceptors = new LinkedList<>();
|
||||
|
||||
@Override
|
||||
public Chain<T> addChain(HttpInterceptor<T> element) {
|
||||
interceptors.add(element);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<HttpInterceptor<T>> iterator() {
|
||||
return interceptors.iterator();
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空
|
||||
*
|
||||
* @return this
|
||||
* @since 5.8.0
|
||||
*/
|
||||
public Chain<T> clear() {
|
||||
interceptors.clear();
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,56 @@
|
|||
package aiyh.utils.tool.cn.hutool.http;
|
||||
|
||||
import aiyh.utils.tool.cn.hutool.core.io.resource.Resource;
|
||||
import aiyh.utils.tool.cn.hutool.core.lang.Assert;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.Serializable;
|
||||
import java.net.URL;
|
||||
|
||||
/**
|
||||
* HTTP资源,可自定义Content-Type
|
||||
*
|
||||
* @author looly
|
||||
* @since 5.7.17
|
||||
*/
|
||||
public class HttpResource implements Resource, Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final Resource resource;
|
||||
private final String contentType;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param resource 资源,非空
|
||||
* @param contentType Content-Type类型,{@code null}表示不设置
|
||||
*/
|
||||
public HttpResource(Resource resource, String contentType) {
|
||||
this.resource = Assert.notNull(resource, "Resource must be not null !");
|
||||
this.contentType = contentType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return resource.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public URL getUrl() {
|
||||
return resource.getUrl();
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getStream() {
|
||||
return resource.getStream();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取自定义Content-Type类型
|
||||
*
|
||||
* @return Content-Type类型
|
||||
*/
|
||||
public String getContentType() {
|
||||
return this.contentType;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,644 @@
|
|||
package aiyh.utils.tool.cn.hutool.http;
|
||||
|
||||
import aiyh.utils.tool.cn.hutool.core.convert.Convert;
|
||||
import aiyh.utils.tool.cn.hutool.core.io.FastByteArrayOutputStream;
|
||||
import aiyh.utils.tool.cn.hutool.core.io.FileUtil;
|
||||
import aiyh.utils.tool.cn.hutool.core.io.IORuntimeException;
|
||||
import aiyh.utils.tool.cn.hutool.core.io.IoUtil;
|
||||
import aiyh.utils.tool.cn.hutool.core.io.StreamProgress;
|
||||
import aiyh.utils.tool.cn.hutool.core.lang.Assert;
|
||||
import aiyh.utils.tool.cn.hutool.core.util.ObjUtil;
|
||||
import aiyh.utils.tool.cn.hutool.core.util.ReUtil;
|
||||
import aiyh.utils.tool.cn.hutool.core.util.StrUtil;
|
||||
import aiyh.utils.tool.cn.hutool.core.util.URLUtil;
|
||||
import aiyh.utils.tool.cn.hutool.http.cookie.GlobalCookieManager;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.Closeable;
|
||||
import java.io.EOFException;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.HttpCookie;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.List;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
/**
|
||||
* Http响应类<br>
|
||||
* 非线程安全对象
|
||||
*
|
||||
* @author Looly
|
||||
*/
|
||||
public class HttpResponse extends HttpBase<HttpResponse> implements Closeable {
|
||||
|
||||
/**
|
||||
* Http配置
|
||||
*/
|
||||
protected HttpConfig config;
|
||||
/**
|
||||
* 持有连接对象
|
||||
*/
|
||||
protected HttpConnection httpConnection;
|
||||
/**
|
||||
* Http请求原始流
|
||||
*/
|
||||
protected InputStream in;
|
||||
/**
|
||||
* 是否异步,异步下只持有流,否则将在初始化时直接读取body内容
|
||||
*/
|
||||
private volatile boolean isAsync;
|
||||
/**
|
||||
* 响应状态码
|
||||
*/
|
||||
protected int status;
|
||||
/**
|
||||
* 是否忽略读取Http响应体
|
||||
*/
|
||||
private final boolean ignoreBody;
|
||||
/**
|
||||
* 从响应中获取的编码
|
||||
*/
|
||||
private Charset charsetFromResponse;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param httpConnection {@link HttpConnection}
|
||||
* @param config Http配置
|
||||
* @param charset 编码,从请求编码中获取默认编码
|
||||
* @param isAsync 是否异步
|
||||
* @param isIgnoreBody 是否忽略读取响应体
|
||||
* @since 3.1.2
|
||||
*/
|
||||
protected HttpResponse(HttpConnection httpConnection, HttpConfig config, Charset charset, boolean isAsync, boolean isIgnoreBody) {
|
||||
this.httpConnection = httpConnection;
|
||||
this.config = config;
|
||||
this.charset = charset;
|
||||
this.isAsync = isAsync;
|
||||
this.ignoreBody = isIgnoreBody;
|
||||
initWithDisconnect();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取状态码
|
||||
*
|
||||
* @return 状态码
|
||||
*/
|
||||
public int getStatus() {
|
||||
return this.status;
|
||||
}
|
||||
|
||||
/**
|
||||
* 请求是否成功,判断依据为:状态码范围在200~299内。
|
||||
*
|
||||
* @return 是否成功请求
|
||||
* @since 4.1.9
|
||||
*/
|
||||
public boolean isOk() {
|
||||
return this.status >= 200 && this.status < 300;
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步<br>
|
||||
* 如果为异步状态,则暂时不读取服务器中响应的内容,而是持有Http链接的{@link InputStream}。<br>
|
||||
* 当调用此方法时,异步状态转为同步状态,此时从Http链接流中读取body内容并暂存在内容中。如果已经是同步状态,则不进行任何操作。
|
||||
*
|
||||
* @return this
|
||||
*/
|
||||
public HttpResponse sync() {
|
||||
return this.isAsync ? forceSync() : this;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- Http Response Header start
|
||||
|
||||
/**
|
||||
* 获取内容编码
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
public String contentEncoding() {
|
||||
return header(aiyh.utils.tool.cn.hutool.http.Header.CONTENT_ENCODING);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取内容长度,以下情况长度无效:
|
||||
* <ul>
|
||||
* <li>Transfer-Encoding: Chunked</li>
|
||||
* <li>Content-Encoding: XXX</li>
|
||||
* </ul>
|
||||
* 参考:https://blog.csdn.net/jiang7701037/article/details/86304302
|
||||
*
|
||||
* @return 长度,-1表示服务端未返回或长度无效
|
||||
* @since 5.7.9
|
||||
*/
|
||||
public long contentLength() {
|
||||
long contentLength = Convert.toLong(header(aiyh.utils.tool.cn.hutool.http.Header.CONTENT_LENGTH), -1L);
|
||||
if (contentLength > 0 && (isChunked() || StrUtil.isNotBlank(contentEncoding()))) {
|
||||
//按照HTTP协议规范,在 Transfer-Encoding和Content-Encoding设置后 Content-Length 无效。
|
||||
contentLength = -1;
|
||||
}
|
||||
return contentLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否为gzip压缩过的内容
|
||||
*
|
||||
* @return 是否为gzip压缩过的内容
|
||||
*/
|
||||
public boolean isGzip() {
|
||||
final String contentEncoding = contentEncoding();
|
||||
return "gzip".equalsIgnoreCase(contentEncoding);
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否为zlib(Deflate)压缩过的内容
|
||||
*
|
||||
* @return 是否为zlib(Deflate)压缩过的内容
|
||||
* @since 4.5.7
|
||||
*/
|
||||
public boolean isDeflate() {
|
||||
final String contentEncoding = contentEncoding();
|
||||
return "deflate".equalsIgnoreCase(contentEncoding);
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否为Transfer-Encoding:Chunked的内容
|
||||
*
|
||||
* @return 是否为Transfer-Encoding:Chunked的内容
|
||||
* @since 4.6.2
|
||||
*/
|
||||
public boolean isChunked() {
|
||||
final String transferEncoding = header(aiyh.utils.tool.cn.hutool.http.Header.TRANSFER_ENCODING);
|
||||
return "Chunked".equalsIgnoreCase(transferEncoding);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取本次请求服务器返回的Cookie信息
|
||||
*
|
||||
* @return Cookie字符串
|
||||
* @since 3.1.1
|
||||
*/
|
||||
public String getCookieStr() {
|
||||
return header(aiyh.utils.tool.cn.hutool.http.Header.SET_COOKIE);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Cookie
|
||||
*
|
||||
* @return Cookie列表
|
||||
* @since 3.1.1
|
||||
*/
|
||||
public List<HttpCookie> getCookies() {
|
||||
return GlobalCookieManager.getCookies(this.httpConnection);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Cookie
|
||||
*
|
||||
* @param name Cookie名
|
||||
* @return {@link HttpCookie}
|
||||
* @since 4.1.4
|
||||
*/
|
||||
public HttpCookie getCookie(String name) {
|
||||
List<HttpCookie> cookie = getCookies();
|
||||
if (null != cookie) {
|
||||
for (HttpCookie httpCookie : cookie) {
|
||||
if (httpCookie.getName().equals(name)) {
|
||||
return httpCookie;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Cookie值
|
||||
*
|
||||
* @param name Cookie名
|
||||
* @return Cookie值
|
||||
* @since 4.1.4
|
||||
*/
|
||||
public String getCookieValue(String name) {
|
||||
HttpCookie cookie = getCookie(name);
|
||||
return (null == cookie) ? null : cookie.getValue();
|
||||
}
|
||||
// ---------------------------------------------------------------- Http Response Header end
|
||||
|
||||
// ---------------------------------------------------------------- Body start
|
||||
|
||||
/**
|
||||
* 获得服务区响应流<br>
|
||||
* 异步模式下获取Http原生流,同步模式下获取获取到的在内存中的副本<br>
|
||||
* 如果想在同步模式下获取流,请先调用{@link #sync()}方法强制同步<br>
|
||||
* 流获取后处理完毕需关闭此类
|
||||
*
|
||||
* @return 响应流
|
||||
*/
|
||||
public InputStream bodyStream() {
|
||||
if (isAsync) {
|
||||
return this.in;
|
||||
}
|
||||
return new ByteArrayInputStream(this.bodyBytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取响应流字节码<br>
|
||||
* 此方法会转为同步模式
|
||||
*
|
||||
* @return byte[]
|
||||
*/
|
||||
@Override
|
||||
public byte[] bodyBytes() {
|
||||
sync();
|
||||
return this.bodyBytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置主体字节码<br>
|
||||
* 需在此方法调用前使用charset方法设置编码,否则使用默认编码UTF-8
|
||||
*
|
||||
* @param bodyBytes 主体
|
||||
* @return this
|
||||
*/
|
||||
public HttpResponse body(byte[] bodyBytes) {
|
||||
sync();
|
||||
if (null != bodyBytes) {
|
||||
this.bodyBytes = bodyBytes;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取响应主体
|
||||
*
|
||||
* @return String
|
||||
* @throws HttpException 包装IO异常
|
||||
*/
|
||||
public String body() throws HttpException {
|
||||
return HttpUtil.getString(bodyBytes(), this.charset, null == this.charsetFromResponse);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将响应内容写出到{@link OutputStream}<br>
|
||||
* 异步模式下直接读取Http流写出,同步模式下将存储在内存中的响应内容写出<br>
|
||||
* 写出后会关闭Http流(异步模式)
|
||||
*
|
||||
* @param out 写出的流
|
||||
* @param isCloseOut 是否关闭输出流
|
||||
* @param streamProgress 进度显示接口,通过实现此接口显示下载进度
|
||||
* @return 写出bytes数
|
||||
* @since 3.3.2
|
||||
*/
|
||||
public long writeBody(OutputStream out, boolean isCloseOut, StreamProgress streamProgress) {
|
||||
Assert.notNull(out, "[out] must be not null!");
|
||||
final long contentLength = contentLength();
|
||||
try {
|
||||
return copyBody(bodyStream(), out, contentLength, streamProgress, this.config.ignoreEOFError);
|
||||
} finally {
|
||||
IoUtil.close(this);
|
||||
if (isCloseOut) {
|
||||
IoUtil.close(out);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将响应内容写出到文件<br>
|
||||
* 异步模式下直接读取Http流写出,同步模式下将存储在内存中的响应内容写出<br>
|
||||
* 写出后会关闭Http流(异步模式)
|
||||
*
|
||||
* @param targetFileOrDir 写出到的文件或目录
|
||||
* @param streamProgress 进度显示接口,通过实现此接口显示下载进度
|
||||
* @return 写出bytes数
|
||||
* @since 3.3.2
|
||||
*/
|
||||
public long writeBody(File targetFileOrDir, StreamProgress streamProgress) {
|
||||
Assert.notNull(targetFileOrDir, "[targetFileOrDir] must be not null!");
|
||||
|
||||
final File outFile = completeFileNameFromHeader(targetFileOrDir);
|
||||
return writeBody(FileUtil.getOutputStream(outFile), true, streamProgress);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将响应内容写出到文件-避免未完成的文件<br>
|
||||
* 异步模式下直接读取Http流写出,同步模式下将存储在内存中的响应内容写出<br>
|
||||
* 写出后会关闭Http流(异步模式)<br>
|
||||
* 来自:https://gitee.com/dromara/hutool/pulls/407<br>
|
||||
* 此方法原理是先在目标文件同级目录下创建临时文件,下载之,等下载完毕后重命名,避免因下载错误导致的文件不完整。
|
||||
*
|
||||
* @param targetFileOrDir 写出到的文件或目录
|
||||
* @param tempFileSuffix 临时文件后缀,默认".temp"
|
||||
* @param streamProgress 进度显示接口,通过实现此接口显示下载进度
|
||||
* @return 写出bytes数
|
||||
* @since 5.7.12
|
||||
*/
|
||||
public long writeBody(File targetFileOrDir, String tempFileSuffix, StreamProgress streamProgress) {
|
||||
Assert.notNull(targetFileOrDir, "[targetFileOrDir] must be not null!");
|
||||
|
||||
File outFile = completeFileNameFromHeader(targetFileOrDir);
|
||||
|
||||
if (StrUtil.isBlank(tempFileSuffix)) {
|
||||
tempFileSuffix = ".temp";
|
||||
} else {
|
||||
tempFileSuffix = StrUtil.addPrefixIfNot(tempFileSuffix, StrUtil.DOT);
|
||||
}
|
||||
|
||||
// 目标文件真实名称
|
||||
final String fileName = outFile.getName();
|
||||
// 临时文件名称
|
||||
final String tempFileName = fileName + tempFileSuffix;
|
||||
|
||||
// 临时文件
|
||||
outFile = new File(outFile.getParentFile(), tempFileName);
|
||||
|
||||
long length;
|
||||
try {
|
||||
length = writeBody(outFile, streamProgress);
|
||||
// 重命名下载好的临时文件
|
||||
FileUtil.rename(outFile, fileName, true);
|
||||
} catch (Throwable e) {
|
||||
// 异常则删除临时文件
|
||||
FileUtil.del(outFile);
|
||||
throw new HttpException(e);
|
||||
}
|
||||
return length;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将响应内容写出到文件<br>
|
||||
* 异步模式下直接读取Http流写出,同步模式下将存储在内存中的响应内容写出<br>
|
||||
* 写出后会关闭Http流(异步模式)
|
||||
*
|
||||
* @param targetFileOrDir 写出到的文件
|
||||
* @param streamProgress 进度显示接口,通过实现此接口显示下载进度
|
||||
* @return 写出的文件
|
||||
* @since 5.6.4
|
||||
*/
|
||||
public File writeBodyForFile(File targetFileOrDir, StreamProgress streamProgress) {
|
||||
Assert.notNull(targetFileOrDir, "[targetFileOrDir] must be not null!");
|
||||
|
||||
final File outFile = completeFileNameFromHeader(targetFileOrDir);
|
||||
writeBody(FileUtil.getOutputStream(outFile), true, streamProgress);
|
||||
|
||||
return outFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将响应内容写出到文件<br>
|
||||
* 异步模式下直接读取Http流写出,同步模式下将存储在内存中的响应内容写出<br>
|
||||
* 写出后会关闭Http流(异步模式)
|
||||
*
|
||||
* @param targetFileOrDir 写出到的文件或目录
|
||||
* @return 写出bytes数
|
||||
* @since 3.3.2
|
||||
*/
|
||||
public long writeBody(File targetFileOrDir) {
|
||||
return writeBody(targetFileOrDir, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将响应内容写出到文件<br>
|
||||
* 异步模式下直接读取Http流写出,同步模式下将存储在内存中的响应内容写出<br>
|
||||
* 写出后会关闭Http流(异步模式)
|
||||
*
|
||||
* @param targetFileOrDir 写出到的文件或目录的路径
|
||||
* @return 写出bytes数
|
||||
* @since 3.3.2
|
||||
*/
|
||||
public long writeBody(String targetFileOrDir) {
|
||||
return writeBody(FileUtil.file(targetFileOrDir));
|
||||
}
|
||||
// ---------------------------------------------------------------- Body end
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
IoUtil.close(this.in);
|
||||
this.in = null;
|
||||
// 关闭连接
|
||||
this.httpConnection.disconnectQuietly();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = StrUtil.builder();
|
||||
sb.append("Response Headers: ").append(StrUtil.CRLF);
|
||||
for (Entry<String, List<String>> entry : this.headers.entrySet()) {
|
||||
sb.append(" ").append(entry).append(StrUtil.CRLF);
|
||||
}
|
||||
|
||||
sb.append("Response Body: ").append(StrUtil.CRLF);
|
||||
sb.append(" ").append(this.body()).append(StrUtil.CRLF);
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 从响应头补全下载文件名
|
||||
*
|
||||
* @param targetFileOrDir 目标文件夹或者目标文件
|
||||
* @return File 保存的文件
|
||||
* @since 5.4.1
|
||||
*/
|
||||
public File completeFileNameFromHeader(File targetFileOrDir) {
|
||||
if (false == targetFileOrDir.isDirectory()) {
|
||||
// 非目录直接返回
|
||||
return targetFileOrDir;
|
||||
}
|
||||
|
||||
// 从头信息中获取文件名
|
||||
String fileName = getFileNameFromDisposition(null);
|
||||
if (StrUtil.isBlank(fileName)) {
|
||||
final String path = httpConnection.getUrl().getPath();
|
||||
// 从路径中获取文件名
|
||||
fileName = StrUtil.subSuf(path, path.lastIndexOf('/') + 1);
|
||||
if (StrUtil.isBlank(fileName)) {
|
||||
// 编码后的路径做为文件名
|
||||
fileName = URLUtil.encodeQuery(path, charset);
|
||||
} else {
|
||||
// issue#I4K0FS@Gitee
|
||||
fileName = URLUtil.decode(fileName, charset);
|
||||
}
|
||||
}
|
||||
return FileUtil.file(targetFileOrDir, fileName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从Content-Disposition头中获取文件名
|
||||
* @param paramName 文件参数名
|
||||
*
|
||||
* @return 文件名,empty表示无
|
||||
*/
|
||||
public String getFileNameFromDisposition(String paramName) {
|
||||
paramName = ObjUtil.defaultIfNull(paramName, "filename");
|
||||
String fileName = null;
|
||||
final String disposition = header(Header.CONTENT_DISPOSITION);
|
||||
if (StrUtil.isNotBlank(disposition)) {
|
||||
fileName = ReUtil.get(paramName+"=\"(.*?)\"", disposition, 1);
|
||||
if (StrUtil.isBlank(fileName)) {
|
||||
fileName = StrUtil.subAfter(disposition, paramName + "=", true);
|
||||
}
|
||||
}
|
||||
return fileName;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- Private method start
|
||||
|
||||
/**
|
||||
* 初始化Http响应,并在报错时关闭连接。<br>
|
||||
* 初始化包括:
|
||||
*
|
||||
* <pre>
|
||||
* 1、读取Http状态
|
||||
* 2、读取头信息
|
||||
* 3、持有Http流,并不关闭流
|
||||
* </pre>
|
||||
*
|
||||
* @return this
|
||||
* @throws HttpException IO异常
|
||||
*/
|
||||
private HttpResponse initWithDisconnect() throws HttpException {
|
||||
try {
|
||||
init();
|
||||
} catch (HttpException e) {
|
||||
this.httpConnection.disconnectQuietly();
|
||||
throw e;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化Http响应<br>
|
||||
* 初始化包括:
|
||||
*
|
||||
* <pre>
|
||||
* 1、读取Http状态
|
||||
* 2、读取头信息
|
||||
* 3、持有Http流,并不关闭流
|
||||
* </pre>
|
||||
*
|
||||
* @return this
|
||||
* @throws HttpException IO异常
|
||||
*/
|
||||
private HttpResponse init() throws HttpException {
|
||||
// 获取响应状态码
|
||||
try {
|
||||
this.status = httpConnection.responseCode();
|
||||
} catch (IOException e) {
|
||||
if (false == (e instanceof FileNotFoundException)) {
|
||||
throw new HttpException(e);
|
||||
}
|
||||
// 服务器无返回内容,忽略之
|
||||
}
|
||||
|
||||
|
||||
// 读取响应头信息
|
||||
try {
|
||||
this.headers = httpConnection.headers();
|
||||
} catch (IllegalArgumentException e) {
|
||||
// ignore
|
||||
// StaticLog.warn(e, e.getMessage());
|
||||
}
|
||||
|
||||
// 存储服务端设置的Cookie信息
|
||||
GlobalCookieManager.store(httpConnection);
|
||||
|
||||
// 获取响应编码
|
||||
final Charset charset = httpConnection.getCharset();
|
||||
this.charsetFromResponse = charset;
|
||||
if (null != charset) {
|
||||
this.charset = charset;
|
||||
}
|
||||
|
||||
// 获取响应内容流
|
||||
this.in = new HttpInputStream(this);
|
||||
|
||||
// 同步情况下强制同步
|
||||
return this.isAsync ? this : forceSync();
|
||||
}
|
||||
|
||||
/**
|
||||
* 强制同步,用于初始化<br>
|
||||
* 强制同步后变化如下:
|
||||
*
|
||||
* <pre>
|
||||
* 1、读取body内容到内存
|
||||
* 2、异步状态设为false(变为同步状态)
|
||||
* 3、关闭Http流
|
||||
* 4、断开与服务器连接
|
||||
* </pre>
|
||||
*
|
||||
* @return this
|
||||
*/
|
||||
private HttpResponse forceSync() {
|
||||
// 非同步状态转为同步状态
|
||||
try {
|
||||
this.readBody(this.in);
|
||||
} catch (IORuntimeException e) {
|
||||
//noinspection StatementWithEmptyBody
|
||||
if (e.getCause() instanceof FileNotFoundException) {
|
||||
// 服务器无返回内容,忽略之
|
||||
} else {
|
||||
throw new HttpException(e);
|
||||
}
|
||||
} finally {
|
||||
if (this.isAsync) {
|
||||
this.isAsync = false;
|
||||
}
|
||||
this.close();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取主体,忽略EOFException异常
|
||||
*
|
||||
* @param in 输入流
|
||||
* @throws IORuntimeException IO异常
|
||||
*/
|
||||
private void readBody(InputStream in) throws IORuntimeException {
|
||||
if (ignoreBody) {
|
||||
return;
|
||||
}
|
||||
|
||||
final long contentLength = contentLength();
|
||||
final FastByteArrayOutputStream out = new FastByteArrayOutputStream((int) contentLength);
|
||||
copyBody(in, out, contentLength, null, this.config.ignoreEOFError);
|
||||
this.bodyBytes = out.toByteArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* 将响应内容写出到{@link OutputStream}<br>
|
||||
* 异步模式下直接读取Http流写出,同步模式下将存储在内存中的响应内容写出<br>
|
||||
* 写出后会关闭Http流(异步模式)
|
||||
*
|
||||
* @param in 输入流
|
||||
* @param out 写出的流
|
||||
* @param contentLength 总长度,-1表示未知
|
||||
* @param streamProgress 进度显示接口,通过实现此接口显示下载进度
|
||||
* @param isIgnoreEOFError 是否忽略响应读取时可能的EOF异常
|
||||
* @return 拷贝长度
|
||||
*/
|
||||
private static long copyBody(InputStream in, OutputStream out, long contentLength, StreamProgress streamProgress, boolean isIgnoreEOFError) {
|
||||
if (null == out) {
|
||||
throw new NullPointerException("[out] is null!");
|
||||
}
|
||||
|
||||
long copyLength = -1;
|
||||
try {
|
||||
copyLength = IoUtil.copy(in, out, IoUtil.DEFAULT_BUFFER_SIZE, contentLength, streamProgress);
|
||||
} catch (IORuntimeException e) {
|
||||
//noinspection StatementWithEmptyBody
|
||||
if (isIgnoreEOFError
|
||||
&& (e.getCause() instanceof EOFException || StrUtil.containsIgnoreCase(e.getMessage(), "Premature EOF"))) {
|
||||
// 忽略读取HTTP流中的EOF错误
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
return copyLength;
|
||||
}
|
||||
// ---------------------------------------------------------------- Private method end
|
||||
}
|
|
@ -0,0 +1,222 @@
|
|||
package aiyh.utils.tool.cn.hutool.http;
|
||||
|
||||
/**
|
||||
* HTTP状态码
|
||||
*
|
||||
* @author Looly
|
||||
* @see java.net.HttpURLConnection
|
||||
*
|
||||
*/
|
||||
public class HttpStatus {
|
||||
|
||||
/* 2XX: generally "OK" */
|
||||
|
||||
/**
|
||||
* HTTP Status-Code 200: OK.
|
||||
*/
|
||||
public static final int HTTP_OK = 200;
|
||||
|
||||
/**
|
||||
* HTTP Status-Code 201: Created.
|
||||
*/
|
||||
public static final int HTTP_CREATED = 201;
|
||||
|
||||
/**
|
||||
* HTTP Status-Code 202: Accepted.
|
||||
*/
|
||||
public static final int HTTP_ACCEPTED = 202;
|
||||
|
||||
/**
|
||||
* HTTP Status-Code 203: Non-Authoritative Information.
|
||||
*/
|
||||
public static final int HTTP_NOT_AUTHORITATIVE = 203;
|
||||
|
||||
/**
|
||||
* HTTP Status-Code 204: No Content.
|
||||
*/
|
||||
public static final int HTTP_NO_CONTENT = 204;
|
||||
|
||||
/**
|
||||
* HTTP Status-Code 205: Reset Content.
|
||||
*/
|
||||
public static final int HTTP_RESET = 205;
|
||||
|
||||
/**
|
||||
* HTTP Status-Code 206: Partial Content.
|
||||
*/
|
||||
public static final int HTTP_PARTIAL = 206;
|
||||
|
||||
/* 3XX: relocation/redirect */
|
||||
|
||||
/**
|
||||
* HTTP Status-Code 300: Multiple Choices.
|
||||
*/
|
||||
public static final int HTTP_MULT_CHOICE = 300;
|
||||
|
||||
/**
|
||||
* HTTP Status-Code 301: Moved Permanently.
|
||||
*/
|
||||
public static final int HTTP_MOVED_PERM = 301;
|
||||
|
||||
/**
|
||||
* HTTP Status-Code 302: Temporary Redirect.
|
||||
*/
|
||||
public static final int HTTP_MOVED_TEMP = 302;
|
||||
|
||||
/**
|
||||
* HTTP Status-Code 303: See Other.
|
||||
*/
|
||||
public static final int HTTP_SEE_OTHER = 303;
|
||||
|
||||
/**
|
||||
* HTTP Status-Code 304: Not Modified.
|
||||
*/
|
||||
public static final int HTTP_NOT_MODIFIED = 304;
|
||||
|
||||
/**
|
||||
* HTTP Status-Code 305: Use Proxy.
|
||||
*/
|
||||
public static final int HTTP_USE_PROXY = 305;
|
||||
|
||||
/**
|
||||
* HTTP 1.1 Status-Code 307: Temporary Redirect.<br>
|
||||
* 见:RFC-7231
|
||||
*/
|
||||
public static final int HTTP_TEMP_REDIRECT = 307;
|
||||
|
||||
/**
|
||||
* HTTP 1.1 Status-Code 308: Permanent Redirect 永久重定向<br>
|
||||
* 见:RFC-7231
|
||||
*/
|
||||
public static final int HTTP_PERMANENT_REDIRECT = 308;
|
||||
|
||||
/* 4XX: client error */
|
||||
|
||||
/**
|
||||
* HTTP Status-Code 400: Bad Request.
|
||||
*/
|
||||
public static final int HTTP_BAD_REQUEST = 400;
|
||||
|
||||
/**
|
||||
* HTTP Status-Code 401: Unauthorized.
|
||||
*/
|
||||
public static final int HTTP_UNAUTHORIZED = 401;
|
||||
|
||||
/**
|
||||
* HTTP Status-Code 402: Payment Required.
|
||||
*/
|
||||
public static final int HTTP_PAYMENT_REQUIRED = 402;
|
||||
|
||||
/**
|
||||
* HTTP Status-Code 403: Forbidden.
|
||||
*/
|
||||
public static final int HTTP_FORBIDDEN = 403;
|
||||
|
||||
/**
|
||||
* HTTP Status-Code 404: Not Found.
|
||||
*/
|
||||
public static final int HTTP_NOT_FOUND = 404;
|
||||
|
||||
/**
|
||||
* HTTP Status-Code 405: Method Not Allowed.
|
||||
*/
|
||||
public static final int HTTP_BAD_METHOD = 405;
|
||||
|
||||
/**
|
||||
* HTTP Status-Code 406: Not Acceptable.
|
||||
*/
|
||||
public static final int HTTP_NOT_ACCEPTABLE = 406;
|
||||
|
||||
/**
|
||||
* HTTP Status-Code 407: Proxy Authentication Required.
|
||||
*/
|
||||
public static final int HTTP_PROXY_AUTH = 407;
|
||||
|
||||
/**
|
||||
* HTTP Status-Code 408: Request Time-Out.
|
||||
*/
|
||||
public static final int HTTP_CLIENT_TIMEOUT = 408;
|
||||
|
||||
/**
|
||||
* HTTP Status-Code 409: Conflict.
|
||||
*/
|
||||
public static final int HTTP_CONFLICT = 409;
|
||||
|
||||
/**
|
||||
* HTTP Status-Code 410: Gone.
|
||||
*/
|
||||
public static final int HTTP_GONE = 410;
|
||||
|
||||
/**
|
||||
* HTTP Status-Code 411: Length Required.
|
||||
*/
|
||||
public static final int HTTP_LENGTH_REQUIRED = 411;
|
||||
|
||||
/**
|
||||
* HTTP Status-Code 412: Precondition Failed.
|
||||
*/
|
||||
public static final int HTTP_PRECON_FAILED = 412;
|
||||
|
||||
/**
|
||||
* HTTP Status-Code 413: Request Entity Too Large.
|
||||
*/
|
||||
public static final int HTTP_ENTITY_TOO_LARGE = 413;
|
||||
|
||||
/**
|
||||
* HTTP Status-Code 414: Request-URI Too Large.
|
||||
*/
|
||||
public static final int HTTP_REQ_TOO_LONG = 414;
|
||||
|
||||
/**
|
||||
* HTTP Status-Code 415: Unsupported Media Type.
|
||||
*/
|
||||
public static final int HTTP_UNSUPPORTED_TYPE = 415;
|
||||
|
||||
/* 5XX: server error */
|
||||
|
||||
/**
|
||||
* HTTP Status-Code 500: Internal Server Error.
|
||||
*/
|
||||
public static final int HTTP_INTERNAL_ERROR = 500;
|
||||
|
||||
/**
|
||||
* HTTP Status-Code 501: Not Implemented.
|
||||
*/
|
||||
public static final int HTTP_NOT_IMPLEMENTED = 501;
|
||||
|
||||
/**
|
||||
* HTTP Status-Code 502: Bad Gateway.
|
||||
*/
|
||||
public static final int HTTP_BAD_GATEWAY = 502;
|
||||
|
||||
/**
|
||||
* HTTP Status-Code 503: Service Unavailable.
|
||||
*/
|
||||
public static final int HTTP_UNAVAILABLE = 503;
|
||||
|
||||
/**
|
||||
* HTTP Status-Code 504: Gateway Timeout.
|
||||
*/
|
||||
public static final int HTTP_GATEWAY_TIMEOUT = 504;
|
||||
|
||||
/**
|
||||
* HTTP Status-Code 505: HTTP Version Not Supported.
|
||||
*/
|
||||
public static final int HTTP_VERSION = 505;
|
||||
|
||||
/**
|
||||
* 是否为重定向状态码
|
||||
* @param responseCode 被检查的状态码
|
||||
* @return 是否为重定向状态码
|
||||
* @since 5.6.3
|
||||
*/
|
||||
public static boolean isRedirected(int responseCode){
|
||||
return responseCode == HTTP_MOVED_PERM
|
||||
|| responseCode == HTTP_MOVED_TEMP
|
||||
|| responseCode == HTTP_SEE_OTHER
|
||||
// issue#1504@Github,307和308是RFC 7538中http 1.1定义的规范
|
||||
|| responseCode == HTTP_TEMP_REDIRECT
|
||||
|| responseCode == HTTP_PERMANENT_REDIRECT;
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,893 @@
|
|||
package aiyh.utils.tool.cn.hutool.http;
|
||||
|
||||
import aiyh.utils.tool.cn.hutool.core.codec.Base64;
|
||||
import aiyh.utils.tool.cn.hutool.core.convert.Convert;
|
||||
import aiyh.utils.tool.cn.hutool.core.io.FileUtil;
|
||||
import aiyh.utils.tool.cn.hutool.core.io.IoUtil;
|
||||
import aiyh.utils.tool.cn.hutool.core.io.StreamProgress;
|
||||
import aiyh.utils.tool.cn.hutool.core.map.MapUtil;
|
||||
import aiyh.utils.tool.cn.hutool.core.net.RFC3986;
|
||||
import aiyh.utils.tool.cn.hutool.core.net.url.UrlQuery;
|
||||
import aiyh.utils.tool.cn.hutool.core.text.StrBuilder;
|
||||
import aiyh.utils.tool.cn.hutool.core.util.CharsetUtil;
|
||||
import aiyh.utils.tool.cn.hutool.core.util.ObjectUtil;
|
||||
import aiyh.utils.tool.cn.hutool.core.util.ReUtil;
|
||||
import aiyh.utils.tool.cn.hutool.core.util.StrUtil;
|
||||
import aiyh.utils.tool.cn.hutool.core.util.URLUtil;
|
||||
import aiyh.utils.tool.cn.hutool.http.cookie.GlobalCookieManager;
|
||||
import aiyh.utils.tool.cn.hutool.http.server.SimpleServer;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.CookieManager;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Http请求工具类
|
||||
*
|
||||
* @author xiaoleilu
|
||||
*/
|
||||
public class HttpUtil {
|
||||
|
||||
/**
|
||||
* 正则:Content-Type中的编码信息
|
||||
*/
|
||||
public static final Pattern CHARSET_PATTERN = Pattern.compile("charset\\s*=\\s*([a-z0-9-]*)", Pattern.CASE_INSENSITIVE);
|
||||
/**
|
||||
* 正则:匹配meta标签的编码信息
|
||||
*/
|
||||
public static final Pattern META_CHARSET_PATTERN = Pattern.compile("<meta[^>]*?charset\\s*=\\s*['\"]?([a-z0-9-]*)", Pattern.CASE_INSENSITIVE);
|
||||
|
||||
/**
|
||||
* 检测是否https
|
||||
*
|
||||
* @param url URL
|
||||
* @return 是否https
|
||||
*/
|
||||
public static boolean isHttps(String url) {
|
||||
return StrUtil.startWithIgnoreCase(url, "https:");
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测是否http
|
||||
*
|
||||
* @param url URL
|
||||
* @return 是否http
|
||||
* @since 5.3.8
|
||||
*/
|
||||
public static boolean isHttp(String url) {
|
||||
return StrUtil.startWithIgnoreCase(url, "http:");
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建Http请求对象
|
||||
*
|
||||
* @param method 方法枚举{@link aiyh.utils.tool.cn.hutool.http.Method}
|
||||
* @param url 请求的URL,可以使HTTP或者HTTPS
|
||||
* @return {@link aiyh.utils.tool.cn.hutool.http.HttpRequest}
|
||||
* @since 3.0.9
|
||||
*/
|
||||
public static aiyh.utils.tool.cn.hutool.http.HttpRequest createRequest(Method method, String url) {
|
||||
return aiyh.utils.tool.cn.hutool.http.HttpRequest.of(url).method(method);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建Http GET请求对象
|
||||
*
|
||||
* @param url 请求的URL,可以使HTTP或者HTTPS
|
||||
* @return {@link aiyh.utils.tool.cn.hutool.http.HttpRequest}
|
||||
* @since 3.2.0
|
||||
*/
|
||||
public static aiyh.utils.tool.cn.hutool.http.HttpRequest createGet(String url) {
|
||||
return createGet(url, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建Http GET请求对象
|
||||
*
|
||||
* @param url 请求的URL,可以使HTTP或者HTTPS
|
||||
* @param isFollowRedirects 是否打开重定向
|
||||
* @return {@link aiyh.utils.tool.cn.hutool.http.HttpRequest}
|
||||
* @since 5.6.4
|
||||
*/
|
||||
public static aiyh.utils.tool.cn.hutool.http.HttpRequest createGet(String url, boolean isFollowRedirects) {
|
||||
return aiyh.utils.tool.cn.hutool.http.HttpRequest.get(url).setFollowRedirects(isFollowRedirects);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建Http POST请求对象
|
||||
*
|
||||
* @param url 请求的URL,可以使HTTP或者HTTPS
|
||||
* @return {@link aiyh.utils.tool.cn.hutool.http.HttpRequest}
|
||||
* @since 3.2.0
|
||||
*/
|
||||
public static aiyh.utils.tool.cn.hutool.http.HttpRequest createPost(String url) {
|
||||
return aiyh.utils.tool.cn.hutool.http.HttpRequest.post(url);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送get请求
|
||||
*
|
||||
* @param urlString 网址
|
||||
* @param customCharset 自定义请求字符集,如果字符集获取不到,使用此字符集
|
||||
* @return 返回内容,如果只检查状态码,正常只返回 "",不正常返回 null
|
||||
*/
|
||||
public static String get(String urlString, Charset customCharset) {
|
||||
return aiyh.utils.tool.cn.hutool.http.HttpRequest.get(urlString).charset(customCharset).execute().body();
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送get请求
|
||||
*
|
||||
* @param urlString 网址
|
||||
* @return 返回内容,如果只检查状态码,正常只返回 "",不正常返回 null
|
||||
*/
|
||||
public static String get(String urlString) {
|
||||
return get(urlString, HttpGlobalConfig.getTimeout());
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送get请求
|
||||
*
|
||||
* @param urlString 网址
|
||||
* @param timeout 超时时长,-1表示默认超时,单位毫秒
|
||||
* @return 返回内容,如果只检查状态码,正常只返回 "",不正常返回 null
|
||||
* @since 3.2.0
|
||||
*/
|
||||
public static String get(String urlString, int timeout) {
|
||||
return aiyh.utils.tool.cn.hutool.http.HttpRequest.get(urlString).timeout(timeout).execute().body();
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送get请求
|
||||
*
|
||||
* @param urlString 网址
|
||||
* @param paramMap post表单数据
|
||||
* @return 返回数据
|
||||
*/
|
||||
public static String get(String urlString, Map<String, Object> paramMap) {
|
||||
return aiyh.utils.tool.cn.hutool.http.HttpRequest.get(urlString).form(paramMap).execute().body();
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送get请求
|
||||
*
|
||||
* @param urlString 网址
|
||||
* @param paramMap post表单数据
|
||||
* @param timeout 超时时长,-1表示默认超时,单位毫秒
|
||||
* @return 返回数据
|
||||
* @since 3.3.0
|
||||
*/
|
||||
public static String get(String urlString, Map<String, Object> paramMap, int timeout) {
|
||||
return aiyh.utils.tool.cn.hutool.http.HttpRequest.get(urlString).form(paramMap).timeout(timeout).execute().body();
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送post请求
|
||||
*
|
||||
* @param urlString 网址
|
||||
* @param paramMap post表单数据
|
||||
* @return 返回数据
|
||||
*/
|
||||
public static String post(String urlString, Map<String, Object> paramMap) {
|
||||
return post(urlString, paramMap, HttpGlobalConfig.getTimeout());
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送post请求
|
||||
*
|
||||
* @param urlString 网址
|
||||
* @param paramMap post表单数据
|
||||
* @param timeout 超时时长,-1表示默认超时,单位毫秒
|
||||
* @return 返回数据
|
||||
* @since 3.2.0
|
||||
*/
|
||||
public static String post(String urlString, Map<String, Object> paramMap, int timeout) {
|
||||
return aiyh.utils.tool.cn.hutool.http.HttpRequest.post(urlString).form(paramMap).timeout(timeout).execute().body();
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送post请求<br>
|
||||
* 请求体body参数支持两种类型:
|
||||
*
|
||||
* <pre>
|
||||
* 1. 标准参数,例如 a=1&b=2 这种格式
|
||||
* 2. Rest模式,此时body需要传入一个JSON或者XML字符串,Hutool会自动绑定其对应的Content-Type
|
||||
* </pre>
|
||||
*
|
||||
* @param urlString 网址
|
||||
* @param body post表单数据
|
||||
* @return 返回数据
|
||||
*/
|
||||
public static String post(String urlString, String body) {
|
||||
return post(urlString, body, HttpGlobalConfig.getTimeout());
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送post请求<br>
|
||||
* 请求体body参数支持两种类型:
|
||||
*
|
||||
* <pre>
|
||||
* 1. 标准参数,例如 a=1&b=2 这种格式
|
||||
* 2. Rest模式,此时body需要传入一个JSON或者XML字符串,Hutool会自动绑定其对应的Content-Type
|
||||
* </pre>
|
||||
*
|
||||
* @param urlString 网址
|
||||
* @param body post表单数据
|
||||
* @param timeout 超时时长,-1表示默认超时,单位毫秒
|
||||
* @return 返回数据
|
||||
* @since 3.2.0
|
||||
*/
|
||||
public static String post(String urlString, String body, int timeout) {
|
||||
return HttpRequest.post(urlString).timeout(timeout).body(body).execute().body();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------------- download
|
||||
|
||||
/**
|
||||
* 下载远程文本
|
||||
*
|
||||
* @param url 请求的url
|
||||
* @param customCharsetName 自定义的字符集
|
||||
* @return 文本
|
||||
*/
|
||||
public static String downloadString(String url, String customCharsetName) {
|
||||
return downloadString(url, CharsetUtil.charset(customCharsetName), null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载远程文本
|
||||
*
|
||||
* @param url 请求的url
|
||||
* @param customCharset 自定义的字符集,可以使用{@link CharsetUtil#charset} 方法转换
|
||||
* @return 文本
|
||||
*/
|
||||
public static String downloadString(String url, Charset customCharset) {
|
||||
return downloadString(url, customCharset, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载远程文本
|
||||
*
|
||||
* @param url 请求的url
|
||||
* @param customCharset 自定义的字符集,可以使用{@link CharsetUtil#charset} 方法转换
|
||||
* @param streamPress 进度条 {@link StreamProgress}
|
||||
* @return 文本
|
||||
*/
|
||||
public static String downloadString(String url, Charset customCharset, StreamProgress streamPress) {
|
||||
return HttpDownloader.downloadString(url, customCharset, streamPress);
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载远程文件
|
||||
*
|
||||
* @param url 请求的url
|
||||
* @param dest 目标文件或目录,当为目录时,取URL中的文件名,取不到使用编码后的URL做为文件名
|
||||
* @return 文件大小
|
||||
*/
|
||||
public static long downloadFile(String url, String dest) {
|
||||
return downloadFile(url, FileUtil.file(dest));
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载远程文件
|
||||
*
|
||||
* @param url 请求的url
|
||||
* @param destFile 目标文件或目录,当为目录时,取URL中的文件名,取不到使用编码后的URL做为文件名
|
||||
* @return 文件大小
|
||||
*/
|
||||
public static long downloadFile(String url, File destFile) {
|
||||
return downloadFile(url, destFile, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载远程文件
|
||||
*
|
||||
* @param url 请求的url
|
||||
* @param destFile 目标文件或目录,当为目录时,取URL中的文件名,取不到使用编码后的URL做为文件名
|
||||
* @param timeout 超时,单位毫秒,-1表示默认超时
|
||||
* @return 文件大小
|
||||
* @since 4.0.4
|
||||
*/
|
||||
public static long downloadFile(String url, File destFile, int timeout) {
|
||||
return downloadFile(url, destFile, timeout, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载远程文件
|
||||
*
|
||||
* @param url 请求的url
|
||||
* @param destFile 目标文件或目录,当为目录时,取URL中的文件名,取不到使用编码后的URL做为文件名
|
||||
* @param streamProgress 进度条
|
||||
* @return 文件大小
|
||||
*/
|
||||
public static long downloadFile(String url, File destFile, StreamProgress streamProgress) {
|
||||
return downloadFile(url, destFile, -1, streamProgress);
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载远程文件
|
||||
*
|
||||
* @param url 请求的url
|
||||
* @param destFile 目标文件或目录,当为目录时,取URL中的文件名,取不到使用编码后的URL做为文件名
|
||||
* @param timeout 超时,单位毫秒,-1表示默认超时
|
||||
* @param streamProgress 进度条
|
||||
* @return 文件大小
|
||||
* @since 4.0.4
|
||||
*/
|
||||
public static long downloadFile(String url, File destFile, int timeout, StreamProgress streamProgress) {
|
||||
return HttpDownloader.downloadFile(url, destFile, timeout, streamProgress);
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载远程文件
|
||||
*
|
||||
* @param url 请求的url
|
||||
* @param dest 目标文件或目录,当为目录时,取URL中的文件名,取不到使用编码后的URL做为文件名
|
||||
* @return 下载的文件对象
|
||||
* @since 5.4.1
|
||||
*/
|
||||
public static File downloadFileFromUrl(String url, String dest) {
|
||||
return downloadFileFromUrl(url, FileUtil.file(dest));
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载远程文件
|
||||
*
|
||||
* @param url 请求的url
|
||||
* @param destFile 目标文件或目录,当为目录时,取URL中的文件名,取不到使用编码后的URL做为文件名
|
||||
* @return 下载的文件对象
|
||||
* @since 5.4.1
|
||||
*/
|
||||
public static File downloadFileFromUrl(String url, File destFile) {
|
||||
return downloadFileFromUrl(url, destFile, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载远程文件
|
||||
*
|
||||
* @param url 请求的url
|
||||
* @param destFile 目标文件或目录,当为目录时,取URL中的文件名,取不到使用编码后的URL做为文件名
|
||||
* @param timeout 超时,单位毫秒,-1表示默认超时
|
||||
* @return 下载的文件对象
|
||||
* @since 5.4.1
|
||||
*/
|
||||
public static File downloadFileFromUrl(String url, File destFile, int timeout) {
|
||||
return downloadFileFromUrl(url, destFile, timeout, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载远程文件
|
||||
*
|
||||
* @param url 请求的url
|
||||
* @param destFile 目标文件或目录,当为目录时,取URL中的文件名,取不到使用编码后的URL做为文件名
|
||||
* @param streamProgress 进度条
|
||||
* @return 下载的文件对象
|
||||
* @since 5.4.1
|
||||
*/
|
||||
public static File downloadFileFromUrl(String url, File destFile, StreamProgress streamProgress) {
|
||||
return downloadFileFromUrl(url, destFile, -1, streamProgress);
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载远程文件
|
||||
*
|
||||
* @param url 请求的url
|
||||
* @param destFile 目标文件或目录,当为目录时,取URL中的文件名,取不到使用编码后的URL做为文件名
|
||||
* @param timeout 超时,单位毫秒,-1表示默认超时
|
||||
* @param streamProgress 进度条
|
||||
* @return 下载的文件对象
|
||||
* @since 5.4.1
|
||||
*/
|
||||
public static File downloadFileFromUrl(String url, File destFile, int timeout, StreamProgress streamProgress) {
|
||||
return HttpDownloader.downloadForFile(url, destFile, timeout, streamProgress);
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载远程文件
|
||||
*
|
||||
* @param url 请求的url
|
||||
* @param out 将下载内容写到输出流中 {@link OutputStream}
|
||||
* @param isCloseOut 是否关闭输出流
|
||||
* @return 文件大小
|
||||
*/
|
||||
public static long download(String url, OutputStream out, boolean isCloseOut) {
|
||||
return download(url, out, isCloseOut, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载远程文件
|
||||
*
|
||||
* @param url 请求的url
|
||||
* @param out 将下载内容写到输出流中 {@link OutputStream}
|
||||
* @param isCloseOut 是否关闭输出流
|
||||
* @param streamProgress 进度条
|
||||
* @return 文件大小
|
||||
*/
|
||||
public static long download(String url, OutputStream out, boolean isCloseOut, StreamProgress streamProgress) {
|
||||
return HttpDownloader.download(url, out, isCloseOut, streamProgress);
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载远程文件数据,支持30x跳转
|
||||
*
|
||||
* @param url 请求的url
|
||||
* @return 文件数据
|
||||
* @since 5.3.6
|
||||
*/
|
||||
public static byte[] downloadBytes(String url) {
|
||||
return HttpDownloader.downloadBytes(url);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将Map形式的Form表单数据转换为Url参数形式,会自动url编码键和值
|
||||
*
|
||||
* @param paramMap 表单数据
|
||||
* @return url参数
|
||||
*/
|
||||
public static String toParams(Map<String, ?> paramMap) {
|
||||
return toParams(paramMap, CharsetUtil.CHARSET_UTF_8);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将Map形式的Form表单数据转换为Url参数形式<br>
|
||||
* 编码键和值对
|
||||
*
|
||||
* @param paramMap 表单数据
|
||||
* @param charsetName 编码
|
||||
* @return url参数
|
||||
* @deprecated 请使用 {@link #toParams(Map, Charset)}
|
||||
*/
|
||||
@Deprecated
|
||||
public static String toParams(Map<String, Object> paramMap, String charsetName) {
|
||||
return toParams(paramMap, CharsetUtil.charset(charsetName));
|
||||
}
|
||||
|
||||
/**
|
||||
* 将Map形式的Form表单数据转换为Url参数形式<br>
|
||||
* paramMap中如果key为空(null和"")会被忽略,如果value为null,会被做为空白符("")<br>
|
||||
* 会自动url编码键和值<br>
|
||||
* 此方法用于拼接URL中的Query部分,并不适用于POST请求中的表单
|
||||
*
|
||||
* <pre>
|
||||
* key1=v1&key2=&key3=v3
|
||||
* </pre>
|
||||
*
|
||||
* @param paramMap 表单数据
|
||||
* @param charset 编码,{@code null} 表示不encode键值对
|
||||
* @return url参数
|
||||
* @see #toParams(Map, Charset, boolean)
|
||||
*/
|
||||
public static String toParams(Map<String, ?> paramMap, Charset charset) {
|
||||
return toParams(paramMap, charset, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将Map形式的Form表单数据转换为Url参数形式<br>
|
||||
* paramMap中如果key为空(null和"")会被忽略,如果value为null,会被做为空白符("")<br>
|
||||
* 会自动url编码键和值
|
||||
*
|
||||
* <pre>
|
||||
* key1=v1&key2=&key3=v3
|
||||
* </pre>
|
||||
*
|
||||
* @param paramMap 表单数据
|
||||
* @param charset 编码,null表示不encode键值对
|
||||
* @param isFormUrlEncoded 是否为x-www-form-urlencoded模式,此模式下空格会编码为'+'
|
||||
* @return url参数
|
||||
* @since 5.7.16
|
||||
*/
|
||||
public static String toParams(Map<String, ?> paramMap, Charset charset, boolean isFormUrlEncoded) {
|
||||
return UrlQuery.of(paramMap, isFormUrlEncoded).build(charset);
|
||||
}
|
||||
|
||||
/**
|
||||
* 对URL参数做编码,只编码键和值<br>
|
||||
* 提供的值可以是url附带参数,但是不能只是url
|
||||
*
|
||||
* <p>注意,此方法只能标准化整个URL,并不适合于单独编码参数值</p>
|
||||
*
|
||||
* @param urlWithParams url和参数,可以包含url本身,也可以单独参数
|
||||
* @param charset 编码
|
||||
* @return 编码后的url和参数
|
||||
* @since 4.0.1
|
||||
*/
|
||||
public static String encodeParams(String urlWithParams, Charset charset) {
|
||||
if (StrUtil.isBlank(urlWithParams)) {
|
||||
return StrUtil.EMPTY;
|
||||
}
|
||||
|
||||
String urlPart = null; // url部分,不包括问号
|
||||
String paramPart; // 参数部分
|
||||
final int pathEndPos = urlWithParams.indexOf('?');
|
||||
if (pathEndPos > -1) {
|
||||
// url + 参数
|
||||
urlPart = StrUtil.subPre(urlWithParams, pathEndPos);
|
||||
paramPart = StrUtil.subSuf(urlWithParams, pathEndPos + 1);
|
||||
if (StrUtil.isBlank(paramPart)) {
|
||||
// 无参数,返回url
|
||||
return urlPart;
|
||||
}
|
||||
} else if (false == StrUtil.contains(urlWithParams, '=')) {
|
||||
// 无参数的URL
|
||||
return urlWithParams;
|
||||
} else {
|
||||
// 无URL的参数
|
||||
paramPart = urlWithParams;
|
||||
}
|
||||
|
||||
paramPart = normalizeParams(paramPart, charset);
|
||||
|
||||
return StrUtil.isBlank(urlPart) ? paramPart : urlPart + "?" + paramPart;
|
||||
}
|
||||
|
||||
/**
|
||||
* 标准化参数字符串,即URL中?后的部分
|
||||
*
|
||||
* <p>注意,此方法只能标准化整个URL,并不适合于单独编码参数值</p>
|
||||
*
|
||||
* @param paramPart 参数字符串
|
||||
* @param charset 编码
|
||||
* @return 标准化的参数字符串
|
||||
* @since 4.5.2
|
||||
*/
|
||||
public static String normalizeParams(String paramPart, Charset charset) {
|
||||
if(StrUtil.isEmpty(paramPart)){
|
||||
return paramPart;
|
||||
}
|
||||
final StrBuilder builder = StrBuilder.create(paramPart.length() + 16);
|
||||
final int len = paramPart.length();
|
||||
String name = null;
|
||||
int pos = 0; // 未处理字符开始位置
|
||||
char c; // 当前字符
|
||||
int i; // 当前字符位置
|
||||
for (i = 0; i < len; i++) {
|
||||
c = paramPart.charAt(i);
|
||||
if (c == '=') { // 键值对的分界点
|
||||
if (null == name) {
|
||||
// 只有=前未定义name时被当作键值分界符,否则做为普通字符
|
||||
name = (pos == i) ? StrUtil.EMPTY : paramPart.substring(pos, i);
|
||||
pos = i + 1;
|
||||
}
|
||||
} else if (c == '&') { // 参数对的分界点
|
||||
if (pos != i) {
|
||||
if (null == name) {
|
||||
// 对于像&a&这类无参数值的字符串,我们将name为a的值设为""
|
||||
name = paramPart.substring(pos, i);
|
||||
builder.append(RFC3986.QUERY_PARAM_NAME.encode(name, charset)).append('=');
|
||||
} else {
|
||||
builder.append(RFC3986.QUERY_PARAM_NAME.encode(name, charset)).append('=')
|
||||
.append(RFC3986.QUERY_PARAM_VALUE.encode(paramPart.substring(pos, i), charset)).append('&');
|
||||
}
|
||||
name = null;
|
||||
}
|
||||
pos = i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
// 结尾处理
|
||||
if (null != name) {
|
||||
builder.append(URLUtil.encodeQuery(name, charset)).append('=');
|
||||
}
|
||||
if (pos != i) {
|
||||
if (null == name && pos > 0) {
|
||||
builder.append('=');
|
||||
}
|
||||
builder.append(URLUtil.encodeQuery(paramPart.substring(pos, i), charset));
|
||||
}
|
||||
|
||||
// 以&结尾则去除之
|
||||
int lastIndex = builder.length() - 1;
|
||||
if ('&' == builder.charAt(lastIndex)) {
|
||||
builder.delTo(lastIndex);
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 将URL参数解析为Map(也可以解析Post中的键值对参数)
|
||||
*
|
||||
* @param paramsStr 参数字符串(或者带参数的Path)
|
||||
* @param charset 字符集
|
||||
* @return 参数Map
|
||||
* @since 5.2.6
|
||||
*/
|
||||
public static Map<String, String> decodeParamMap(String paramsStr, Charset charset) {
|
||||
final Map<CharSequence, CharSequence> queryMap = UrlQuery.of(paramsStr, charset).getQueryMap();
|
||||
if (MapUtil.isEmpty(queryMap)) {
|
||||
return MapUtil.empty();
|
||||
}
|
||||
return Convert.toMap(String.class, String.class, queryMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将URL参数解析为Map(也可以解析Post中的键值对参数)
|
||||
*
|
||||
* @param paramsStr 参数字符串(或者带参数的Path)
|
||||
* @param charset 字符集
|
||||
* @return 参数Map
|
||||
*/
|
||||
public static Map<String, List<String>> decodeParams(String paramsStr, String charset) {
|
||||
return decodeParams(paramsStr, charset, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将URL参数解析为Map(也可以解析Post中的键值对参数)
|
||||
*
|
||||
* @param paramsStr 参数字符串(或者带参数的Path)
|
||||
* @param charset 字符集
|
||||
* @param isFormUrlEncoded 是否为x-www-form-urlencoded模式,此模式下空格会编码为'+'
|
||||
* @return 参数Map
|
||||
* @since 5.8.12
|
||||
*/
|
||||
public static Map<String, List<String>> decodeParams(String paramsStr, String charset, boolean isFormUrlEncoded) {
|
||||
return decodeParams(paramsStr, CharsetUtil.charset(charset), isFormUrlEncoded);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将URL QueryString参数解析为Map
|
||||
*
|
||||
* @param paramsStr 参数字符串(或者带参数的Path)
|
||||
* @param charset 字符集
|
||||
* @return 参数Map
|
||||
* @since 5.2.6
|
||||
*/
|
||||
public static Map<String, List<String>> decodeParams(String paramsStr, Charset charset) {
|
||||
return decodeParams(paramsStr, charset, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将URL参数解析为Map(也可以解析Post中的键值对参数)
|
||||
*
|
||||
* @param paramsStr 参数字符串(或者带参数的Path)
|
||||
* @param charset 字符集
|
||||
* @param isFormUrlEncoded 是否为x-www-form-urlencoded模式,此模式下空格会编码为'+'
|
||||
* @return 参数Map
|
||||
*/
|
||||
public static Map<String, List<String>> decodeParams(String paramsStr, Charset charset, boolean isFormUrlEncoded) {
|
||||
final Map<CharSequence, CharSequence> queryMap =
|
||||
UrlQuery.of(paramsStr, charset, true, isFormUrlEncoded).getQueryMap();
|
||||
if (MapUtil.isEmpty(queryMap)) {
|
||||
return MapUtil.empty();
|
||||
}
|
||||
|
||||
final Map<String, List<String>> params = new LinkedHashMap<>();
|
||||
queryMap.forEach((key, value) -> {
|
||||
final List<String> values = params.computeIfAbsent(StrUtil.str(key), k -> new ArrayList<>(1));
|
||||
// 一般是一个参数
|
||||
values.add(StrUtil.str(value));
|
||||
});
|
||||
return params;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将表单数据加到URL中(用于GET表单提交)<br>
|
||||
* 表单的键值对会被url编码,但是url中原参数不会被编码
|
||||
*
|
||||
* @param url URL
|
||||
* @param form 表单数据
|
||||
* @param charset 编码
|
||||
* @param isEncodeParams 是否对键和值做转义处理
|
||||
* @return 合成后的URL
|
||||
*/
|
||||
public static String urlWithForm(String url, Map<String, Object> form, Charset charset, boolean isEncodeParams) {
|
||||
if (isEncodeParams && StrUtil.contains(url, '?')) {
|
||||
// 在需要编码的情况下,如果url中已经有部分参数,则编码之
|
||||
url = encodeParams(url, charset);
|
||||
}
|
||||
|
||||
// url和参数是分别编码的
|
||||
return urlWithForm(url, toParams(form, charset), charset, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将表单数据字符串加到URL中(用于GET表单提交)
|
||||
*
|
||||
* @param url URL
|
||||
* @param queryString 表单数据字符串
|
||||
* @param charset 编码
|
||||
* @param isEncode 是否对键和值做转义处理
|
||||
* @return 拼接后的字符串
|
||||
*/
|
||||
public static String urlWithForm(String url, String queryString, Charset charset, boolean isEncode) {
|
||||
if (StrUtil.isBlank(queryString)) {
|
||||
// 无额外参数
|
||||
if (StrUtil.contains(url, '?')) {
|
||||
// url中包含参数
|
||||
return isEncode ? encodeParams(url, charset) : url;
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
// 始终有参数
|
||||
final StrBuilder urlBuilder = StrBuilder.create(url.length() + queryString.length() + 16);
|
||||
int qmIndex = url.indexOf('?');
|
||||
if (qmIndex > 0) {
|
||||
// 原URL带参数,则对这部分参数单独编码(如果选项为进行编码)
|
||||
urlBuilder.append(isEncode ? encodeParams(url, charset) : url);
|
||||
if (false == StrUtil.endWith(url, '&')) {
|
||||
// 已经带参数的情况下追加参数
|
||||
urlBuilder.append('&');
|
||||
}
|
||||
} else {
|
||||
// 原url无参数,则不做编码
|
||||
urlBuilder.append(url);
|
||||
if (qmIndex < 0) {
|
||||
// 无 '?' 追加之
|
||||
urlBuilder.append('?');
|
||||
}
|
||||
}
|
||||
urlBuilder.append(isEncode ? encodeParams(queryString, charset) : queryString);
|
||||
return urlBuilder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 从Http连接的头信息中获得字符集<br>
|
||||
* 从ContentType中获取
|
||||
*
|
||||
* @param conn HTTP连接对象
|
||||
* @return 字符集
|
||||
*/
|
||||
public static String getCharset(HttpURLConnection conn) {
|
||||
if (conn == null) {
|
||||
return null;
|
||||
}
|
||||
return getCharset(conn.getContentType());
|
||||
}
|
||||
|
||||
/**
|
||||
* 从Http连接的头信息中获得字符集<br>
|
||||
* 从ContentType中获取
|
||||
*
|
||||
* @param contentType Content-Type
|
||||
* @return 字符集
|
||||
* @since 5.2.6
|
||||
*/
|
||||
public static String getCharset(String contentType) {
|
||||
if (StrUtil.isBlank(contentType)) {
|
||||
return null;
|
||||
}
|
||||
return ReUtil.get(CHARSET_PATTERN, contentType, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从流中读取内容<br>
|
||||
* 首先尝试使用charset编码读取内容(如果为空默认UTF-8),如果isGetCharsetFromContent为true,则通过正则在正文中获取编码信息,转换为指定编码;
|
||||
*
|
||||
* @param in 输入流
|
||||
* @param charset 字符集
|
||||
* @param isGetCharsetFromContent 是否从返回内容中获得编码信息
|
||||
* @return 内容
|
||||
*/
|
||||
public static String getString(InputStream in, Charset charset, boolean isGetCharsetFromContent) {
|
||||
final byte[] contentBytes = IoUtil.readBytes(in);
|
||||
return getString(contentBytes, charset, isGetCharsetFromContent);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从流中读取内容<br>
|
||||
* 首先尝试使用charset编码读取内容(如果为空默认UTF-8),如果isGetCharsetFromContent为true,则通过正则在正文中获取编码信息,转换为指定编码;
|
||||
*
|
||||
* @param contentBytes 内容byte数组
|
||||
* @param charset 字符集
|
||||
* @param isGetCharsetFromContent 是否从返回内容中获得编码信息
|
||||
* @return 内容
|
||||
*/
|
||||
public static String getString(byte[] contentBytes, Charset charset, boolean isGetCharsetFromContent) {
|
||||
if (null == contentBytes) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (null == charset) {
|
||||
charset = CharsetUtil.CHARSET_UTF_8;
|
||||
}
|
||||
String content = new String(contentBytes, charset);
|
||||
if (isGetCharsetFromContent) {
|
||||
final String charsetInContentStr = ReUtil.get(META_CHARSET_PATTERN, content, 1);
|
||||
if (StrUtil.isNotBlank(charsetInContentStr)) {
|
||||
Charset charsetInContent = null;
|
||||
try {
|
||||
charsetInContent = Charset.forName(charsetInContentStr);
|
||||
} catch (Exception e) {
|
||||
if (StrUtil.containsIgnoreCase(charsetInContentStr, "utf-8") || StrUtil.containsIgnoreCase(charsetInContentStr, "utf8")) {
|
||||
charsetInContent = CharsetUtil.CHARSET_UTF_8;
|
||||
} else if (StrUtil.containsIgnoreCase(charsetInContentStr, "gbk")) {
|
||||
charsetInContent = CharsetUtil.CHARSET_GBK;
|
||||
}
|
||||
// ignore
|
||||
}
|
||||
if (null != charsetInContent && false == charset.equals(charsetInContent)) {
|
||||
content = new String(contentBytes, charsetInContent);
|
||||
}
|
||||
}
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据文件扩展名获得MimeType
|
||||
*
|
||||
* @param filePath 文件路径或文件名
|
||||
* @param defaultValue 当获取MimeType为null时的默认值
|
||||
* @return MimeType
|
||||
* @see FileUtil#getMimeType(String)
|
||||
* @since 4.6.5
|
||||
*/
|
||||
public static String getMimeType(String filePath, String defaultValue) {
|
||||
return ObjectUtil.defaultIfNull(getMimeType(filePath), defaultValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据文件扩展名获得MimeType
|
||||
*
|
||||
* @param filePath 文件路径或文件名
|
||||
* @return MimeType
|
||||
* @see FileUtil#getMimeType(String)
|
||||
*/
|
||||
public static String getMimeType(String filePath) {
|
||||
return FileUtil.getMimeType(filePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从请求参数的body中判断请求的Content-Type类型,支持的类型有:
|
||||
*
|
||||
* <pre>
|
||||
* 1. application/json
|
||||
* 1. application/xml
|
||||
* </pre>
|
||||
*
|
||||
* @param body 请求参数体
|
||||
* @return Content-Type类型,如果无法判断返回null
|
||||
* @see aiyh.utils.tool.cn.hutool.http.ContentType#get(String)
|
||||
* @since 3.2.0
|
||||
*/
|
||||
public static String getContentTypeByRequestBody(String body) {
|
||||
final aiyh.utils.tool.cn.hutool.http.ContentType contentType = ContentType.get(body);
|
||||
return (null == contentType) ? null : contentType.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建简易的Http服务器
|
||||
*
|
||||
* @param port 端口
|
||||
* @return {@link SimpleServer}
|
||||
* @since 5.2.6
|
||||
*/
|
||||
public static SimpleServer createServer(int port) {
|
||||
return new SimpleServer(port);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建简单的账号秘密验证信息,构建后类似于:
|
||||
* <pre>
|
||||
* Basic YWxhZGRpbjpvcGVuc2VzYW1l
|
||||
* </pre>
|
||||
*
|
||||
* @param username 账号
|
||||
* @param password 密码
|
||||
* @param charset 编码(如果账号或密码中有非ASCII字符适用)
|
||||
* @return 密码验证信息
|
||||
* @since 5.4.6
|
||||
*/
|
||||
public static String buildBasicAuth(String username, String password, Charset charset) {
|
||||
final String data = username.concat(":").concat(password);
|
||||
return "Basic " + Base64.encode(data, charset);
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭Cookie
|
||||
*
|
||||
* @see GlobalCookieManager#setCookieManager(CookieManager)
|
||||
* @since 5.6.5
|
||||
*/
|
||||
public static void closeCookie() {
|
||||
GlobalCookieManager.setCookieManager(null);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package aiyh.utils.tool.cn.hutool.http;
|
||||
|
||||
/**
|
||||
* Http方法枚举
|
||||
*
|
||||
* @author Looly
|
||||
*/
|
||||
public enum Method {
|
||||
GET, POST, HEAD, OPTIONS, PUT, DELETE, TRACE, CONNECT, PATCH
|
||||
}
|
|
@ -0,0 +1,185 @@
|
|||
package aiyh.utils.tool.cn.hutool.http;
|
||||
|
||||
import aiyh.utils.tool.cn.hutool.core.convert.Convert;
|
||||
import aiyh.utils.tool.cn.hutool.core.io.IORuntimeException;
|
||||
import aiyh.utils.tool.cn.hutool.core.io.IoUtil;
|
||||
import aiyh.utils.tool.cn.hutool.core.io.resource.MultiResource;
|
||||
import aiyh.utils.tool.cn.hutool.core.io.resource.Resource;
|
||||
import aiyh.utils.tool.cn.hutool.core.io.resource.StringResource;
|
||||
import aiyh.utils.tool.cn.hutool.core.util.StrUtil;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
/**
|
||||
* Multipart/form-data输出流封装<br>
|
||||
* 遵循RFC2388规范
|
||||
*
|
||||
* @author looly
|
||||
* @since 5.7.17
|
||||
*/
|
||||
public class MultipartOutputStream extends OutputStream {
|
||||
|
||||
private static final String CONTENT_DISPOSITION_TEMPLATE = "Content-Disposition: form-data; name=\"{}\"\r\n";
|
||||
private static final String CONTENT_DISPOSITION_FILE_TEMPLATE = "Content-Disposition: form-data; name=\"{}\"; filename=\"{}\"\r\n";
|
||||
|
||||
private static final String CONTENT_TYPE_FILE_TEMPLATE = "Content-Type: {}\r\n";
|
||||
|
||||
private final OutputStream out;
|
||||
private final Charset charset;
|
||||
private final String boundary;
|
||||
|
||||
private boolean isFinish;
|
||||
|
||||
/**
|
||||
* 构造,使用全局默认的边界字符串
|
||||
*
|
||||
* @param out HTTP写出流
|
||||
* @param charset 编码
|
||||
*/
|
||||
public MultipartOutputStream(OutputStream out, Charset charset) {
|
||||
this(out, charset, HttpGlobalConfig.getBoundary());
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param out HTTP写出流
|
||||
* @param charset 编码
|
||||
* @param boundary 边界符
|
||||
* @since 5.7.17
|
||||
*/
|
||||
public MultipartOutputStream(OutputStream out, Charset charset, String boundary) {
|
||||
this.out = out;
|
||||
this.charset = charset;
|
||||
this.boundary = boundary;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加Multipart表单的数据项<br>
|
||||
* <pre>
|
||||
* --分隔符(boundary)[换行]
|
||||
* Content-Disposition: form-data; name="参数名"[换行]
|
||||
* [换行]
|
||||
* 参数值[换行]
|
||||
* </pre>
|
||||
* <p>
|
||||
* 或者:
|
||||
*
|
||||
* <pre>
|
||||
* --分隔符(boundary)[换行]
|
||||
* Content-Disposition: form-data; name="表单名"; filename="文件名"[换行]
|
||||
* Content-Type: MIME类型[换行]
|
||||
* [换行]
|
||||
* 文件的二进制内容[换行]
|
||||
* </pre>
|
||||
*
|
||||
* @param formFieldName 表单名
|
||||
* @param value 值,可以是普通值、资源(如文件等)
|
||||
* @return this
|
||||
* @throws IORuntimeException IO异常
|
||||
*/
|
||||
public MultipartOutputStream write(String formFieldName, Object value) throws IORuntimeException {
|
||||
// 多资源
|
||||
if (value instanceof MultiResource) {
|
||||
for (Resource subResource : (MultiResource) value) {
|
||||
write(formFieldName, subResource);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
// --分隔符(boundary)[换行]
|
||||
beginPart();
|
||||
|
||||
if (value instanceof Resource) {
|
||||
appendResource(formFieldName, (Resource) value);
|
||||
} else {
|
||||
appendResource(formFieldName,
|
||||
new StringResource(Convert.toStr(value), null, this.charset));
|
||||
}
|
||||
|
||||
write(StrUtil.CRLF);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(int b) throws IOException {
|
||||
this.out.write(b);
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传表单结束
|
||||
*
|
||||
* @throws IORuntimeException IO异常
|
||||
*/
|
||||
public void finish() throws IORuntimeException {
|
||||
if (!isFinish) {
|
||||
write(StrUtil.format("--{}--\r\n", boundary));
|
||||
this.isFinish = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
finish();
|
||||
IoUtil.close(this.out);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加Multipart表单的Resource数据项,支持包括{@link HttpResource}资源格式
|
||||
*
|
||||
* @param formFieldName 表单名
|
||||
* @param resource 资源
|
||||
* @throws IORuntimeException IO异常
|
||||
*/
|
||||
private void appendResource(String formFieldName, Resource resource) throws IORuntimeException {
|
||||
final String fileName = resource.getName();
|
||||
|
||||
// Content-Disposition
|
||||
if (null == fileName) {
|
||||
// Content-Disposition: form-data; name="参数名"[换行]
|
||||
write(StrUtil.format(CONTENT_DISPOSITION_TEMPLATE, formFieldName));
|
||||
} else {
|
||||
// Content-Disposition: form-data; name="参数名"; filename="文件名"[换行]
|
||||
write(StrUtil.format(CONTENT_DISPOSITION_FILE_TEMPLATE, formFieldName, fileName));
|
||||
}
|
||||
|
||||
// Content-Type
|
||||
if (resource instanceof HttpResource) {
|
||||
final String contentType = ((HttpResource) resource).getContentType();
|
||||
if (StrUtil.isNotBlank(contentType)) {
|
||||
// Content-Type: 类型[换行]
|
||||
write(StrUtil.format(CONTENT_TYPE_FILE_TEMPLATE, contentType));
|
||||
}
|
||||
} else if (StrUtil.isNotEmpty(fileName)) {
|
||||
// 根据name的扩展名指定互联网媒体类型,默认二进制流数据
|
||||
write(StrUtil.format(CONTENT_TYPE_FILE_TEMPLATE,
|
||||
HttpUtil.getMimeType(fileName, ContentType.OCTET_STREAM.getValue())));
|
||||
}
|
||||
|
||||
// 内容
|
||||
write("\r\n");
|
||||
resource.writeTo(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* part开始,写出:<br>
|
||||
* <pre>
|
||||
* --分隔符(boundary)[换行]
|
||||
* </pre>
|
||||
*/
|
||||
private void beginPart() {
|
||||
// --分隔符(boundary)[换行]
|
||||
write("--", boundary, StrUtil.CRLF);
|
||||
}
|
||||
|
||||
/**
|
||||
* 写出对象
|
||||
*
|
||||
* @param objs 写出的对象(转换为字符串)
|
||||
*/
|
||||
private void write(Object... objs) {
|
||||
IoUtil.write(this, this.charset, false, objs);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,189 @@
|
|||
package aiyh.utils.tool.cn.hutool.http;
|
||||
|
||||
/**
|
||||
* 返回状态码
|
||||
*
|
||||
* @author Looly
|
||||
*/
|
||||
interface Status {
|
||||
/**
|
||||
* HTTP Status-Code 200: OK.
|
||||
*/
|
||||
int HTTP_OK = 200;
|
||||
|
||||
/**
|
||||
* HTTP Status-Code 201: Created.
|
||||
*/
|
||||
int HTTP_CREATED = 201;
|
||||
|
||||
/**
|
||||
* HTTP Status-Code 202: Accepted.
|
||||
*/
|
||||
int HTTP_ACCEPTED = 202;
|
||||
|
||||
/**
|
||||
* HTTP Status-Code 203: Non-Authoritative Information.
|
||||
*/
|
||||
int HTTP_NOT_AUTHORITATIVE = 203;
|
||||
|
||||
/**
|
||||
* HTTP Status-Code 204: No Content.
|
||||
*/
|
||||
int HTTP_NO_CONTENT = 204;
|
||||
|
||||
/**
|
||||
* HTTP Status-Code 205: Reset Content.
|
||||
*/
|
||||
int HTTP_RESET = 205;
|
||||
|
||||
/**
|
||||
* HTTP Status-Code 206: Partial Content.
|
||||
*/
|
||||
int HTTP_PARTIAL = 206;
|
||||
|
||||
/* 3XX: relocation/redirect */
|
||||
|
||||
/**
|
||||
* HTTP Status-Code 300: Multiple Choices.
|
||||
*/
|
||||
int HTTP_MULT_CHOICE = 300;
|
||||
|
||||
/**
|
||||
* HTTP Status-Code 301: Moved Permanently.
|
||||
*/
|
||||
int HTTP_MOVED_PERM = 301;
|
||||
|
||||
/**
|
||||
* HTTP Status-Code 302: Temporary Redirect.
|
||||
*/
|
||||
int HTTP_MOVED_TEMP = 302;
|
||||
|
||||
/**
|
||||
* HTTP Status-Code 303: See Other.
|
||||
*/
|
||||
int HTTP_SEE_OTHER = 303;
|
||||
|
||||
/**
|
||||
* HTTP Status-Code 304: Not Modified.
|
||||
*/
|
||||
int HTTP_NOT_MODIFIED = 304;
|
||||
|
||||
/**
|
||||
* HTTP Status-Code 305: Use Proxy.
|
||||
*/
|
||||
int HTTP_USE_PROXY = 305;
|
||||
|
||||
/* 4XX: client error */
|
||||
|
||||
/**
|
||||
* HTTP Status-Code 400: Bad Request.
|
||||
*/
|
||||
int HTTP_BAD_REQUEST = 400;
|
||||
|
||||
/**
|
||||
* HTTP Status-Code 401: Unauthorized.
|
||||
*/
|
||||
int HTTP_UNAUTHORIZED = 401;
|
||||
|
||||
/**
|
||||
* HTTP Status-Code 402: Payment Required.
|
||||
*/
|
||||
int HTTP_PAYMENT_REQUIRED = 402;
|
||||
|
||||
/**
|
||||
* HTTP Status-Code 403: Forbidden.
|
||||
*/
|
||||
int HTTP_FORBIDDEN = 403;
|
||||
|
||||
/**
|
||||
* HTTP Status-Code 404: Not Found.
|
||||
*/
|
||||
int HTTP_NOT_FOUND = 404;
|
||||
|
||||
/**
|
||||
* HTTP Status-Code 405: Method Not Allowed.
|
||||
*/
|
||||
int HTTP_BAD_METHOD = 405;
|
||||
|
||||
/**
|
||||
* HTTP Status-Code 406: Not Acceptable.
|
||||
*/
|
||||
int HTTP_NOT_ACCEPTABLE = 406;
|
||||
|
||||
/**
|
||||
* HTTP Status-Code 407: Proxy Authentication Required.
|
||||
*/
|
||||
int HTTP_PROXY_AUTH = 407;
|
||||
|
||||
/**
|
||||
* HTTP Status-Code 408: Request Time-Out.
|
||||
*/
|
||||
int HTTP_CLIENT_TIMEOUT = 408;
|
||||
|
||||
/**
|
||||
* HTTP Status-Code 409: Conflict.
|
||||
*/
|
||||
int HTTP_CONFLICT = 409;
|
||||
|
||||
/**
|
||||
* HTTP Status-Code 410: Gone.
|
||||
*/
|
||||
int HTTP_GONE = 410;
|
||||
|
||||
/**
|
||||
* HTTP Status-Code 411: Length Required.
|
||||
*/
|
||||
int HTTP_LENGTH_REQUIRED = 411;
|
||||
|
||||
/**
|
||||
* HTTP Status-Code 412: Precondition Failed.
|
||||
*/
|
||||
int HTTP_PRECON_FAILED = 412;
|
||||
|
||||
/**
|
||||
* HTTP Status-Code 413: Request Entity Too Large.
|
||||
*/
|
||||
int HTTP_ENTITY_TOO_LARGE = 413;
|
||||
|
||||
/**
|
||||
* HTTP Status-Code 414: Request-URI Too Large.
|
||||
*/
|
||||
int HTTP_REQ_TOO_LONG = 414;
|
||||
|
||||
/**
|
||||
* HTTP Status-Code 415: Unsupported Media Type.
|
||||
*/
|
||||
int HTTP_UNSUPPORTED_TYPE = 415;
|
||||
|
||||
/* 5XX: server error */
|
||||
|
||||
/**
|
||||
* HTTP Status-Code 500: Internal Server Error.
|
||||
*/
|
||||
int HTTP_INTERNAL_ERROR = 500;
|
||||
|
||||
/**
|
||||
* HTTP Status-Code 501: Not Implemented.
|
||||
*/
|
||||
int HTTP_NOT_IMPLEMENTED = 501;
|
||||
|
||||
/**
|
||||
* HTTP Status-Code 502: Bad Gateway.
|
||||
*/
|
||||
int HTTP_BAD_GATEWAY = 502;
|
||||
|
||||
/**
|
||||
* HTTP Status-Code 503: Service Unavailable.
|
||||
*/
|
||||
int HTTP_UNAVAILABLE = 503;
|
||||
|
||||
/**
|
||||
* HTTP Status-Code 504: Gateway Timeout.
|
||||
*/
|
||||
int HTTP_GATEWAY_TIMEOUT = 504;
|
||||
|
||||
/**
|
||||
* HTTP Status-Code 505: HTTP Version Not Supported.
|
||||
*/
|
||||
int HTTP_VERSION = 505;
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
package aiyh.utils.tool.cn.hutool.http.body;
|
||||
|
||||
import aiyh.utils.tool.cn.hutool.core.io.IoUtil;
|
||||
|
||||
import java.io.OutputStream;
|
||||
|
||||
/**
|
||||
* bytes类型的Http request body,主要发送编码后的表单数据或rest body(如JSON或XML)
|
||||
*
|
||||
* @author looly
|
||||
* @since 5.7.17
|
||||
*/
|
||||
public class BytesBody implements RequestBody {
|
||||
|
||||
private final byte[] content;
|
||||
|
||||
/**
|
||||
* 创建 Http request body
|
||||
*
|
||||
* @param content body内容,编码后
|
||||
* @return BytesBody
|
||||
*/
|
||||
public static BytesBody create(byte[] content) {
|
||||
return new BytesBody(content);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param content Body内容,编码后
|
||||
*/
|
||||
public BytesBody(byte[] content) {
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(OutputStream out) {
|
||||
IoUtil.write(out, false, content);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
package aiyh.utils.tool.cn.hutool.http.body;
|
||||
|
||||
import aiyh.utils.tool.cn.hutool.core.net.url.UrlQuery;
|
||||
import aiyh.utils.tool.cn.hutool.core.util.StrUtil;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* application/x-www-form-urlencoded 类型请求body封装
|
||||
*
|
||||
* @author looly
|
||||
* @since 5.7.17
|
||||
*/
|
||||
public class FormUrlEncodedBody extends BytesBody {
|
||||
|
||||
/**
|
||||
* 创建 Http request body
|
||||
*
|
||||
* @param form 表单
|
||||
* @param charset 编码
|
||||
* @return FormUrlEncodedBody
|
||||
*/
|
||||
public static FormUrlEncodedBody create(Map<String, Object> form, Charset charset) {
|
||||
return new FormUrlEncodedBody(form, charset);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param form 表单
|
||||
* @param charset 编码
|
||||
*/
|
||||
public FormUrlEncodedBody(Map<String, Object> form, Charset charset) {
|
||||
super(StrUtil.bytes(UrlQuery.of(form, true).build(charset), charset));
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
package aiyh.utils.tool.cn.hutool.http.body;
|
||||
|
||||
import aiyh.utils.tool.cn.hutool.core.io.IoUtil;
|
||||
import aiyh.utils.tool.cn.hutool.core.map.MapUtil;
|
||||
import aiyh.utils.tool.cn.hutool.http.ContentType;
|
||||
import aiyh.utils.tool.cn.hutool.http.HttpGlobalConfig;
|
||||
import aiyh.utils.tool.cn.hutool.http.MultipartOutputStream;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Multipart/form-data数据的请求体封装<br>
|
||||
* 遵循RFC2388规范
|
||||
*
|
||||
* @author looly
|
||||
* @since 5.3.5
|
||||
*/
|
||||
public class MultipartBody implements RequestBody {
|
||||
|
||||
private static final String CONTENT_TYPE_MULTIPART_PREFIX = ContentType.MULTIPART.getValue() + "; boundary=";
|
||||
|
||||
/**
|
||||
* 存储表单数据
|
||||
*/
|
||||
private final Map<String, Object> form;
|
||||
/**
|
||||
* 编码
|
||||
*/
|
||||
private final Charset charset;
|
||||
/**
|
||||
* 边界
|
||||
*/
|
||||
private final String boundary = HttpGlobalConfig.getBoundary();
|
||||
|
||||
/**
|
||||
* 根据已有表单内容,构建MultipartBody
|
||||
*
|
||||
* @param form 表单
|
||||
* @param charset 编码
|
||||
* @return MultipartBody
|
||||
*/
|
||||
public static MultipartBody create(Map<String, Object> form, Charset charset) {
|
||||
return new MultipartBody(form, charset);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Multipart的Content-Type类型
|
||||
*
|
||||
* @return Multipart的Content-Type类型
|
||||
*/
|
||||
public String getContentType() {
|
||||
return CONTENT_TYPE_MULTIPART_PREFIX + boundary;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param form 表单
|
||||
* @param charset 编码
|
||||
*/
|
||||
public MultipartBody(Map<String, Object> form, Charset charset) {
|
||||
this.form = form;
|
||||
this.charset = charset;
|
||||
}
|
||||
|
||||
/**
|
||||
* 写出Multiparty数据,不关闭流
|
||||
*
|
||||
* @param out out流
|
||||
*/
|
||||
@Override
|
||||
public void write(OutputStream out) {
|
||||
final MultipartOutputStream stream = new MultipartOutputStream(out, this.charset, this.boundary);
|
||||
if (MapUtil.isNotEmpty(this.form)) {
|
||||
this.form.forEach(stream::write);
|
||||
}
|
||||
stream.finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
write(out);
|
||||
return IoUtil.toStr(out, this.charset);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
package aiyh.utils.tool.cn.hutool.http.body;
|
||||
|
||||
import aiyh.utils.tool.cn.hutool.core.io.IoUtil;
|
||||
|
||||
import java.io.OutputStream;
|
||||
|
||||
/**
|
||||
* 定义请求体接口
|
||||
*/
|
||||
public interface RequestBody {
|
||||
|
||||
/**
|
||||
* 写出数据,不关闭流
|
||||
*
|
||||
* @param out out流
|
||||
*/
|
||||
void write(OutputStream out);
|
||||
|
||||
/**
|
||||
* 写出并关闭{@link OutputStream}
|
||||
*
|
||||
* @param out {@link OutputStream}
|
||||
* @since 5.7.17
|
||||
*/
|
||||
default void writeClose(OutputStream out) {
|
||||
try {
|
||||
write(out);
|
||||
} finally {
|
||||
IoUtil.close(out);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
/**
|
||||
* 请求体封装实现
|
||||
*
|
||||
* @author looly
|
||||
*/
|
||||
package aiyh.utils.tool.cn.hutool.http.body;
|
|
@ -0,0 +1,109 @@
|
|||
package aiyh.utils.tool.cn.hutool.http.cookie;
|
||||
|
||||
import aiyh.utils.tool.cn.hutool.core.io.IORuntimeException;
|
||||
import aiyh.utils.tool.cn.hutool.core.util.URLUtil;
|
||||
import aiyh.utils.tool.cn.hutool.http.HttpConnection;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.CookieManager;
|
||||
import java.net.CookiePolicy;
|
||||
import java.net.HttpCookie;
|
||||
import java.net.URI;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 全局Cookie管理器,只针对Hutool请求有效
|
||||
*
|
||||
* @author Looly
|
||||
* @since 4.5.15
|
||||
*/
|
||||
public class GlobalCookieManager {
|
||||
|
||||
/** Cookie管理 */
|
||||
private static CookieManager cookieManager;
|
||||
|
||||
static {
|
||||
cookieManager = new CookieManager(new ThreadLocalCookieStore(), CookiePolicy.ACCEPT_ALL);
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义{@link CookieManager}
|
||||
*
|
||||
* @param customCookieManager 自定义的{@link CookieManager}
|
||||
*/
|
||||
public static void setCookieManager(CookieManager customCookieManager) {
|
||||
cookieManager = customCookieManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取全局{@link CookieManager}
|
||||
*
|
||||
* @return {@link CookieManager}
|
||||
*/
|
||||
public static CookieManager getCookieManager() {
|
||||
return cookieManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定域名下所有Cookie信息
|
||||
*
|
||||
* @param conn HTTP连接
|
||||
* @return Cookie信息列表
|
||||
* @since 4.6.9
|
||||
*/
|
||||
public static List<HttpCookie> getCookies(aiyh.utils.tool.cn.hutool.http.HttpConnection conn) {
|
||||
return cookieManager.getCookieStore().get(getURI(conn));
|
||||
}
|
||||
|
||||
/**
|
||||
* 将本地存储的Cookie信息附带到Http请求中,不覆盖用户定义好的Cookie
|
||||
*
|
||||
* @param conn {@link aiyh.utils.tool.cn.hutool.http.HttpConnection}
|
||||
*/
|
||||
public static void add(aiyh.utils.tool.cn.hutool.http.HttpConnection conn) {
|
||||
if (null == cookieManager) {
|
||||
// 全局Cookie管理器关闭
|
||||
return;
|
||||
}
|
||||
|
||||
Map<String, List<String>> cookieHeader;
|
||||
try {
|
||||
cookieHeader = cookieManager.get(getURI(conn), new HashMap<>(0));
|
||||
} catch (IOException e) {
|
||||
throw new IORuntimeException(e);
|
||||
}
|
||||
|
||||
// 不覆盖模式回填Cookie头,这样用户定义的Cookie将优先
|
||||
conn.header(cookieHeader, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 存储响应的Cookie信息到本地
|
||||
*
|
||||
* @param conn {@link aiyh.utils.tool.cn.hutool.http.HttpConnection}
|
||||
*/
|
||||
public static void store(aiyh.utils.tool.cn.hutool.http.HttpConnection conn) {
|
||||
if (null == cookieManager) {
|
||||
// 全局Cookie管理器关闭
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
cookieManager.put(getURI(conn), conn.headers());
|
||||
} catch (IOException e) {
|
||||
throw new IORuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取连接的URL中URI信息
|
||||
*
|
||||
* @param conn HttpConnection
|
||||
* @return URI
|
||||
*/
|
||||
private static URI getURI(HttpConnection conn) {
|
||||
return URLUtil.toURI(conn.getUrl());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
package aiyh.utils.tool.cn.hutool.http.cookie;
|
||||
|
||||
import java.net.CookieManager;
|
||||
import java.net.CookieStore;
|
||||
import java.net.HttpCookie;
|
||||
import java.net.URI;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 线程隔离的Cookie存储。多线程环境下Cookie隔离使用,防止Cookie覆盖<br>
|
||||
* <p>
|
||||
* 见:https://stackoverflow.com/questions/16305486/cookiemanager-for-multiple-threads
|
||||
*
|
||||
* @author looly
|
||||
* @since 4.1.18
|
||||
*/
|
||||
public class ThreadLocalCookieStore implements CookieStore {
|
||||
|
||||
private final static ThreadLocal<CookieStore> STORES = new ThreadLocal<CookieStore>() {
|
||||
@Override
|
||||
protected synchronized CookieStore initialValue() {
|
||||
/* InMemoryCookieStore */
|
||||
return (new CookieManager()).getCookieStore();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取本线程下的CookieStore
|
||||
*
|
||||
* @return CookieStore
|
||||
*/
|
||||
public CookieStore getCookieStore() {
|
||||
return STORES.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除当前线程的Cookie
|
||||
*
|
||||
* @return this
|
||||
*/
|
||||
public ThreadLocalCookieStore removeCurrent() {
|
||||
STORES.remove();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(URI uri, HttpCookie cookie) {
|
||||
getCookieStore().add(uri, cookie);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<HttpCookie> get(URI uri) {
|
||||
return getCookieStore().get(uri);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<HttpCookie> getCookies() {
|
||||
return getCookieStore().getCookies();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<URI> getURIs() {
|
||||
return getCookieStore().getURIs();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean remove(URI uri, HttpCookie cookie) {
|
||||
return getCookieStore().remove(uri, cookie);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeAll() {
|
||||
return getCookieStore().removeAll();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
/**
|
||||
* 自定义Cookie
|
||||
*
|
||||
* @author looly
|
||||
*/
|
||||
package aiyh.utils.tool.cn.hutool.http.cookie;
|
|
@ -0,0 +1,6 @@
|
|||
/**
|
||||
* Hutool-http针对JDK的HttpUrlConnection做一层封装,简化了HTTPS请求、文件上传、Cookie记忆等操作,使Http请求变得无比简单。
|
||||
*
|
||||
* @author looly
|
||||
*/
|
||||
package aiyh.utils.tool.cn.hutool.http;
|
|
@ -0,0 +1,57 @@
|
|||
package aiyh.utils.tool.cn.hutool.http.server;
|
||||
|
||||
import aiyh.utils.tool.cn.hutool.core.util.CharsetUtil;
|
||||
import com.sun.net.httpserver.HttpContext;
|
||||
import com.sun.net.httpserver.HttpExchange;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
/**
|
||||
* HttpServer公用对象,提供HttpExchange包装和公用方法
|
||||
*
|
||||
* @author looly
|
||||
* @since 5.2.6
|
||||
*/
|
||||
public class HttpServerBase implements Closeable {
|
||||
|
||||
final static Charset DEFAULT_CHARSET = CharsetUtil.CHARSET_UTF_8;
|
||||
|
||||
final HttpExchange httpExchange;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param httpExchange {@link HttpExchange}
|
||||
*/
|
||||
public HttpServerBase(HttpExchange httpExchange) {
|
||||
this.httpExchange = httpExchange;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取{@link HttpExchange}对象
|
||||
*
|
||||
* @return {@link HttpExchange}对象
|
||||
*/
|
||||
public HttpExchange getHttpExchange() {
|
||||
return this.httpExchange;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取{@link HttpContext}
|
||||
*
|
||||
* @return {@link HttpContext}
|
||||
* @since 5.5.7
|
||||
*/
|
||||
public HttpContext getHttpContext() {
|
||||
return getHttpExchange().getHttpContext();
|
||||
}
|
||||
|
||||
/**
|
||||
* 调用{@link HttpExchange#close()},关闭请求流和响应流
|
||||
*/
|
||||
@Override
|
||||
public void close() {
|
||||
this.httpExchange.close();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,442 @@
|
|||
package aiyh.utils.tool.cn.hutool.http.server;
|
||||
|
||||
import aiyh.utils.tool.cn.hutool.core.collection.CollUtil;
|
||||
import aiyh.utils.tool.cn.hutool.core.io.IORuntimeException;
|
||||
import aiyh.utils.tool.cn.hutool.core.io.IoUtil;
|
||||
import aiyh.utils.tool.cn.hutool.core.map.CaseInsensitiveMap;
|
||||
import aiyh.utils.tool.cn.hutool.core.map.multi.ListValueMap;
|
||||
import aiyh.utils.tool.cn.hutool.core.net.NetUtil;
|
||||
import aiyh.utils.tool.cn.hutool.core.net.multipart.MultipartFormData;
|
||||
import aiyh.utils.tool.cn.hutool.core.net.multipart.UploadSetting;
|
||||
import aiyh.utils.tool.cn.hutool.core.util.ArrayUtil;
|
||||
import aiyh.utils.tool.cn.hutool.core.util.CharsetUtil;
|
||||
import aiyh.utils.tool.cn.hutool.core.util.StrUtil;
|
||||
import aiyh.utils.tool.cn.hutool.http.Header;
|
||||
import aiyh.utils.tool.cn.hutool.http.HttpUtil;
|
||||
import aiyh.utils.tool.cn.hutool.http.Method;
|
||||
import aiyh.utils.tool.cn.hutool.http.useragent.UserAgent;
|
||||
import aiyh.utils.tool.cn.hutool.http.useragent.UserAgentUtil;
|
||||
import com.sun.net.httpserver.Headers;
|
||||
import com.sun.net.httpserver.HttpExchange;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.HttpCookie;
|
||||
import java.net.URI;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Http请求对象,对{@link HttpExchange}封装
|
||||
*
|
||||
* @author looly
|
||||
* @since 5.2.6
|
||||
*/
|
||||
public class HttpServerRequest extends HttpServerBase {
|
||||
|
||||
private Map<String, HttpCookie> cookieCache;
|
||||
private ListValueMap<String, String> paramsCache;
|
||||
private MultipartFormData multipartFormDataCache;
|
||||
private Charset charsetCache;
|
||||
private byte[] bodyCache;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param httpExchange {@link HttpExchange}
|
||||
*/
|
||||
public HttpServerRequest(HttpExchange httpExchange) {
|
||||
super(httpExchange);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得Http Method
|
||||
*
|
||||
* @return Http Method
|
||||
*/
|
||||
public String getMethod() {
|
||||
return this.httpExchange.getRequestMethod();
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否为GET请求
|
||||
*
|
||||
* @return 是否为GET请求
|
||||
*/
|
||||
public boolean isGetMethod() {
|
||||
return aiyh.utils.tool.cn.hutool.http.Method.GET.name().equalsIgnoreCase(getMethod());
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否为POST请求
|
||||
*
|
||||
* @return 是否为POST请求
|
||||
*/
|
||||
public boolean isPostMethod() {
|
||||
return Method.POST.name().equalsIgnoreCase(getMethod());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得请求URI
|
||||
*
|
||||
* @return 请求URI
|
||||
*/
|
||||
public URI getURI() {
|
||||
return this.httpExchange.getRequestURI();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得请求路径Path
|
||||
*
|
||||
* @return 请求路径
|
||||
*/
|
||||
public String getPath() {
|
||||
return getURI().getPath();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取请求参数
|
||||
*
|
||||
* @return 参数字符串
|
||||
*/
|
||||
public String getQuery() {
|
||||
return getURI().getQuery();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得请求header中的信息
|
||||
*
|
||||
* @return header值
|
||||
*/
|
||||
public Headers getHeaders() {
|
||||
return this.httpExchange.getRequestHeaders();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得请求header中的信息
|
||||
*
|
||||
* @param headerKey 头信息的KEY
|
||||
* @return header值
|
||||
*/
|
||||
public String getHeader(aiyh.utils.tool.cn.hutool.http.Header headerKey) {
|
||||
return getHeader(headerKey.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得请求header中的信息
|
||||
*
|
||||
* @param headerKey 头信息的KEY
|
||||
* @return header值
|
||||
*/
|
||||
public String getHeader(String headerKey) {
|
||||
return getHeaders().getFirst(headerKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得请求header中的信息
|
||||
*
|
||||
* @param headerKey 头信息的KEY
|
||||
* @param charset 字符集
|
||||
* @return header值
|
||||
*/
|
||||
public String getHeader(String headerKey, Charset charset) {
|
||||
final String header = getHeader(headerKey);
|
||||
if (null != header) {
|
||||
return CharsetUtil.convert(header, CharsetUtil.CHARSET_ISO_8859_1, charset);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Content-Type头信息
|
||||
*
|
||||
* @return Content-Type头信息
|
||||
*/
|
||||
public String getContentType() {
|
||||
return getHeader(aiyh.utils.tool.cn.hutool.http.Header.CONTENT_TYPE);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取编码,获取失败默认使用UTF-8,获取规则如下:
|
||||
*
|
||||
* <pre>
|
||||
* 1、从Content-Type头中获取编码,类似于:text/html;charset=utf-8
|
||||
* </pre>
|
||||
*
|
||||
* @return 编码,默认UTF-8
|
||||
*/
|
||||
public Charset getCharset() {
|
||||
if (null == this.charsetCache) {
|
||||
final String contentType = getContentType();
|
||||
final String charsetStr = aiyh.utils.tool.cn.hutool.http.HttpUtil.getCharset(contentType);
|
||||
this.charsetCache = CharsetUtil.parse(charsetStr, DEFAULT_CHARSET);
|
||||
}
|
||||
|
||||
return this.charsetCache;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得User-Agent
|
||||
*
|
||||
* @return User-Agent字符串
|
||||
*/
|
||||
public String getUserAgentStr() {
|
||||
return getHeader(aiyh.utils.tool.cn.hutool.http.Header.USER_AGENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得User-Agent,未识别返回null
|
||||
*
|
||||
* @return User-Agent字符串,未识别返回null
|
||||
*/
|
||||
public UserAgent getUserAgent() {
|
||||
return UserAgentUtil.parse(getUserAgentStr());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得Cookie信息字符串
|
||||
*
|
||||
* @return cookie字符串
|
||||
*/
|
||||
public String getCookiesStr() {
|
||||
return getHeader(Header.COOKIE);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得Cookie信息列表
|
||||
*
|
||||
* @return Cookie信息列表
|
||||
*/
|
||||
public Collection<HttpCookie> getCookies() {
|
||||
return getCookieMap().values();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得Cookie信息Map,键为Cookie名,值为HttpCookie对象
|
||||
*
|
||||
* @return Cookie信息Map
|
||||
*/
|
||||
public Map<String, HttpCookie> getCookieMap() {
|
||||
if (null == this.cookieCache) {
|
||||
cookieCache = Collections.unmodifiableMap(CollUtil.toMap(
|
||||
NetUtil.parseCookies(getCookiesStr()),
|
||||
new CaseInsensitiveMap<>(),
|
||||
HttpCookie::getName));
|
||||
}
|
||||
return cookieCache;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得指定Cookie名对应的HttpCookie对象
|
||||
*
|
||||
* @param cookieName Cookie名
|
||||
* @return HttpCookie对象
|
||||
*/
|
||||
public HttpCookie getCookie(String cookieName) {
|
||||
return getCookieMap().get(cookieName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否为Multipart类型表单,此类型表单用于文件上传
|
||||
*
|
||||
* @return 是否为Multipart类型表单,此类型表单用于文件上传
|
||||
*/
|
||||
public boolean isMultipart() {
|
||||
if (!isPostMethod()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final String contentType = getContentType();
|
||||
if (StrUtil.isBlank(contentType)) {
|
||||
return false;
|
||||
}
|
||||
return contentType.toLowerCase().startsWith("multipart/");
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取请求体文本,可以是form表单、json、xml等任意内容<br>
|
||||
* 使用{@link #getCharset()}判断编码,判断失败使用UTF-8编码
|
||||
*
|
||||
* @return 请求
|
||||
*/
|
||||
public String getBody() {
|
||||
return getBody(getCharset());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取请求体文本,可以是form表单、json、xml等任意内容
|
||||
*
|
||||
* @param charset 编码
|
||||
* @return 请求
|
||||
*/
|
||||
public String getBody(Charset charset) {
|
||||
return StrUtil.str(getBodyBytes(), charset);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取body的bytes数组
|
||||
*
|
||||
* @return body的bytes数组
|
||||
*/
|
||||
public byte[] getBodyBytes() {
|
||||
if (null == this.bodyCache) {
|
||||
this.bodyCache = IoUtil.readBytes(getBodyStream(), true);
|
||||
}
|
||||
return this.bodyCache;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取请求体的流,流中可以读取请求内容,包括请求表单数据或文件上传数据
|
||||
*
|
||||
* @return 流
|
||||
*/
|
||||
public InputStream getBodyStream() {
|
||||
return this.httpExchange.getRequestBody();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定名称的参数值,取第一个值
|
||||
*
|
||||
* @param name 参数名
|
||||
* @return 参数值
|
||||
* @since 5.5.8
|
||||
*/
|
||||
public String getParam(String name) {
|
||||
return getParams().get(name, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定名称的参数值
|
||||
*
|
||||
* @param name 参数名
|
||||
* @return 参数值
|
||||
* @since 5.5.8
|
||||
*/
|
||||
public List<String> getParams(String name) {
|
||||
return getParams().get(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取参数Map
|
||||
*
|
||||
* @return 参数map
|
||||
*/
|
||||
public ListValueMap<String, String> getParams() {
|
||||
if (null == this.paramsCache) {
|
||||
this.paramsCache = new ListValueMap<>();
|
||||
final Charset charset = getCharset();
|
||||
|
||||
// 解析URL中的参数
|
||||
final String query = getQuery();
|
||||
if (StrUtil.isNotBlank(query)) {
|
||||
this.paramsCache.putAll(aiyh.utils.tool.cn.hutool.http.HttpUtil.decodeParams(query, charset, false));
|
||||
}
|
||||
|
||||
// 解析multipart中的参数
|
||||
if (isMultipart()) {
|
||||
this.paramsCache.putAll(getMultipart().getParamListMap());
|
||||
} else {
|
||||
// 解析body中的参数
|
||||
final String body = getBody();
|
||||
if (StrUtil.isNotBlank(body)) {
|
||||
this.paramsCache.putAll(HttpUtil.decodeParams(body, charset, true));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return this.paramsCache;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取客户端IP
|
||||
*
|
||||
* <p>
|
||||
* 默认检测的Header:
|
||||
*
|
||||
* <pre>
|
||||
* 1、X-Forwarded-For
|
||||
* 2、X-Real-IP
|
||||
* 3、Proxy-Client-IP
|
||||
* 4、WL-Proxy-Client-IP
|
||||
* </pre>
|
||||
*
|
||||
* <p>
|
||||
* otherHeaderNames参数用于自定义检测的Header<br>
|
||||
* 需要注意的是,使用此方法获取的客户IP地址必须在Http服务器(例如Nginx)中配置头信息,否则容易造成IP伪造。
|
||||
* </p>
|
||||
*
|
||||
* @param otherHeaderNames 其他自定义头文件,通常在Http服务器(例如Nginx)中配置
|
||||
* @return IP地址
|
||||
*/
|
||||
public String getClientIP(String... otherHeaderNames) {
|
||||
String[] headers = {"X-Forwarded-For", "X-Real-IP", "Proxy-Client-IP", "WL-Proxy-Client-IP", "HTTP_CLIENT_IP", "HTTP_X_FORWARDED_FOR"};
|
||||
if (ArrayUtil.isNotEmpty(otherHeaderNames)) {
|
||||
headers = ArrayUtil.addAll(headers, otherHeaderNames);
|
||||
}
|
||||
|
||||
return getClientIPByHeader(headers);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取客户端IP
|
||||
*
|
||||
* <p>
|
||||
* headerNames参数用于自定义检测的Header<br>
|
||||
* 需要注意的是,使用此方法获取的客户IP地址必须在Http服务器(例如Nginx)中配置头信息,否则容易造成IP伪造。
|
||||
* </p>
|
||||
*
|
||||
* @param headerNames 自定义头,通常在Http服务器(例如Nginx)中配置
|
||||
* @return IP地址
|
||||
* @since 4.4.1
|
||||
*/
|
||||
public String getClientIPByHeader(String... headerNames) {
|
||||
String ip;
|
||||
for (String header : headerNames) {
|
||||
ip = getHeader(header);
|
||||
if (!NetUtil.isUnknown(ip)) {
|
||||
return NetUtil.getMultistageReverseProxyIp(ip);
|
||||
}
|
||||
}
|
||||
|
||||
ip = this.httpExchange.getRemoteAddress().getHostName();
|
||||
return NetUtil.getMultistageReverseProxyIp(ip);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得MultiPart表单内容,多用于获得上传的文件
|
||||
*
|
||||
* @return MultipartFormData
|
||||
* @throws IORuntimeException IO异常
|
||||
* @since 5.3.0
|
||||
*/
|
||||
public MultipartFormData getMultipart() throws IORuntimeException {
|
||||
if (null == this.multipartFormDataCache) {
|
||||
this.multipartFormDataCache = parseMultipart(new UploadSetting());
|
||||
}
|
||||
return this.multipartFormDataCache;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得multipart/form-data 表单内容<br>
|
||||
* 包括文件和普通表单数据<br>
|
||||
* 在同一次请求中,此方法只能被执行一次!
|
||||
*
|
||||
* @param uploadSetting 上传文件的设定,包括最大文件大小、保存在内存的边界大小、临时目录、扩展名限定等
|
||||
* @return MultiPart表单
|
||||
* @throws IORuntimeException IO异常
|
||||
* @since 5.3.0
|
||||
*/
|
||||
public MultipartFormData parseMultipart(UploadSetting uploadSetting) throws IORuntimeException {
|
||||
final MultipartFormData formData = new MultipartFormData(uploadSetting);
|
||||
try {
|
||||
formData.parseRequestStream(getBodyStream(), getCharset());
|
||||
} catch (IOException e) {
|
||||
throw new IORuntimeException(e);
|
||||
}
|
||||
|
||||
return formData;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,429 @@
|
|||
package aiyh.utils.tool.cn.hutool.http.server;
|
||||
|
||||
import aiyh.utils.tool.cn.hutool.core.io.FileUtil;
|
||||
import aiyh.utils.tool.cn.hutool.core.io.IORuntimeException;
|
||||
import aiyh.utils.tool.cn.hutool.core.io.IoUtil;
|
||||
import aiyh.utils.tool.cn.hutool.core.util.ObjectUtil;
|
||||
import aiyh.utils.tool.cn.hutool.core.util.StrUtil;
|
||||
import aiyh.utils.tool.cn.hutool.core.util.URLUtil;
|
||||
import aiyh.utils.tool.cn.hutool.http.ContentType;
|
||||
import aiyh.utils.tool.cn.hutool.http.Header;
|
||||
import aiyh.utils.tool.cn.hutool.http.HttpStatus;
|
||||
import aiyh.utils.tool.cn.hutool.http.HttpUtil;
|
||||
import com.sun.net.httpserver.Headers;
|
||||
import com.sun.net.httpserver.HttpExchange;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Http响应对象,用于写出数据到客户端
|
||||
*/
|
||||
public class HttpServerResponse extends HttpServerBase {
|
||||
|
||||
private Charset charset;
|
||||
/**
|
||||
* 是否已经发送了Http状态码,如果没有,提前写出状态码
|
||||
*/
|
||||
private boolean isSendCode;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param httpExchange {@link HttpExchange}
|
||||
*/
|
||||
public HttpServerResponse(HttpExchange httpExchange) {
|
||||
super(httpExchange);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送HTTP状态码,Content-Length为0不定长度,会输出Transfer-encoding: chunked
|
||||
*
|
||||
* @param httpStatusCode HTTP状态码,见HttpStatus
|
||||
* @return this
|
||||
*/
|
||||
public HttpServerResponse send(int httpStatusCode) {
|
||||
return send(httpStatusCode, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送成功状态码
|
||||
*
|
||||
* @return this
|
||||
*/
|
||||
public HttpServerResponse sendOk() {
|
||||
return send(aiyh.utils.tool.cn.hutool.http.HttpStatus.HTTP_OK);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送成功状态码
|
||||
*
|
||||
* @param bodyLength 响应体长度,默认0表示不定长度,会输出Transfer-encoding: chunked
|
||||
* @return this
|
||||
* @since 5.5.7
|
||||
*/
|
||||
public HttpServerResponse sendOk(int bodyLength) {
|
||||
return send(aiyh.utils.tool.cn.hutool.http.HttpStatus.HTTP_OK, bodyLength);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送404错误页
|
||||
*
|
||||
* @param content 错误页页面内容,默认text/html类型
|
||||
* @return this
|
||||
*/
|
||||
public HttpServerResponse send404(String content) {
|
||||
return sendError(HttpStatus.HTTP_NOT_FOUND, content);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送错误页
|
||||
*
|
||||
* @param errorCode HTTP错误状态码,见HttpStatus
|
||||
* @param content 错误页页面内容,默认text/html类型
|
||||
* @return this
|
||||
*/
|
||||
public HttpServerResponse sendError(int errorCode, String content) {
|
||||
send(errorCode);
|
||||
setContentType(aiyh.utils.tool.cn.hutool.http.ContentType.TEXT_HTML.toString());
|
||||
return write(content);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送HTTP状态码
|
||||
*
|
||||
* @param httpStatusCode HTTP状态码,见HttpStatus
|
||||
* @param bodyLength 响应体长度,默认0表示不定长度,会输出Transfer-encoding: chunked
|
||||
* @return this
|
||||
*/
|
||||
public HttpServerResponse send(int httpStatusCode, long bodyLength) {
|
||||
if (this.isSendCode) {
|
||||
throw new IORuntimeException("Http status code has been send!");
|
||||
}
|
||||
|
||||
try {
|
||||
this.httpExchange.sendResponseHeaders(httpStatusCode, bodyLength);
|
||||
} catch (IOException e) {
|
||||
throw new IORuntimeException(e);
|
||||
}
|
||||
|
||||
this.isSendCode = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得所有响应头,获取后可以添加新的响应头
|
||||
*
|
||||
* @return 响应头
|
||||
*/
|
||||
public Headers getHeaders() {
|
||||
return this.httpExchange.getResponseHeaders();
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加响应头,如果已经存在,则追加
|
||||
*
|
||||
* @param header 头key
|
||||
* @param value 值
|
||||
* @return this
|
||||
*/
|
||||
public HttpServerResponse addHeader(String header, String value) {
|
||||
getHeaders().add(header, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置响应头,如果已经存在,则覆盖
|
||||
*
|
||||
* @param header 头key
|
||||
* @param value 值
|
||||
* @return this
|
||||
*/
|
||||
public HttpServerResponse setHeader(aiyh.utils.tool.cn.hutool.http.Header header, String value) {
|
||||
return setHeader(header.getValue(), value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置响应头,如果已经存在,则覆盖
|
||||
*
|
||||
* @param header 头key
|
||||
* @param value 值
|
||||
* @return this
|
||||
*/
|
||||
public HttpServerResponse setHeader(String header, String value) {
|
||||
getHeaders().set(header, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置响应头,如果已经存在,则覆盖
|
||||
*
|
||||
* @param header 头key
|
||||
* @param value 值列表
|
||||
* @return this
|
||||
*/
|
||||
public HttpServerResponse setHeader(String header, List<String> value) {
|
||||
getHeaders().put(header, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置所有响应头,如果已经存在,则覆盖
|
||||
*
|
||||
* @param headers 响应头map
|
||||
* @return this
|
||||
*/
|
||||
public HttpServerResponse setHeaders(Map<String, List<String>> headers) {
|
||||
getHeaders().putAll(headers);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置Content-Type头,类似于:text/html;charset=utf-8<br>
|
||||
* 如果用户传入的信息无charset信息,自动根据charset补充,charset设置见{@link #setCharset(Charset)}
|
||||
*
|
||||
* @param contentType Content-Type头内容
|
||||
* @return this
|
||||
*/
|
||||
public HttpServerResponse setContentType(String contentType) {
|
||||
if (null != contentType && null != this.charset) {
|
||||
if (!contentType.contains(";charset=")) {
|
||||
contentType = ContentType.build(contentType, this.charset);
|
||||
}
|
||||
}
|
||||
|
||||
return setHeader(aiyh.utils.tool.cn.hutool.http.Header.CONTENT_TYPE, contentType);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置Content-Length头
|
||||
*
|
||||
* @param contentLength Content-Length头内容
|
||||
* @return this
|
||||
*/
|
||||
public HttpServerResponse setContentLength(long contentLength) {
|
||||
return setHeader(aiyh.utils.tool.cn.hutool.http.Header.CONTENT_LENGTH, String.valueOf(contentLength));
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置响应的编码
|
||||
*
|
||||
* @param charset 编码
|
||||
* @return this
|
||||
*/
|
||||
public HttpServerResponse setCharset(Charset charset) {
|
||||
this.charset = charset;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置属性
|
||||
*
|
||||
* @param name 属性名
|
||||
* @param value 属性值
|
||||
* @return this
|
||||
*/
|
||||
public HttpServerResponse setAttr(String name, Object value) {
|
||||
this.httpExchange.setAttribute(name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取响应数据流
|
||||
*
|
||||
* @return 响应数据流
|
||||
*/
|
||||
public OutputStream getOut() {
|
||||
if (!this.isSendCode) {
|
||||
sendOk();
|
||||
}
|
||||
return this.httpExchange.getResponseBody();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取响应数据流
|
||||
*
|
||||
* @return 响应数据流
|
||||
*/
|
||||
public PrintWriter getWriter() {
|
||||
final Charset charset = ObjectUtil.defaultIfNull(this.charset, DEFAULT_CHARSET);
|
||||
return new PrintWriter(new OutputStreamWriter(getOut(), charset));
|
||||
}
|
||||
|
||||
/**
|
||||
* 写出数据到客户端
|
||||
*
|
||||
* @param data 数据
|
||||
* @param contentType Content-Type类型
|
||||
* @return this
|
||||
*/
|
||||
public HttpServerResponse write(String data, String contentType) {
|
||||
setContentType(contentType);
|
||||
return write(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 写出数据到客户端
|
||||
*
|
||||
* @param data 数据
|
||||
* @return this
|
||||
*/
|
||||
public HttpServerResponse write(String data) {
|
||||
final Charset charset = ObjectUtil.defaultIfNull(this.charset, DEFAULT_CHARSET);
|
||||
return write(StrUtil.bytes(data, charset));
|
||||
}
|
||||
|
||||
/**
|
||||
* 写出数据到客户端
|
||||
*
|
||||
* @param data 数据
|
||||
* @param contentType 返回的类型
|
||||
* @return this
|
||||
*/
|
||||
public HttpServerResponse write(byte[] data, String contentType) {
|
||||
setContentType(contentType);
|
||||
return write(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 写出数据到客户端
|
||||
*
|
||||
* @param data 数据
|
||||
* @return this
|
||||
*/
|
||||
public HttpServerResponse write(byte[] data) {
|
||||
final ByteArrayInputStream in = new ByteArrayInputStream(data);
|
||||
return write(in, in.available());
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回数据给客户端
|
||||
*
|
||||
* @param in 需要返回客户端的内容
|
||||
* @param contentType 返回的类型
|
||||
* @return this
|
||||
* @since 5.2.6
|
||||
*/
|
||||
public HttpServerResponse write(InputStream in, String contentType) {
|
||||
return write(in, 0, contentType);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回数据给客户端
|
||||
*
|
||||
* @param in 需要返回客户端的内容
|
||||
* @param length 内容长度,默认0表示不定长度,会输出Transfer-encoding: chunked
|
||||
* @param contentType 返回的类型
|
||||
* @return this
|
||||
* @since 5.2.7
|
||||
*/
|
||||
public HttpServerResponse write(InputStream in, int length, String contentType) {
|
||||
setContentType(contentType);
|
||||
return write(in, length);
|
||||
}
|
||||
|
||||
/**
|
||||
* 写出数据到客户端
|
||||
*
|
||||
* @param in 数据流
|
||||
* @return this
|
||||
*/
|
||||
public HttpServerResponse write(InputStream in) {
|
||||
return write(in, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 写出数据到客户端
|
||||
*
|
||||
* @param in 数据流
|
||||
* @param length 指定响应内容长度,默认0表示不定长度,会输出Transfer-encoding: chunked
|
||||
* @return this
|
||||
*/
|
||||
public HttpServerResponse write(InputStream in, int length) {
|
||||
if (!isSendCode) {
|
||||
sendOk(Math.max(0, length));
|
||||
}
|
||||
OutputStream out = null;
|
||||
try {
|
||||
out = this.httpExchange.getResponseBody();
|
||||
IoUtil.copy(in, out);
|
||||
} finally {
|
||||
IoUtil.close(out);
|
||||
IoUtil.close(in);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回文件给客户端(文件下载)
|
||||
*
|
||||
* @param file 写出的文件对象
|
||||
* @return this
|
||||
* @since 5.2.6
|
||||
*/
|
||||
public HttpServerResponse write(File file) {
|
||||
return write(file, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回文件给客户端(文件下载)
|
||||
*
|
||||
* @param file 写出的文件对象
|
||||
* @param fileName 文件名
|
||||
* @return this
|
||||
* @since 5.5.8
|
||||
*/
|
||||
public HttpServerResponse write(File file, String fileName) {
|
||||
final long fileSize = file.length();
|
||||
if (fileSize > Integer.MAX_VALUE) {
|
||||
throw new IllegalArgumentException("File size is too bigger than " + Integer.MAX_VALUE);
|
||||
}
|
||||
|
||||
if (StrUtil.isBlank(fileName)) {
|
||||
fileName = file.getName();
|
||||
}
|
||||
final String contentType = ObjectUtil.defaultIfNull(HttpUtil.getMimeType(fileName), "application/octet-stream");
|
||||
BufferedInputStream in = null;
|
||||
try {
|
||||
in = FileUtil.getInputStream(file);
|
||||
write(in, (int) fileSize, contentType, fileName);
|
||||
} finally {
|
||||
IoUtil.close(in);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回文件数据给客户端(文件下载)
|
||||
*
|
||||
* @param in 需要返回客户端的内容
|
||||
* @param contentType 返回的类型
|
||||
* @param fileName 文件名
|
||||
* @since 5.2.6
|
||||
*/
|
||||
public void write(InputStream in, String contentType, String fileName) {
|
||||
write(in, 0, contentType, fileName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回文件数据给客户端(文件下载)
|
||||
*
|
||||
* @param in 需要返回客户端的内容
|
||||
* @param length 长度
|
||||
* @param contentType 返回的类型
|
||||
* @param fileName 文件名
|
||||
* @return this
|
||||
* @since 5.2.7
|
||||
*/
|
||||
public HttpServerResponse write(InputStream in, int length, String contentType, String fileName) {
|
||||
final Charset charset = ObjectUtil.defaultIfNull(this.charset, DEFAULT_CHARSET);
|
||||
|
||||
if (!contentType.startsWith("text/")) {
|
||||
// 非文本类型数据直接走下载
|
||||
setHeader(Header.CONTENT_DISPOSITION, StrUtil.format("attachment;filename={}", URLUtil.encode(fileName, charset)));
|
||||
}
|
||||
return write(in, length, contentType);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,226 @@
|
|||
package aiyh.utils.tool.cn.hutool.http.server;
|
||||
|
||||
import aiyh.utils.tool.cn.hutool.core.io.IORuntimeException;
|
||||
import aiyh.utils.tool.cn.hutool.core.lang.Console;
|
||||
import aiyh.utils.tool.cn.hutool.core.thread.GlobalThreadPool;
|
||||
import aiyh.utils.tool.cn.hutool.core.util.StrUtil;
|
||||
import aiyh.utils.tool.cn.hutool.http.server.action.Action;
|
||||
import aiyh.utils.tool.cn.hutool.http.server.action.RootAction;
|
||||
import aiyh.utils.tool.cn.hutool.http.server.filter.HttpFilter;
|
||||
import aiyh.utils.tool.cn.hutool.http.server.filter.SimpleFilter;
|
||||
import aiyh.utils.tool.cn.hutool.http.server.handler.ActionHandler;
|
||||
import com.sun.net.httpserver.*;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
/**
|
||||
* 简易Http服务器,基于{@link HttpServer}
|
||||
*
|
||||
* @author looly
|
||||
* @since 5.2.5
|
||||
*/
|
||||
public class SimpleServer {
|
||||
|
||||
private final HttpServer server;
|
||||
private final List<Filter> filters;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param port 监听端口
|
||||
*/
|
||||
public SimpleServer(int port) {
|
||||
this(new InetSocketAddress(port));
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param hostname 监听地址
|
||||
* @param port 监听端口
|
||||
*/
|
||||
public SimpleServer(String hostname, int port) {
|
||||
this(new InetSocketAddress(hostname, port));
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param address 监听地址
|
||||
*/
|
||||
public SimpleServer(InetSocketAddress address) {
|
||||
this(address, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param address 监听地址
|
||||
* @param configurator https配置信息,用于使用自定义SSL(TLS)证书等
|
||||
*/
|
||||
public SimpleServer(InetSocketAddress address, HttpsConfigurator configurator) {
|
||||
try {
|
||||
if (null != configurator) {
|
||||
final HttpsServer server = HttpsServer.create(address, 0);
|
||||
server.setHttpsConfigurator(configurator);
|
||||
this.server = server;
|
||||
} else {
|
||||
this.server = HttpServer.create(address, 0);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new IORuntimeException(e);
|
||||
}
|
||||
setExecutor(GlobalThreadPool.getExecutor());
|
||||
filters = new ArrayList<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* 增加请求过滤器,此过滤器对所有请求有效<br>
|
||||
* 此方法需在以下方法前之前调用:
|
||||
*
|
||||
* <ul>
|
||||
* <li>{@link #setRoot(File)} </li>
|
||||
* <li>{@link #setRoot(String)} </li>
|
||||
* <li>{@link #createContext(String, HttpHandler)} </li>
|
||||
* <li>{@link #addHandler(String, HttpHandler)}</li>
|
||||
* <li>{@link #addAction(String, Action)} </li>
|
||||
* </ul>
|
||||
*
|
||||
* @param filter {@link Filter} 请求过滤器
|
||||
* @return this
|
||||
* @since 5.5.7
|
||||
*/
|
||||
public SimpleServer addFilter(Filter filter) {
|
||||
this.filters.add(filter);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 增加请求过滤器,此过滤器对所有请求有效<br>
|
||||
* 此方法需在以下方法前之前调用:
|
||||
*
|
||||
* <ul>
|
||||
* <li>{@link #setRoot(File)} </li>
|
||||
* <li>{@link #setRoot(String)} </li>
|
||||
* <li>{@link #createContext(String, HttpHandler)} </li>
|
||||
* <li>{@link #addHandler(String, HttpHandler)}</li>
|
||||
* <li>{@link #addAction(String, Action)} </li>
|
||||
* </ul>
|
||||
*
|
||||
* @param filter {@link Filter} 请求过滤器
|
||||
* @return this
|
||||
* @since 5.5.7
|
||||
*/
|
||||
public SimpleServer addFilter(HttpFilter filter) {
|
||||
return addFilter(new SimpleFilter() {
|
||||
@Override
|
||||
public void doFilter(HttpExchange httpExchange, Chain chain) throws IOException {
|
||||
filter.doFilter(new HttpServerRequest(httpExchange), new HttpServerResponse(httpExchange), chain);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 增加请求处理规则
|
||||
*
|
||||
* @param path 路径,例如:/a/b 或者 a/b
|
||||
* @param handler 处理器,包括请求和响应处理
|
||||
* @return this
|
||||
* @see #createContext(String, HttpHandler)
|
||||
*/
|
||||
public SimpleServer addHandler(String path, HttpHandler handler) {
|
||||
createContext(path, handler);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建请求映射上下文,创建后,用户访问指定路径可使用{@link HttpHandler} 中的规则进行处理
|
||||
*
|
||||
* @param path 路径,例如:/a/b 或者 a/b
|
||||
* @param handler 处理器,包括请求和响应处理
|
||||
* @return {@link HttpContext}
|
||||
* @since 5.5.7
|
||||
*/
|
||||
public HttpContext createContext(String path, HttpHandler handler) {
|
||||
// 非/开头的路径会报错
|
||||
path = StrUtil.addPrefixIfNot(path, StrUtil.SLASH);
|
||||
final HttpContext context = this.server.createContext(path, handler);
|
||||
// 增加整体过滤器
|
||||
context.getFilters().addAll(this.filters);
|
||||
return context;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置根目录,默认的页面从root目录中读取解析返回
|
||||
*
|
||||
* @param root 路径
|
||||
* @return this
|
||||
*/
|
||||
public SimpleServer setRoot(String root) {
|
||||
return setRoot(new File(root));
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置根目录,默认的页面从root目录中读取解析返回
|
||||
*
|
||||
* @param root 路径
|
||||
* @return this
|
||||
*/
|
||||
public SimpleServer setRoot(File root) {
|
||||
return addAction("/", new RootAction(root));
|
||||
}
|
||||
|
||||
/**
|
||||
* 增加请求处理规则
|
||||
*
|
||||
* @param path 路径
|
||||
* @param action 处理器
|
||||
* @return this
|
||||
*/
|
||||
public SimpleServer addAction(String path, Action action) {
|
||||
return addHandler(path, new ActionHandler(action));
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置自定义线程池
|
||||
*
|
||||
* @param executor {@link Executor}
|
||||
* @return this
|
||||
*/
|
||||
public SimpleServer setExecutor(Executor executor) {
|
||||
this.server.setExecutor(executor);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得原始HttpServer对象
|
||||
*
|
||||
* @return {@link HttpServer}
|
||||
*/
|
||||
public HttpServer getRawServer() {
|
||||
return this.server;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取服务器地址信息
|
||||
*
|
||||
* @return {@link InetSocketAddress}
|
||||
*/
|
||||
public InetSocketAddress getAddress() {
|
||||
return this.server.getAddress();
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动Http服务器,启动后会阻塞当前线程
|
||||
*/
|
||||
public void start() {
|
||||
final InetSocketAddress address = getAddress();
|
||||
Console.log("Hutool Simple Http Server listen on 【{}:{}】", address.getHostName(), address.getPort());
|
||||
this.server.start();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
package aiyh.utils.tool.cn.hutool.http.server.action;
|
||||
|
||||
import aiyh.utils.tool.cn.hutool.http.server.HttpServerRequest;
|
||||
import aiyh.utils.tool.cn.hutool.http.server.HttpServerResponse;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* 请求处理接口<br>
|
||||
* 当用户请求某个Path,则调用相应Action的doAction方法
|
||||
*
|
||||
* @author Looly
|
||||
* @since 5.2.6
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface Action {
|
||||
|
||||
/**
|
||||
* 处理请求
|
||||
*
|
||||
* @param request 请求对象
|
||||
* @param response 响应对象
|
||||
* @throws IOException IO异常
|
||||
*/
|
||||
void doAction(HttpServerRequest request, HttpServerResponse response) throws IOException;
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
package aiyh.utils.tool.cn.hutool.http.server.action;
|
||||
|
||||
import aiyh.utils.tool.cn.hutool.core.collection.CollUtil;
|
||||
import aiyh.utils.tool.cn.hutool.core.io.FileUtil;
|
||||
import aiyh.utils.tool.cn.hutool.http.server.HttpServerRequest;
|
||||
import aiyh.utils.tool.cn.hutool.http.server.HttpServerResponse;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 默认的处理器,通过解析用户传入的path,找到网页根目录下对应文件后返回
|
||||
*
|
||||
* @author looly
|
||||
* @since 5.2.6
|
||||
*/
|
||||
public class RootAction implements Action {
|
||||
|
||||
public static final String DEFAULT_INDEX_FILE_NAME = "index.html";
|
||||
|
||||
private final File rootDir;
|
||||
private final List<String> indexFileNames;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param rootDir 网页根目录
|
||||
*/
|
||||
public RootAction(String rootDir) {
|
||||
this(new File(rootDir));
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param rootDir 网页根目录
|
||||
*/
|
||||
public RootAction(File rootDir) {
|
||||
this(rootDir, DEFAULT_INDEX_FILE_NAME);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param rootDir 网页根目录
|
||||
* @param indexFileNames 主页文件名列表
|
||||
*/
|
||||
public RootAction(String rootDir, String... indexFileNames) {
|
||||
this(new File(rootDir), indexFileNames);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param rootDir 网页根目录
|
||||
* @param indexFileNames 主页文件名列表
|
||||
* @since 5.4.0
|
||||
*/
|
||||
public RootAction(File rootDir, String... indexFileNames) {
|
||||
this.rootDir = rootDir;
|
||||
this.indexFileNames = CollUtil.toList(indexFileNames);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doAction(HttpServerRequest request, HttpServerResponse response) {
|
||||
final String path = request.getPath();
|
||||
|
||||
File file = FileUtil.file(rootDir, path);
|
||||
if (file.exists()) {
|
||||
if (file.isDirectory()) {
|
||||
for (String indexFileName : indexFileNames) {
|
||||
// 默认读取主页
|
||||
file = FileUtil.file(file, indexFileName);
|
||||
if (file.exists() && file.isFile()) {
|
||||
response.write(file);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
final String name = request.getParam("name");
|
||||
response.write(file, name);
|
||||
}
|
||||
}
|
||||
|
||||
response.send404("404 Not Found !");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
/**
|
||||
* {@link com.sun.net.httpserver.HttpServer} 封装
|
||||
*
|
||||
* @author looly
|
||||
*/
|
||||
package aiyh.utils.tool.cn.hutool.http.server.action;
|
|
@ -0,0 +1,27 @@
|
|||
package aiyh.utils.tool.cn.hutool.http.server.filter;
|
||||
|
||||
import aiyh.utils.tool.cn.hutool.http.server.HttpServerRequest;
|
||||
import aiyh.utils.tool.cn.hutool.http.server.HttpServerResponse;
|
||||
import com.sun.net.httpserver.Filter;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* 过滤器接口,用于简化{@link Filter} 使用
|
||||
*
|
||||
* @author looly
|
||||
* @since 5.5.7
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface HttpFilter {
|
||||
|
||||
/**
|
||||
* 执行过滤
|
||||
*
|
||||
* @param req {@link aiyh.utils.tool.cn.hutool.http.server.HttpServerRequest} 请求对象,用于获取请求内容
|
||||
* @param res {@link aiyh.utils.tool.cn.hutool.http.server.HttpServerResponse} 响应对象,用于写出内容
|
||||
* @param chain {@link Filter.Chain}
|
||||
* @throws IOException IO异常
|
||||
*/
|
||||
void doFilter(HttpServerRequest req, HttpServerResponse res, Filter.Chain chain) throws IOException;
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
package aiyh.utils.tool.cn.hutool.http.server.filter;
|
||||
|
||||
import com.sun.net.httpserver.Filter;
|
||||
|
||||
/**
|
||||
* 匿名简单过滤器,跳过了描述
|
||||
*
|
||||
* @author looly
|
||||
* @since 5.5.7
|
||||
*/
|
||||
public abstract class SimpleFilter extends Filter {
|
||||
|
||||
@Override
|
||||
public String description() {
|
||||
return "Anonymous Filter";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
/**
|
||||
* {@link com.sun.net.httpserver.Filter} 实现包装
|
||||
*/
|
||||
package aiyh.utils.tool.cn.hutool.http.server.filter;
|
|
@ -0,0 +1,38 @@
|
|||
package aiyh.utils.tool.cn.hutool.http.server.handler;
|
||||
|
||||
import aiyh.utils.tool.cn.hutool.http.server.HttpServerRequest;
|
||||
import aiyh.utils.tool.cn.hutool.http.server.HttpServerResponse;
|
||||
import aiyh.utils.tool.cn.hutool.http.server.action.Action;
|
||||
import com.sun.net.httpserver.HttpExchange;
|
||||
import com.sun.net.httpserver.HttpHandler;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Action处理器,用于将HttpHandler转换为Action形式
|
||||
*
|
||||
* @author looly
|
||||
* @since 5.2.6
|
||||
*/
|
||||
public class ActionHandler implements HttpHandler {
|
||||
|
||||
private final Action action;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param action Action
|
||||
*/
|
||||
public ActionHandler(Action action) {
|
||||
this.action = action;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(HttpExchange httpExchange) throws IOException {
|
||||
action.doAction(
|
||||
new HttpServerRequest(httpExchange),
|
||||
new HttpServerResponse(httpExchange)
|
||||
);
|
||||
httpExchange.close();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
/**
|
||||
* {@link com.sun.net.httpserver.HttpHandler} 实现包装
|
||||
*/
|
||||
package aiyh.utils.tool.cn.hutool.http.server.handler;
|
|
@ -0,0 +1,6 @@
|
|||
/**
|
||||
* Http服务器封装
|
||||
*
|
||||
* @author looly
|
||||
*/
|
||||
package aiyh.utils.tool.cn.hutool.http.server;
|
|
@ -0,0 +1,25 @@
|
|||
package aiyh.utils.tool.cn.hutool.http.ssl;
|
||||
|
||||
import aiyh.utils.tool.cn.hutool.core.io.IORuntimeException;
|
||||
import aiyh.utils.tool.cn.hutool.core.net.SSLProtocols;
|
||||
|
||||
/**
|
||||
* 兼容android低版本SSL连接<br>
|
||||
* 在测试HttpUrlConnection的时候,发现一部分手机无法连接[GithubPage]
|
||||
*
|
||||
* <p>
|
||||
* 最后发现原来是某些SSL协议没有开启
|
||||
*
|
||||
* @author MikaGuraNTK
|
||||
*/
|
||||
public class AndroidSupportSSLFactory extends CustomProtocolsSSLFactory {
|
||||
|
||||
// Android低版本不重置的话某些SSL访问就会失败
|
||||
private static final String[] protocols = {
|
||||
SSLProtocols.SSLv3, SSLProtocols.TLSv1, SSLProtocols.TLSv11, SSLProtocols.TLSv12};
|
||||
|
||||
public AndroidSupportSSLFactory() throws IORuntimeException {
|
||||
super(protocols);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,97 @@
|
|||
package aiyh.utils.tool.cn.hutool.http.ssl;
|
||||
|
||||
import aiyh.utils.tool.cn.hutool.core.io.IORuntimeException;
|
||||
import aiyh.utils.tool.cn.hutool.core.net.SSLUtil;
|
||||
import aiyh.utils.tool.cn.hutool.core.util.ArrayUtil;
|
||||
|
||||
import javax.net.ssl.SSLSocket;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.Socket;
|
||||
|
||||
/**
|
||||
* 自定义支持协议类型的SSLSocketFactory
|
||||
*
|
||||
* @author looly
|
||||
*/
|
||||
public class CustomProtocolsSSLFactory extends SSLSocketFactory {
|
||||
|
||||
private final String[] protocols;
|
||||
private final SSLSocketFactory base;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param protocols 支持协议列表
|
||||
* @throws IORuntimeException IO异常
|
||||
*/
|
||||
public CustomProtocolsSSLFactory(String... protocols) throws IORuntimeException {
|
||||
this.protocols = protocols;
|
||||
this.base = SSLUtil.createSSLContext(null).getSocketFactory();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getDefaultCipherSuites() {
|
||||
return base.getDefaultCipherSuites();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getSupportedCipherSuites() {
|
||||
return base.getSupportedCipherSuites();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket() throws IOException {
|
||||
final SSLSocket sslSocket = (SSLSocket) base.createSocket();
|
||||
resetProtocols(sslSocket);
|
||||
return sslSocket;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SSLSocket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
|
||||
final SSLSocket socket = (SSLSocket) base.createSocket(s, host, port, autoClose);
|
||||
resetProtocols(socket);
|
||||
return socket;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(String host, int port) throws IOException {
|
||||
final SSLSocket socket = (SSLSocket) base.createSocket(host, port);
|
||||
resetProtocols(socket);
|
||||
return socket;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException {
|
||||
final SSLSocket socket = (SSLSocket) base.createSocket(host, port, localHost, localPort);
|
||||
resetProtocols(socket);
|
||||
return socket;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(InetAddress host, int port) throws IOException {
|
||||
final SSLSocket socket = (SSLSocket) base.createSocket(host, port);
|
||||
resetProtocols(socket);
|
||||
return socket;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
|
||||
final SSLSocket socket = (SSLSocket) base.createSocket(address, port, localAddress, localPort);
|
||||
resetProtocols(socket);
|
||||
return socket;
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置可用策略
|
||||
*
|
||||
* @param socket SSLSocket
|
||||
*/
|
||||
private void resetProtocols(SSLSocket socket) {
|
||||
if (ArrayUtil.isNotEmpty(this.protocols)) {
|
||||
socket.setEnabledProtocols(this.protocols);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
package aiyh.utils.tool.cn.hutool.http.ssl;
|
||||
|
||||
/**
|
||||
* 默认的SSLSocketFactory
|
||||
*
|
||||
* @author Looly
|
||||
* @since 5.1.2
|
||||
*/
|
||||
public class DefaultSSLFactory extends CustomProtocolsSSLFactory {
|
||||
|
||||
public DefaultSSLFactory() {
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
package aiyh.utils.tool.cn.hutool.http.ssl;
|
||||
|
||||
import aiyh.utils.tool.cn.hutool.core.util.StrUtil;
|
||||
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
|
||||
/**
|
||||
* 默认的全局SSL配置,当用户未设置相关信息时,使用默认设置,默认设置为单例模式。
|
||||
*
|
||||
* @author looly
|
||||
* @since 5.1.2
|
||||
*/
|
||||
public class DefaultSSLInfo {
|
||||
/**
|
||||
* 默认信任全部的域名校验器
|
||||
*/
|
||||
public static final aiyh.utils.tool.cn.hutool.http.ssl.TrustAnyHostnameVerifier TRUST_ANY_HOSTNAME_VERIFIER;
|
||||
/**
|
||||
* 默认的SSLSocketFactory,区分安卓
|
||||
*/
|
||||
public static final SSLSocketFactory DEFAULT_SSF;
|
||||
|
||||
static {
|
||||
TRUST_ANY_HOSTNAME_VERIFIER = new TrustAnyHostnameVerifier();
|
||||
if (StrUtil.equalsIgnoreCase("dalvik", System.getProperty("java.vm.name"))) {
|
||||
// 兼容android低版本SSL连接
|
||||
DEFAULT_SSF = new AndroidSupportSSLFactory();
|
||||
} else {
|
||||
DEFAULT_SSF = new DefaultSSLFactory();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,95 @@
|
|||
package aiyh.utils.tool.cn.hutool.http.ssl;
|
||||
|
||||
import aiyh.utils.tool.cn.hutool.core.net.SSLContextBuilder;
|
||||
import aiyh.utils.tool.cn.hutool.core.net.SSLProtocols;
|
||||
|
||||
import javax.net.ssl.KeyManager;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
import javax.net.ssl.TrustManager;
|
||||
import java.security.KeyManagementException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
|
||||
/**
|
||||
* SSLSocketFactory构建器
|
||||
*
|
||||
* @author Looly
|
||||
* @see SSLContextBuilder
|
||||
* @deprecated 请使用 {@link SSLContextBuilder}
|
||||
*/
|
||||
@Deprecated
|
||||
public class SSLSocketFactoryBuilder implements SSLProtocols {
|
||||
|
||||
SSLContextBuilder sslContextBuilder;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*/
|
||||
public SSLSocketFactoryBuilder() {
|
||||
this.sslContextBuilder = SSLContextBuilder.create();
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建 SSLSocketFactoryBuilder
|
||||
*
|
||||
* @return SSLSocketFactoryBuilder
|
||||
*/
|
||||
public static SSLSocketFactoryBuilder create() {
|
||||
return new SSLSocketFactoryBuilder();
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置协议
|
||||
*
|
||||
* @param protocol 协议
|
||||
* @return 自身
|
||||
*/
|
||||
public SSLSocketFactoryBuilder setProtocol(String protocol) {
|
||||
this.sslContextBuilder.setProtocol(protocol);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置信任信息
|
||||
*
|
||||
* @param trustManagers TrustManager列表
|
||||
* @return 自身
|
||||
*/
|
||||
public SSLSocketFactoryBuilder setTrustManagers(TrustManager... trustManagers) {
|
||||
this.sslContextBuilder.setTrustManagers(trustManagers);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 JSSE key managers
|
||||
*
|
||||
* @param keyManagers JSSE key managers
|
||||
* @return 自身
|
||||
*/
|
||||
public SSLSocketFactoryBuilder setKeyManagers(KeyManager... keyManagers) {
|
||||
this.sslContextBuilder.setKeyManagers(keyManagers);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 SecureRandom
|
||||
*
|
||||
* @param secureRandom SecureRandom
|
||||
* @return 自己
|
||||
*/
|
||||
public SSLSocketFactoryBuilder setSecureRandom(SecureRandom secureRandom) {
|
||||
this.sslContextBuilder.setSecureRandom(secureRandom);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建SSLSocketFactory
|
||||
*
|
||||
* @return SSLSocketFactory
|
||||
* @throws NoSuchAlgorithmException 无此算法
|
||||
* @throws KeyManagementException Key管理异常
|
||||
*/
|
||||
public SSLSocketFactory build() throws NoSuchAlgorithmException, KeyManagementException {
|
||||
return this.sslContextBuilder.buildChecked().getSocketFactory();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
package aiyh.utils.tool.cn.hutool.http.ssl;
|
||||
|
||||
import javax.net.ssl.HostnameVerifier;
|
||||
import javax.net.ssl.SSLSession;
|
||||
|
||||
/**
|
||||
* https 域名校验
|
||||
*
|
||||
* @author Looly
|
||||
*/
|
||||
public class TrustAnyHostnameVerifier implements HostnameVerifier {
|
||||
|
||||
@Override
|
||||
public boolean verify(String hostname, SSLSession session) {
|
||||
return true;// 直接返回true
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
/**
|
||||
* SSL封装
|
||||
*
|
||||
* @author looly
|
||||
*/
|
||||
package aiyh.utils.tool.cn.hutool.http.ssl;
|
|
@ -0,0 +1,140 @@
|
|||
package aiyh.utils.tool.cn.hutool.http.useragent;
|
||||
|
||||
import aiyh.utils.tool.cn.hutool.core.collection.CollUtil;
|
||||
import aiyh.utils.tool.cn.hutool.core.util.ReUtil;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* 浏览器对象
|
||||
*
|
||||
* @author looly
|
||||
* @since 4.2.1
|
||||
*/
|
||||
public class Browser extends UserAgentInfo {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 未知
|
||||
*/
|
||||
public static final Browser Unknown = new Browser(NameUnknown, null, null);
|
||||
/**
|
||||
* 其它版本
|
||||
*/
|
||||
public static final String Other_Version = "[\\/ ]([\\d\\w\\.\\-]+)";
|
||||
|
||||
/**
|
||||
* 支持的浏览器类型
|
||||
*/
|
||||
public static final List<Browser> browers = CollUtil.newArrayList(
|
||||
// 部分特殊浏览器是基于安卓、Iphone等的,需要优先判断
|
||||
// 企业微信 企业微信使用微信浏览器内核,会包含 MicroMessenger 所以要放在前面
|
||||
new Browser("wxwork", "wxwork", "wxwork\\/([\\d\\w\\.\\-]+)"),
|
||||
// 微信
|
||||
new Browser("MicroMessenger", "MicroMessenger", Other_Version),
|
||||
// 微信小程序
|
||||
new Browser("miniProgram", "miniProgram", Other_Version),
|
||||
// QQ浏览器
|
||||
new Browser("QQBrowser", "MQQBrowser", "MQQBrowser\\/([\\d\\w\\.\\-]+)"),
|
||||
// 钉钉PC端浏览器
|
||||
new Browser("DingTalk-win", "dingtalk-win", "DingTalk\\(([\\d\\w\\.\\-]+)\\)"),
|
||||
// 钉钉内置浏览器
|
||||
new Browser("DingTalk", "DingTalk", "AliApp\\(DingTalk\\/([\\d\\w\\.\\-]+)\\)"),
|
||||
// 支付宝内置浏览器
|
||||
new Browser("Alipay", "AlipayClient", "AliApp\\(AP\\/([\\d\\w\\.\\-]+)\\)"),
|
||||
// 淘宝内置浏览器
|
||||
new Browser("Taobao", "taobao", "AliApp\\(TB\\/([\\d\\w\\.\\-]+)\\)"),
|
||||
// UC浏览器
|
||||
new Browser("UCBrowser", "UC?Browser", "UC?Browser\\/([\\d\\w\\.\\-]+)"),
|
||||
// XiaoMi 浏览器
|
||||
new Browser("MiuiBrowser", "MiuiBrowser|mibrowser", "MiuiBrowser\\/([\\d\\w\\.\\-]+)"),
|
||||
// 夸克浏览器
|
||||
new Browser("Quark", "Quark", Other_Version),
|
||||
// 联想浏览器
|
||||
new Browser("Lenovo", "SLBrowser", "SLBrowser/([\\d\\w\\.\\-]+)"),
|
||||
new Browser("MSEdge", "Edge|Edg", "(?:edge|Edg|EdgA)\\/([\\d\\w\\.\\-]+)"),
|
||||
new Browser("Chrome", "chrome", Other_Version),
|
||||
new Browser("Firefox", "firefox", Other_Version),
|
||||
new Browser("IEMobile", "iemobile", Other_Version),
|
||||
new Browser("Android Browser", "android", "version\\/([\\d\\w\\.\\-]+)"),
|
||||
new Browser("Safari", "safari", "version\\/([\\d\\w\\.\\-]+)"),
|
||||
new Browser("Opera", "opera", Other_Version),
|
||||
new Browser("Konqueror", "konqueror", Other_Version),
|
||||
new Browser("PS3", "playstation 3", "([\\d\\w\\.\\-]+)\\)\\s*$"),
|
||||
new Browser("PSP", "playstation portable", "([\\d\\w\\.\\-]+)\\)?\\s*$"),
|
||||
new Browser("Lotus", "lotus.notes", "Lotus-Notes\\/([\\w.]+)"),
|
||||
new Browser("Thunderbird", "thunderbird", Other_Version),
|
||||
new Browser("Netscape", "netscape", Other_Version),
|
||||
new Browser("Seamonkey", "seamonkey", Other_Version),
|
||||
new Browser("Outlook", "microsoft.outlook", Other_Version),
|
||||
new Browser("Evolution", "evolution", Other_Version),
|
||||
new Browser("MSIE", "msie", "msie ([\\d\\w\\.\\-]+)"),
|
||||
new Browser("MSIE11", "rv:11", "rv:([\\d\\w\\.\\-]+)"),
|
||||
new Browser("Gabble", "Gabble", Other_Version),
|
||||
new Browser("Yammer Desktop", "AdobeAir", "([\\d\\w\\.\\-]+)\\/Yammer"),
|
||||
new Browser("Yammer Mobile", "Yammer[\\s]+([\\d\\w\\.\\-]+)", "Yammer[\\s]+([\\d\\w\\.\\-]+)"),
|
||||
new Browser("Apache HTTP Client", "Apache\\\\-HttpClient", "Apache\\-HttpClient\\/([\\d\\w\\.\\-]+)"),
|
||||
new Browser("BlackBerry", "BlackBerry", "BlackBerry[\\d]+\\/([\\d\\w\\.\\-]+)")
|
||||
);
|
||||
|
||||
/**
|
||||
* 添加自定义的浏览器类型
|
||||
*
|
||||
* @param name 浏览器名称
|
||||
* @param regex 关键字或表达式
|
||||
* @param versionRegex 匹配版本的正则
|
||||
* @since 5.7.4
|
||||
*/
|
||||
synchronized public static void addCustomBrowser(String name, String regex, String versionRegex) {
|
||||
browers.add(new Browser(name, regex, versionRegex));
|
||||
}
|
||||
|
||||
private Pattern versionPattern;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param name 浏览器名称
|
||||
* @param regex 关键字或表达式
|
||||
* @param versionRegex 匹配版本的正则
|
||||
*/
|
||||
public Browser(String name, String regex, String versionRegex) {
|
||||
super(name, regex);
|
||||
if (Other_Version.equals(versionRegex)) {
|
||||
versionRegex = name + versionRegex;
|
||||
}
|
||||
if (null != versionRegex) {
|
||||
this.versionPattern = Pattern.compile(versionRegex, Pattern.CASE_INSENSITIVE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取浏览器版本
|
||||
*
|
||||
* @param userAgentString User-Agent字符串
|
||||
* @return 版本
|
||||
*/
|
||||
public String getVersion(String userAgentString) {
|
||||
if (isUnknown()) {
|
||||
return null;
|
||||
}
|
||||
return ReUtil.getGroup1(this.versionPattern, userAgentString);
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否移动浏览器
|
||||
*
|
||||
* @return 是否移动浏览器
|
||||
*/
|
||||
public boolean isMobile() {
|
||||
final String name = this.getName();
|
||||
return "PSP".equals(name) ||
|
||||
"Yammer Mobile".equals(name) ||
|
||||
"Android Browser".equals(name) ||
|
||||
"IEMobile".equals(name) ||
|
||||
"MicroMessenger".equals(name) ||
|
||||
"miniProgram".equals(name) ||
|
||||
"DingTalk".equals(name);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
package aiyh.utils.tool.cn.hutool.http.useragent;
|
||||
|
||||
import aiyh.utils.tool.cn.hutool.core.collection.CollUtil;
|
||||
import aiyh.utils.tool.cn.hutool.core.util.ReUtil;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* 引擎对象
|
||||
*
|
||||
* @author looly
|
||||
* @since 4.2.1
|
||||
*/
|
||||
public class Engine extends UserAgentInfo {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** 未知 */
|
||||
public static final Engine Unknown = new Engine(NameUnknown, null);
|
||||
|
||||
/**
|
||||
* 支持的引擎类型
|
||||
*/
|
||||
public static final List<Engine> engines = CollUtil.newArrayList(
|
||||
new Engine("Trident", "trident"),
|
||||
new Engine("Webkit", "webkit"),
|
||||
new Engine("Chrome", "chrome"),
|
||||
new Engine("Opera", "opera"),
|
||||
new Engine("Presto", "presto"),
|
||||
new Engine("Gecko", "gecko"),
|
||||
new Engine("KHTML", "khtml"),
|
||||
new Engine("Konqueror", "konqueror"),
|
||||
new Engine("MIDP", "MIDP")
|
||||
);
|
||||
|
||||
private final Pattern versionPattern;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param name 引擎名称
|
||||
* @param regex 关键字或表达式
|
||||
*/
|
||||
public Engine(String name, String regex) {
|
||||
super(name, regex);
|
||||
this.versionPattern = Pattern.compile(name + "[/\\- ]([\\d\\w.\\-]+)", Pattern.CASE_INSENSITIVE);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取引擎版本
|
||||
*
|
||||
* @param userAgentString User-Agent字符串
|
||||
* @return 版本
|
||||
* @since 5.7.4
|
||||
*/
|
||||
public String getVersion(String userAgentString) {
|
||||
if (isUnknown()) {
|
||||
return null;
|
||||
}
|
||||
return ReUtil.getGroup1(this.versionPattern, userAgentString);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,107 @@
|
|||
package aiyh.utils.tool.cn.hutool.http.useragent;
|
||||
|
||||
import aiyh.utils.tool.cn.hutool.core.collection.CollUtil;
|
||||
import aiyh.utils.tool.cn.hutool.core.util.ReUtil;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* 系统对象
|
||||
*
|
||||
* @author looly
|
||||
* @since 4.2.1
|
||||
*/
|
||||
public class OS extends UserAgentInfo {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 未知
|
||||
*/
|
||||
public static final OS Unknown = new OS(NameUnknown, null);
|
||||
|
||||
/**
|
||||
* 支持的引擎类型
|
||||
*/
|
||||
public static final List<OS> oses = CollUtil.newArrayList(//
|
||||
new OS("Windows 10 or Windows Server 2016", "windows nt 10\\.0", "windows nt (10\\.0)"),//
|
||||
new OS("Windows 8.1 or Windows Server 2012R2", "windows nt 6\\.3", "windows nt (6\\.3)"),//
|
||||
new OS("Windows 8 or Windows Server 2012", "windows nt 6\\.2", "windows nt (6\\.2)"),//
|
||||
new OS("Windows Vista", "windows nt 6\\.0", "windows nt (6\\.0)"), //
|
||||
new OS("Windows 7 or Windows Server 2008R2", "windows nt 6\\.1", "windows nt (6\\.1)"), //
|
||||
new OS("Windows 2003", "windows nt 5\\.2", "windows nt (5\\.2)"), //
|
||||
new OS("Windows XP", "windows nt 5\\.1", "windows nt (5\\.1)"), //
|
||||
new OS("Windows 2000", "windows nt 5\\.0", "windows nt (5\\.0)"), //
|
||||
new OS("Windows Phone", "windows (ce|phone|mobile)( os)?", "windows (?:ce|phone|mobile) (\\d+([._]\\d+)*)"), //
|
||||
new OS("Windows", "windows"), //
|
||||
new OS("OSX", "os x (\\d+)[._](\\d+)", "os x (\\d+([._]\\d+)*)"), //
|
||||
new OS("Android", "Android", "Android (\\d+([._]\\d+)*)"),//
|
||||
new OS("Android", "XiaoMi|MI\\s+", "\\(X(\\d+([._]\\d+)*)"),//
|
||||
new OS("Linux", "linux"), //
|
||||
new OS("Wii", "wii", "wii libnup/(\\d+([._]\\d+)*)"), //
|
||||
new OS("PS3", "playstation 3", "playstation 3; (\\d+([._]\\d+)*)"), //
|
||||
new OS("PSP", "playstation portable", "Portable\\); (\\d+([._]\\d+)*)"), //
|
||||
new OS("iPad", "\\(iPad.*os (\\d+)[._](\\d+)", "\\(iPad.*os (\\d+([._]\\d+)*)"), //
|
||||
new OS("iPhone", "\\(iPhone.*os (\\d+)[._](\\d+)", "\\(iPhone.*os (\\d+([._]\\d+)*)"), //
|
||||
new OS("YPod", "iPod touch[\\s\\;]+iPhone.*os (\\d+)[._](\\d+)", "iPod touch[\\s\\;]+iPhone.*os (\\d+([._]\\d+)*)"), //
|
||||
new OS("YPad", "iPad[\\s\\;]+iPhone.*os (\\d+)[._](\\d+)", "iPad[\\s\\;]+iPhone.*os (\\d+([._]\\d+)*)"), //
|
||||
new OS("YPhone", "iPhone[\\s\\;]+iPhone.*os (\\d+)[._](\\d+)", "iPhone[\\s\\;]+iPhone.*os (\\d+([._]\\d+)*)"), //
|
||||
new OS("Symbian", "symbian(os)?"), //
|
||||
new OS("Darwin", "Darwin\\/([\\d\\w\\.\\-]+)", "Darwin\\/([\\d\\w\\.\\-]+)"), //
|
||||
new OS("Adobe Air", "AdobeAir\\/([\\d\\w\\.\\-]+)", "AdobeAir\\/([\\d\\w\\.\\-]+)"), //
|
||||
new OS("Java", "Java[\\s]+([\\d\\w\\.\\-]+)", "Java[\\s]+([\\d\\w\\.\\-]+)")//
|
||||
);
|
||||
|
||||
/**
|
||||
* 添加自定义的系统类型
|
||||
*
|
||||
* @param name 浏览器名称
|
||||
* @param regex 关键字或表达式
|
||||
* @param versionRegex 匹配版本的正则
|
||||
* @since 5.7.4
|
||||
*/
|
||||
synchronized public static void addCustomOs(String name, String regex, String versionRegex) {
|
||||
oses.add(new OS(name, regex, versionRegex));
|
||||
}
|
||||
|
||||
private Pattern versionPattern;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param name 系统名称
|
||||
* @param regex 关键字或表达式
|
||||
*/
|
||||
public OS(String name, String regex) {
|
||||
this(name, regex, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param name 系统名称
|
||||
* @param regex 关键字或表达式
|
||||
* @param versionRegex 版本正则表达式
|
||||
* @since 5.7.4
|
||||
*/
|
||||
public OS(String name, String regex, String versionRegex) {
|
||||
super(name, regex);
|
||||
if (null != versionRegex) {
|
||||
this.versionPattern = Pattern.compile(versionRegex, Pattern.CASE_INSENSITIVE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取浏览器版本
|
||||
*
|
||||
* @param userAgentString User-Agent字符串
|
||||
* @return 版本
|
||||
*/
|
||||
public String getVersion(String userAgentString) {
|
||||
if (isUnknown() || null == this.versionPattern) {
|
||||
// 无版本信息
|
||||
return null;
|
||||
}
|
||||
return ReUtil.getGroup1(this.versionPattern, userAgentString);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,147 @@
|
|||
package aiyh.utils.tool.cn.hutool.http.useragent;
|
||||
|
||||
import aiyh.utils.tool.cn.hutool.core.collection.CollUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 平台对象
|
||||
*
|
||||
* @author looly
|
||||
* @since 4.2.1
|
||||
*/
|
||||
public class Platform extends UserAgentInfo {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 未知
|
||||
*/
|
||||
public static final Platform Unknown = new Platform(NameUnknown, null);
|
||||
|
||||
/**
|
||||
* Iphone
|
||||
*/
|
||||
public static final Platform IPHONE = new Platform("iPhone", "iphone");
|
||||
/**
|
||||
* ipod
|
||||
*/
|
||||
public static final Platform IPOD = new Platform("iPod", "ipod");
|
||||
/**
|
||||
* ipad
|
||||
*/
|
||||
public static final Platform IPAD = new Platform("iPad", "ipad");
|
||||
|
||||
/**
|
||||
* android
|
||||
*/
|
||||
public static final Platform ANDROID = new Platform("Android", "android");
|
||||
/**
|
||||
* android
|
||||
*/
|
||||
public static final Platform GOOGLE_TV = new Platform("GoogleTV", "googletv");
|
||||
|
||||
/**
|
||||
* Windows Phone
|
||||
*/
|
||||
public static final Platform WINDOWS_PHONE = new Platform("Windows Phone", "windows (ce|phone|mobile)( os)?");
|
||||
|
||||
/**
|
||||
* 支持的移动平台类型
|
||||
*/
|
||||
public static final List<Platform> mobilePlatforms = CollUtil.newArrayList(//
|
||||
WINDOWS_PHONE, //
|
||||
IPAD, //
|
||||
IPOD, //
|
||||
IPHONE, //
|
||||
new Platform("Android", "XiaoMi|MI\\s+"), //
|
||||
ANDROID, //
|
||||
GOOGLE_TV, //
|
||||
new Platform("htcFlyer", "htc_flyer"), //
|
||||
new Platform("Symbian", "symbian(os)?"), //
|
||||
new Platform("Blackberry", "blackberry") //
|
||||
);
|
||||
|
||||
/**
|
||||
* 支持的桌面平台类型
|
||||
*/
|
||||
public static final List<Platform> desktopPlatforms = CollUtil.newArrayList(//
|
||||
new Platform("Windows", "windows"), //
|
||||
new Platform("Mac", "(macintosh|darwin)"), //
|
||||
new Platform("Linux", "linux"), //
|
||||
new Platform("Wii", "wii"), //
|
||||
new Platform("Playstation", "playstation"), //
|
||||
new Platform("Java", "java") //
|
||||
);
|
||||
|
||||
/**
|
||||
* 支持的平台类型
|
||||
*/
|
||||
public static final List<Platform> platforms;
|
||||
|
||||
static {
|
||||
platforms = new ArrayList<>(13);
|
||||
platforms.addAll(mobilePlatforms);
|
||||
platforms.addAll(desktopPlatforms);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param name 平台名称
|
||||
* @param regex 关键字或表达式
|
||||
*/
|
||||
public Platform(String name, String regex) {
|
||||
super(name, regex);
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否为移动平台
|
||||
*
|
||||
* @return 是否为移动平台
|
||||
*/
|
||||
public boolean isMobile() {
|
||||
return mobilePlatforms.contains(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否为Iphone或者iPod设备
|
||||
*
|
||||
* @return 是否为Iphone或者iPod设备
|
||||
* @since 5.2.3
|
||||
*/
|
||||
public boolean isIPhoneOrIPod() {
|
||||
return this.equals(IPHONE) || this.equals(IPOD);
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否为Iphone或者iPod设备
|
||||
*
|
||||
* @return 是否为Iphone或者iPod设备
|
||||
* @since 5.2.3
|
||||
*/
|
||||
public boolean isIPad() {
|
||||
return this.equals(IPAD);
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否为IOS平台,包括IPhone、IPod、IPad
|
||||
*
|
||||
* @return 是否为IOS平台,包括IPhone、IPod、IPad
|
||||
* @since 5.2.3
|
||||
*/
|
||||
public boolean isIos() {
|
||||
return isIPhoneOrIPod() || isIPad();
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否为Android平台,包括Android和Google TV
|
||||
*
|
||||
* @return 是否为Android平台,包括Android和Google TV
|
||||
* @since 5.2.3
|
||||
*/
|
||||
public boolean isAndroid() {
|
||||
return this.equals(ANDROID) || this.equals(GOOGLE_TV);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,196 @@
|
|||
package aiyh.utils.tool.cn.hutool.http.useragent;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* User-Agent信息对象
|
||||
*
|
||||
* @author looly
|
||||
* @since 4.2.1
|
||||
*/
|
||||
public class UserAgent implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 是否为移动平台
|
||||
*/
|
||||
private boolean mobile;
|
||||
/**
|
||||
* 浏览器类型
|
||||
*/
|
||||
private aiyh.utils.tool.cn.hutool.http.useragent.Browser browser;
|
||||
/**
|
||||
* 浏览器版本
|
||||
*/
|
||||
private String version;
|
||||
|
||||
/**
|
||||
* 平台类型
|
||||
*/
|
||||
private aiyh.utils.tool.cn.hutool.http.useragent.Platform platform;
|
||||
|
||||
/**
|
||||
* 系统类型
|
||||
*/
|
||||
private aiyh.utils.tool.cn.hutool.http.useragent.OS os;
|
||||
/**
|
||||
* 系统版本
|
||||
*/
|
||||
private String osVersion;
|
||||
|
||||
/**
|
||||
* 引擎类型
|
||||
*/
|
||||
private aiyh.utils.tool.cn.hutool.http.useragent.Engine engine;
|
||||
/**
|
||||
* 引擎版本
|
||||
*/
|
||||
private String engineVersion;
|
||||
|
||||
/**
|
||||
* 是否为移动平台
|
||||
*
|
||||
* @return 是否为移动平台
|
||||
*/
|
||||
public boolean isMobile() {
|
||||
return mobile;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置是否为移动平台
|
||||
*
|
||||
* @param mobile 是否为移动平台
|
||||
*/
|
||||
public void setMobile(boolean mobile) {
|
||||
this.mobile = mobile;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取浏览器类型
|
||||
*
|
||||
* @return 浏览器类型
|
||||
*/
|
||||
public aiyh.utils.tool.cn.hutool.http.useragent.Browser getBrowser() {
|
||||
return browser;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置浏览器类型
|
||||
*
|
||||
* @param browser 浏览器类型
|
||||
*/
|
||||
public void setBrowser(Browser browser) {
|
||||
this.browser = browser;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取平台类型
|
||||
*
|
||||
* @return 平台类型
|
||||
*/
|
||||
public aiyh.utils.tool.cn.hutool.http.useragent.Platform getPlatform() {
|
||||
return platform;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置平台类型
|
||||
*
|
||||
* @param platform 平台类型
|
||||
*/
|
||||
public void setPlatform(Platform platform) {
|
||||
this.platform = platform;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取系统类型
|
||||
*
|
||||
* @return 系统类型
|
||||
*/
|
||||
public aiyh.utils.tool.cn.hutool.http.useragent.OS getOs() {
|
||||
return os;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置系统类型
|
||||
*
|
||||
* @param os 系统类型
|
||||
*/
|
||||
public void setOs(OS os) {
|
||||
this.os = os;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取系统版本
|
||||
*
|
||||
* @return 系统版本
|
||||
* @since 5.7.4
|
||||
*/
|
||||
public String getOsVersion() {
|
||||
return this.osVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置系统版本
|
||||
*
|
||||
* @param osVersion 系统版本
|
||||
* @since 5.7.4
|
||||
*/
|
||||
public void setOsVersion(String osVersion) {
|
||||
this.osVersion = osVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取引擎类型
|
||||
*
|
||||
* @return 引擎类型
|
||||
*/
|
||||
public aiyh.utils.tool.cn.hutool.http.useragent.Engine getEngine() {
|
||||
return engine;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置引擎类型
|
||||
*
|
||||
* @param engine 引擎类型
|
||||
*/
|
||||
public void setEngine(Engine engine) {
|
||||
this.engine = engine;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取浏览器版本
|
||||
*
|
||||
* @return 浏览器版本
|
||||
*/
|
||||
public String getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置浏览器版本
|
||||
*
|
||||
* @param version 浏览器版本
|
||||
*/
|
||||
public void setVersion(String version) {
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取引擎版本
|
||||
*
|
||||
* @return 引擎版本
|
||||
*/
|
||||
public String getEngineVersion() {
|
||||
return engineVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置引擎版本
|
||||
*
|
||||
* @param engineVersion 引擎版本
|
||||
*/
|
||||
public void setEngineVersion(String engineVersion) {
|
||||
this.engineVersion = engineVersion;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,114 @@
|
|||
package aiyh.utils.tool.cn.hutool.http.useragent;
|
||||
|
||||
import aiyh.utils.tool.cn.hutool.core.util.ReUtil;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* User-agent信息
|
||||
*
|
||||
* @author looly
|
||||
* @since 4.2.1
|
||||
*/
|
||||
public class UserAgentInfo implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 未知类型
|
||||
*/
|
||||
public static final String NameUnknown = "Unknown";
|
||||
|
||||
/** 信息名称 */
|
||||
private final String name;
|
||||
/** 信息匹配模式 */
|
||||
private final Pattern pattern;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param name 名字
|
||||
* @param regex 表达式
|
||||
*/
|
||||
public UserAgentInfo(String name, String regex) {
|
||||
this(name, (null == regex) ? null : Pattern.compile(regex, Pattern.CASE_INSENSITIVE));
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param name 名字
|
||||
* @param pattern 匹配模式
|
||||
*/
|
||||
public UserAgentInfo(String name, Pattern pattern) {
|
||||
this.name = name;
|
||||
this.pattern = pattern;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取信息名称
|
||||
*
|
||||
* @return 信息名称
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取匹配模式
|
||||
*
|
||||
* @return 匹配模式
|
||||
*/
|
||||
public Pattern getPattern() {
|
||||
return pattern;
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定内容中是否包含匹配此信息的内容
|
||||
*
|
||||
* @param content User-Agent字符串
|
||||
* @return 是否包含匹配此信息的内容
|
||||
*/
|
||||
public boolean isMatch(String content) {
|
||||
return ReUtil.contains(this.pattern, content);
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否为Unknown
|
||||
*
|
||||
* @return 是否为Unknown
|
||||
*/
|
||||
public boolean isUnknown() {
|
||||
return NameUnknown.equals(this.name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((name == null) ? 0 : name.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
final UserAgentInfo other = (UserAgentInfo) obj;
|
||||
if (name == null) {
|
||||
return other.name == null;
|
||||
} else return name.equals(other.name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.name;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,108 @@
|
|||
package aiyh.utils.tool.cn.hutool.http.useragent;
|
||||
|
||||
import aiyh.utils.tool.cn.hutool.core.util.StrUtil;
|
||||
|
||||
/**
|
||||
* User-Agent解析器
|
||||
*
|
||||
* @author looly
|
||||
* @since 4.2.1
|
||||
*/
|
||||
public class UserAgentParser {
|
||||
|
||||
/**
|
||||
* 解析User-Agent
|
||||
*
|
||||
* @param userAgentString User-Agent字符串
|
||||
* @return {@link UserAgent}
|
||||
*/
|
||||
public static UserAgent parse(String userAgentString) {
|
||||
if (StrUtil.isBlank(userAgentString)) {
|
||||
return null;
|
||||
}
|
||||
final UserAgent userAgent = new UserAgent();
|
||||
|
||||
// 浏览器
|
||||
final aiyh.utils.tool.cn.hutool.http.useragent.Browser browser = parseBrowser(userAgentString);
|
||||
userAgent.setBrowser(browser);
|
||||
userAgent.setVersion(browser.getVersion(userAgentString));
|
||||
|
||||
// 浏览器引擎
|
||||
final aiyh.utils.tool.cn.hutool.http.useragent.Engine engine = parseEngine(userAgentString);
|
||||
userAgent.setEngine(engine);
|
||||
userAgent.setEngineVersion(engine.getVersion(userAgentString));
|
||||
|
||||
// 操作系统
|
||||
final aiyh.utils.tool.cn.hutool.http.useragent.OS os = parseOS(userAgentString);
|
||||
userAgent.setOs(os);
|
||||
userAgent.setOsVersion(os.getVersion(userAgentString));
|
||||
|
||||
// 平台
|
||||
final aiyh.utils.tool.cn.hutool.http.useragent.Platform platform = parsePlatform(userAgentString);
|
||||
userAgent.setPlatform(platform);
|
||||
userAgent.setMobile(platform.isMobile() || browser.isMobile());
|
||||
|
||||
|
||||
return userAgent;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析浏览器类型
|
||||
*
|
||||
* @param userAgentString User-Agent字符串
|
||||
* @return 浏览器类型
|
||||
*/
|
||||
private static aiyh.utils.tool.cn.hutool.http.useragent.Browser parseBrowser(String userAgentString) {
|
||||
for (aiyh.utils.tool.cn.hutool.http.useragent.Browser browser : aiyh.utils.tool.cn.hutool.http.useragent.Browser.browers) {
|
||||
if (browser.isMatch(userAgentString)) {
|
||||
return browser;
|
||||
}
|
||||
}
|
||||
return Browser.Unknown;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析引擎类型
|
||||
*
|
||||
* @param userAgentString User-Agent字符串
|
||||
* @return 引擎类型
|
||||
*/
|
||||
private static aiyh.utils.tool.cn.hutool.http.useragent.Engine parseEngine(String userAgentString) {
|
||||
for (aiyh.utils.tool.cn.hutool.http.useragent.Engine engine : aiyh.utils.tool.cn.hutool.http.useragent.Engine.engines) {
|
||||
if (engine.isMatch(userAgentString)) {
|
||||
return engine;
|
||||
}
|
||||
}
|
||||
return Engine.Unknown;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析系统类型
|
||||
*
|
||||
* @param userAgentString User-Agent字符串
|
||||
* @return 系统类型
|
||||
*/
|
||||
private static aiyh.utils.tool.cn.hutool.http.useragent.OS parseOS(String userAgentString) {
|
||||
for (aiyh.utils.tool.cn.hutool.http.useragent.OS os : aiyh.utils.tool.cn.hutool.http.useragent.OS.oses) {
|
||||
if (os.isMatch(userAgentString)) {
|
||||
return os;
|
||||
}
|
||||
}
|
||||
return OS.Unknown;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析平台类型
|
||||
*
|
||||
* @param userAgentString User-Agent字符串
|
||||
* @return 平台类型
|
||||
*/
|
||||
private static aiyh.utils.tool.cn.hutool.http.useragent.Platform parsePlatform(String userAgentString) {
|
||||
for (aiyh.utils.tool.cn.hutool.http.useragent.Platform platform : aiyh.utils.tool.cn.hutool.http.useragent.Platform.platforms) {
|
||||
if (platform.isMatch(userAgentString)) {
|
||||
return platform;
|
||||
}
|
||||
}
|
||||
return Platform.Unknown;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
package aiyh.utils.tool.cn.hutool.http.useragent;
|
||||
|
||||
/**
|
||||
* User-Agent工具类
|
||||
*
|
||||
* @author looly
|
||||
*/
|
||||
public class UserAgentUtil {
|
||||
|
||||
/**
|
||||
* 解析User-Agent
|
||||
*
|
||||
* @param userAgentString User-Agent字符串
|
||||
* @return {@link UserAgent}
|
||||
*/
|
||||
public static UserAgent parse(String userAgentString) {
|
||||
return UserAgentParser.parse(userAgentString);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
/**
|
||||
* User-Agent解析
|
||||
*
|
||||
* @author looly
|
||||
*/
|
||||
package aiyh.utils.tool.cn.hutool.http.useragent;
|
|
@ -0,0 +1,654 @@
|
|||
package aiyh.utils.tool.cn.hutool.http.webservice;
|
||||
|
||||
import aiyh.utils.tool.cn.hutool.core.collection.CollUtil;
|
||||
import aiyh.utils.tool.cn.hutool.core.io.IoUtil;
|
||||
import aiyh.utils.tool.cn.hutool.core.map.MapUtil;
|
||||
import aiyh.utils.tool.cn.hutool.core.util.ObjectUtil;
|
||||
import aiyh.utils.tool.cn.hutool.core.util.StrUtil;
|
||||
import aiyh.utils.tool.cn.hutool.core.util.XmlUtil;
|
||||
import aiyh.utils.tool.cn.hutool.http.HttpBase;
|
||||
import aiyh.utils.tool.cn.hutool.http.HttpGlobalConfig;
|
||||
import aiyh.utils.tool.cn.hutool.http.HttpRequest;
|
||||
import aiyh.utils.tool.cn.hutool.http.HttpResponse;
|
||||
|
||||
import javax.xml.XMLConstants;
|
||||
import javax.xml.namespace.QName;
|
||||
import javax.xml.soap.*;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
/**
|
||||
* SOAP客户端
|
||||
*
|
||||
* <p>
|
||||
* 此对象用于构建一个SOAP消息,并通过HTTP接口发出消息内容。
|
||||
* SOAP消息本质上是一个XML文本,可以通过调用{@link #getMsgStr(boolean)} 方法获取消息体
|
||||
* <p>
|
||||
* 使用方法:
|
||||
*
|
||||
* <pre>
|
||||
* SoapClient client = SoapClient.create(url)
|
||||
* .setMethod(methodName, namespaceURI)
|
||||
* .setCharset(CharsetUtil.CHARSET_GBK)
|
||||
* .setParam(param1, "XXX");
|
||||
*
|
||||
* String response = client.send(true);
|
||||
*
|
||||
* </pre>
|
||||
*
|
||||
* @author looly
|
||||
* @since 4.5.4
|
||||
*/
|
||||
public class SoapClient extends HttpBase<SoapClient> {
|
||||
|
||||
/**
|
||||
* XML消息体的Content-Type
|
||||
* soap1.1 : text/xml
|
||||
* soap1.2 : application/soap+xml
|
||||
* soap1.1与soap1.2区别: https://www.cnblogs.com/qlqwjy/p/7577147.html
|
||||
*/
|
||||
private static final String CONTENT_TYPE_SOAP11_TEXT_XML = "text/xml;charset=";
|
||||
private static final String CONTENT_TYPE_SOAP12_SOAP_XML = "application/soap+xml;charset=";
|
||||
|
||||
/**
|
||||
* 请求的URL地址
|
||||
*/
|
||||
private String url;
|
||||
|
||||
/**
|
||||
* 默认连接超时
|
||||
*/
|
||||
private int connectionTimeout = aiyh.utils.tool.cn.hutool.http.HttpGlobalConfig.getTimeout();
|
||||
/**
|
||||
* 默认读取超时
|
||||
*/
|
||||
private int readTimeout = HttpGlobalConfig.getTimeout();
|
||||
|
||||
/**
|
||||
* 消息工厂,用于创建消息
|
||||
*/
|
||||
private MessageFactory factory;
|
||||
/**
|
||||
* SOAP消息
|
||||
*/
|
||||
private SOAPMessage message;
|
||||
/**
|
||||
* 消息方法节点
|
||||
*/
|
||||
private SOAPBodyElement methodEle;
|
||||
/**
|
||||
* 应用于方法上的命名空间URI
|
||||
*/
|
||||
private final String namespaceURI;
|
||||
/**
|
||||
* Soap协议
|
||||
* soap1.1 : text/xml
|
||||
* soap1.2 : application/soap+xml
|
||||
*/
|
||||
private final SoapProtocol protocol;
|
||||
|
||||
/**
|
||||
* 创建SOAP客户端,默认使用soap1.1版本协议
|
||||
*
|
||||
* @param url WS的URL地址
|
||||
* @return this
|
||||
*/
|
||||
public static SoapClient create(String url) {
|
||||
return new SoapClient(url);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建SOAP客户端
|
||||
*
|
||||
* @param url WS的URL地址
|
||||
* @param protocol 协议,见{@link SoapProtocol}
|
||||
* @return this
|
||||
*/
|
||||
public static SoapClient create(String url, SoapProtocol protocol) {
|
||||
return new SoapClient(url, protocol);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建SOAP客户端
|
||||
*
|
||||
* @param url WS的URL地址
|
||||
* @param protocol 协议,见{@link SoapProtocol}
|
||||
* @param namespaceURI 方法上的命名空间URI
|
||||
* @return this
|
||||
* @since 4.5.6
|
||||
*/
|
||||
public static SoapClient create(String url, SoapProtocol protocol, String namespaceURI) {
|
||||
return new SoapClient(url, protocol, namespaceURI);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造,默认使用soap1.1版本协议
|
||||
*
|
||||
* @param url WS的URL地址
|
||||
*/
|
||||
public SoapClient(String url) {
|
||||
this(url, SoapProtocol.SOAP_1_1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param url WS的URL地址
|
||||
* @param protocol 协议版本,见{@link SoapProtocol}
|
||||
*/
|
||||
public SoapClient(String url, SoapProtocol protocol) {
|
||||
this(url, protocol, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param url WS的URL地址
|
||||
* @param protocol 协议版本,见{@link SoapProtocol}
|
||||
* @param namespaceURI 方法上的命名空间URI
|
||||
* @since 4.5.6
|
||||
*/
|
||||
public SoapClient(String url, SoapProtocol protocol, String namespaceURI) {
|
||||
this.url = url;
|
||||
this.namespaceURI = namespaceURI;
|
||||
this.protocol = protocol;
|
||||
init(protocol);
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化
|
||||
*
|
||||
* @param protocol 协议版本枚举,见{@link SoapProtocol}
|
||||
* @return this
|
||||
*/
|
||||
public SoapClient init(SoapProtocol protocol) {
|
||||
// 创建消息工厂
|
||||
try {
|
||||
this.factory = MessageFactory.newInstance(protocol.getValue());
|
||||
// 根据消息工厂创建SoapMessage
|
||||
this.message = factory.createMessage();
|
||||
} catch (SOAPException e) {
|
||||
throw new SoapRuntimeException(e);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置SOAP客户端,用于客户端复用
|
||||
*
|
||||
* <p>
|
||||
* 重置后需调用serMethod方法重新指定请求方法,并调用setParam方法重新定义参数
|
||||
*
|
||||
* @return this
|
||||
* @since 4.6.7
|
||||
*/
|
||||
public SoapClient reset() {
|
||||
try {
|
||||
this.message = factory.createMessage();
|
||||
} catch (SOAPException e) {
|
||||
throw new SoapRuntimeException(e);
|
||||
}
|
||||
this.methodEle = null;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置编码
|
||||
*
|
||||
* @param charset 编码
|
||||
* @return this
|
||||
* @see #charset(Charset)
|
||||
*/
|
||||
public SoapClient setCharset(Charset charset) {
|
||||
return this.charset(charset);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SoapClient charset(Charset charset) {
|
||||
super.charset(charset);
|
||||
try {
|
||||
this.message.setProperty(SOAPMessage.CHARACTER_SET_ENCODING, this.charset());
|
||||
this.message.setProperty(SOAPMessage.WRITE_XML_DECLARATION, "true");
|
||||
} catch (SOAPException e) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置Webservice请求地址
|
||||
*
|
||||
* @param url Webservice请求地址
|
||||
* @return this
|
||||
*/
|
||||
public SoapClient setUrl(String url) {
|
||||
this.url = url;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 增加SOAP头信息,方法返回{@link SOAPHeaderElement}可以设置具体属性和子节点
|
||||
*
|
||||
* @param name 头信息标签名
|
||||
* @param actorURI 中间的消息接收者
|
||||
* @param roleUri Role的URI
|
||||
* @param mustUnderstand 标题项对于要对其进行处理的接收者来说是强制的还是可选的
|
||||
* @param relay relay属性
|
||||
* @return {@link SOAPHeaderElement}
|
||||
* @since 5.4.4
|
||||
*/
|
||||
public SOAPHeaderElement addSOAPHeader(QName name, String actorURI, String roleUri, Boolean mustUnderstand, Boolean relay) {
|
||||
final SOAPHeaderElement ele = addSOAPHeader(name);
|
||||
try {
|
||||
if (StrUtil.isNotBlank(roleUri)) {
|
||||
ele.setRole(roleUri);
|
||||
}
|
||||
if (null != relay) {
|
||||
ele.setRelay(relay);
|
||||
}
|
||||
} catch (SOAPException e) {
|
||||
throw new SoapRuntimeException(e);
|
||||
}
|
||||
|
||||
if (StrUtil.isNotBlank(actorURI)) {
|
||||
ele.setActor(actorURI);
|
||||
}
|
||||
if (null != mustUnderstand) {
|
||||
ele.setMustUnderstand(mustUnderstand);
|
||||
}
|
||||
|
||||
return ele;
|
||||
}
|
||||
|
||||
/**
|
||||
* 增加SOAP头信息,方法返回{@link SOAPHeaderElement}可以设置具体属性和子节点
|
||||
*
|
||||
* @param localName 头节点名称
|
||||
* @return {@link SOAPHeaderElement}
|
||||
* @since 5.4.7
|
||||
*/
|
||||
public SOAPHeaderElement addSOAPHeader(String localName) {
|
||||
return addSOAPHeader(new QName(localName));
|
||||
}
|
||||
|
||||
/**
|
||||
* 增加SOAP头信息,方法返回{@link SOAPHeaderElement}可以设置具体属性和子节点
|
||||
*
|
||||
* @param localName 头节点名称
|
||||
* @param value 头节点的值
|
||||
* @return {@link SOAPHeaderElement}
|
||||
* @since 5.4.7
|
||||
*/
|
||||
public SOAPHeaderElement addSOAPHeader(String localName, String value) {
|
||||
final SOAPHeaderElement soapHeaderElement = addSOAPHeader(localName);
|
||||
soapHeaderElement.setTextContent(value);
|
||||
return soapHeaderElement;
|
||||
}
|
||||
|
||||
/**
|
||||
* 增加SOAP头信息,方法返回{@link SOAPHeaderElement}可以设置具体属性和子节点
|
||||
*
|
||||
* @param name 头节点名称
|
||||
* @return {@link SOAPHeaderElement}
|
||||
* @since 5.4.4
|
||||
*/
|
||||
public SOAPHeaderElement addSOAPHeader(QName name) {
|
||||
SOAPHeaderElement ele;
|
||||
try {
|
||||
ele = this.message.getSOAPHeader().addHeaderElement(name);
|
||||
} catch (SOAPException e) {
|
||||
throw new SoapRuntimeException(e);
|
||||
}
|
||||
return ele;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置请求方法
|
||||
*
|
||||
* @param name 方法名及其命名空间
|
||||
* @param params 参数
|
||||
* @param useMethodPrefix 是否使用方法的命名空间前缀
|
||||
* @return this
|
||||
*/
|
||||
public SoapClient setMethod(Name name, Map<String, Object> params, boolean useMethodPrefix) {
|
||||
return setMethod(new QName(name.getURI(), name.getLocalName(), name.getPrefix()), params, useMethodPrefix);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置请求方法
|
||||
*
|
||||
* @param name 方法名及其命名空间
|
||||
* @param params 参数
|
||||
* @param useMethodPrefix 是否使用方法的命名空间前缀
|
||||
* @return this
|
||||
*/
|
||||
public SoapClient setMethod(QName name, Map<String, Object> params, boolean useMethodPrefix) {
|
||||
setMethod(name);
|
||||
final String prefix = useMethodPrefix ? name.getPrefix() : null;
|
||||
final SOAPBodyElement methodEle = this.methodEle;
|
||||
for (Entry<String, Object> entry : MapUtil.wrap(params)) {
|
||||
setParam(methodEle, entry.getKey(), entry.getValue(), prefix);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置请求方法<br>
|
||||
* 方法名自动识别前缀,前缀和方法名使用“:”分隔<br>
|
||||
* 当识别到前缀后,自动添加xmlns属性,关联到默认的namespaceURI
|
||||
*
|
||||
* @param methodName 方法名
|
||||
* @return this
|
||||
*/
|
||||
public SoapClient setMethod(String methodName) {
|
||||
return setMethod(methodName, ObjectUtil.defaultIfNull(this.namespaceURI, XMLConstants.NULL_NS_URI));
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置请求方法<br>
|
||||
* 方法名自动识别前缀,前缀和方法名使用“:”分隔<br>
|
||||
* 当识别到前缀后,自动添加xmlns属性,关联到传入的namespaceURI
|
||||
*
|
||||
* @param methodName 方法名(可有前缀也可无)
|
||||
* @param namespaceURI 命名空间URI
|
||||
* @return this
|
||||
*/
|
||||
public SoapClient setMethod(String methodName, String namespaceURI) {
|
||||
final List<String> methodNameList = StrUtil.split(methodName, ':');
|
||||
final QName qName;
|
||||
if (2 == methodNameList.size()) {
|
||||
qName = new QName(namespaceURI, methodNameList.get(1), methodNameList.get(0));
|
||||
} else {
|
||||
qName = new QName(namespaceURI, methodName);
|
||||
}
|
||||
return setMethod(qName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置请求方法
|
||||
*
|
||||
* @param name 方法名及其命名空间
|
||||
* @return this
|
||||
*/
|
||||
public SoapClient setMethod(QName name) {
|
||||
try {
|
||||
this.methodEle = this.message.getSOAPBody().addBodyElement(name);
|
||||
} catch (SOAPException e) {
|
||||
throw new SoapRuntimeException(e);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置方法参数,使用方法的前缀
|
||||
*
|
||||
* @param name 参数名
|
||||
* @param value 参数值,可以是字符串或Map或{@link SOAPElement}
|
||||
* @return this
|
||||
*/
|
||||
public SoapClient setParam(String name, Object value) {
|
||||
return setParam(name, value, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置方法参数
|
||||
*
|
||||
* @param name 参数名
|
||||
* @param value 参数值,可以是字符串或Map或{@link SOAPElement}
|
||||
* @param useMethodPrefix 是否使用方法的命名空间前缀
|
||||
* @return this
|
||||
*/
|
||||
public SoapClient setParam(String name, Object value, boolean useMethodPrefix) {
|
||||
setParam(this.methodEle, name, value, useMethodPrefix ? this.methodEle.getPrefix() : null);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量设置参数,使用方法的前缀
|
||||
*
|
||||
* @param params 参数列表
|
||||
* @return this
|
||||
* @since 4.5.6
|
||||
*/
|
||||
public SoapClient setParams(Map<String, Object> params) {
|
||||
return setParams(params, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量设置参数
|
||||
*
|
||||
* @param params 参数列表
|
||||
* @param useMethodPrefix 是否使用方法的命名空间前缀
|
||||
* @return this
|
||||
* @since 4.5.6
|
||||
*/
|
||||
public SoapClient setParams(Map<String, Object> params, boolean useMethodPrefix) {
|
||||
for (Entry<String, Object> entry : MapUtil.wrap(params)) {
|
||||
setParam(entry.getKey(), entry.getValue(), useMethodPrefix);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取方法节点<br>
|
||||
* 用于创建子节点等操作
|
||||
*
|
||||
* @return {@link SOAPBodyElement}
|
||||
* @since 4.5.6
|
||||
*/
|
||||
public SOAPBodyElement getMethodEle() {
|
||||
return this.methodEle;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取SOAP消息对象 {@link SOAPMessage}
|
||||
*
|
||||
* @return {@link SOAPMessage}
|
||||
* @since 4.5.6
|
||||
*/
|
||||
public SOAPMessage getMessage() {
|
||||
return this.message;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取SOAP请求消息
|
||||
*
|
||||
* @param pretty 是否格式化
|
||||
* @return 消息字符串
|
||||
*/
|
||||
public String getMsgStr(boolean pretty) {
|
||||
return SoapUtil.toString(this.message, pretty, this.charset);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将SOAP消息的XML内容输出到流
|
||||
*
|
||||
* @param out 输出流
|
||||
* @return this
|
||||
* @since 4.5.6
|
||||
*/
|
||||
public SoapClient write(OutputStream out) {
|
||||
try {
|
||||
this.message.writeTo(out);
|
||||
} catch (SOAPException | IOException e) {
|
||||
throw new SoapRuntimeException(e);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置超时,单位:毫秒<br>
|
||||
* 超时包括:
|
||||
*
|
||||
* <pre>
|
||||
* 1. 连接超时
|
||||
* 2. 读取响应超时
|
||||
* </pre>
|
||||
*
|
||||
* @param milliseconds 超时毫秒数
|
||||
* @return this
|
||||
* @see #setConnectionTimeout(int)
|
||||
* @see #setReadTimeout(int)
|
||||
*/
|
||||
public SoapClient timeout(int milliseconds) {
|
||||
setConnectionTimeout(milliseconds);
|
||||
setReadTimeout(milliseconds);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置连接超时,单位:毫秒
|
||||
*
|
||||
* @param milliseconds 超时毫秒数
|
||||
* @return this
|
||||
* @since 4.5.6
|
||||
*/
|
||||
public SoapClient setConnectionTimeout(int milliseconds) {
|
||||
this.connectionTimeout = milliseconds;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置连接超时,单位:毫秒
|
||||
*
|
||||
* @param milliseconds 超时毫秒数
|
||||
* @return this
|
||||
* @since 4.5.6
|
||||
*/
|
||||
public SoapClient setReadTimeout(int milliseconds) {
|
||||
this.readTimeout = milliseconds;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行Webservice请求,即发送SOAP内容
|
||||
*
|
||||
* @return 返回结果
|
||||
*/
|
||||
public SOAPMessage sendForMessage() {
|
||||
final aiyh.utils.tool.cn.hutool.http.HttpResponse res = sendForResponse();
|
||||
final MimeHeaders headers = new MimeHeaders();
|
||||
for (Entry<String, List<String>> entry : res.headers().entrySet()) {
|
||||
if (StrUtil.isNotEmpty(entry.getKey())) {
|
||||
headers.setHeader(entry.getKey(), CollUtil.get(entry.getValue(), 0));
|
||||
}
|
||||
}
|
||||
try {
|
||||
return this.factory.createMessage(headers, res.bodyStream());
|
||||
} catch (IOException | SOAPException e) {
|
||||
throw new SoapRuntimeException(e);
|
||||
} finally {
|
||||
IoUtil.close(res);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行Webservice请求,即发送SOAP内容
|
||||
*
|
||||
* @return 返回结果
|
||||
*/
|
||||
public String send() {
|
||||
return send(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行Webservice请求,即发送SOAP内容
|
||||
*
|
||||
* @param pretty 是否格式化
|
||||
* @return 返回结果
|
||||
*/
|
||||
public String send(boolean pretty) {
|
||||
final String body = sendForResponse().body();
|
||||
return pretty ? XmlUtil.format(body) : body;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------------------------------------- Private method start
|
||||
|
||||
/**
|
||||
* 发送请求,获取异步响应
|
||||
*
|
||||
* @return 响应对象
|
||||
*/
|
||||
public HttpResponse sendForResponse() {
|
||||
return HttpRequest.post(this.url)//
|
||||
.setFollowRedirects(true)//
|
||||
.setConnectionTimeout(this.connectionTimeout)
|
||||
.setReadTimeout(this.readTimeout)
|
||||
.contentType(getXmlContentType())//
|
||||
.header(this.headers())
|
||||
.body(getMsgStr(false))//
|
||||
.executeAsync();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取请求的Content-Type,附加编码信息
|
||||
*
|
||||
* @return 请求的Content-Type
|
||||
*/
|
||||
private String getXmlContentType() {
|
||||
switch (this.protocol) {
|
||||
case SOAP_1_1:
|
||||
return CONTENT_TYPE_SOAP11_TEXT_XML.concat(this.charset.toString());
|
||||
case SOAP_1_2:
|
||||
return CONTENT_TYPE_SOAP12_SOAP_XML.concat(this.charset.toString());
|
||||
default:
|
||||
throw new SoapRuntimeException("Unsupported protocol: {}", this.protocol);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置方法参数
|
||||
*
|
||||
* @param ele 方法节点
|
||||
* @param name 参数名
|
||||
* @param value 参数值
|
||||
* @param prefix 命名空间前缀, {@code null}表示不使用前缀
|
||||
* @return {@link SOAPElement}子节点
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
private static SOAPElement setParam(SOAPElement ele, String name, Object value, String prefix) {
|
||||
final SOAPElement childEle;
|
||||
try {
|
||||
if (StrUtil.isNotBlank(prefix)) {
|
||||
childEle = ele.addChildElement(name, prefix);
|
||||
} else {
|
||||
childEle = ele.addChildElement(name);
|
||||
}
|
||||
} catch (SOAPException e) {
|
||||
throw new SoapRuntimeException(e);
|
||||
}
|
||||
|
||||
if (null != value) {
|
||||
if (value instanceof SOAPElement) {
|
||||
// 单个子节点
|
||||
try {
|
||||
ele.addChildElement((SOAPElement) value);
|
||||
} catch (SOAPException e) {
|
||||
throw new SoapRuntimeException(e);
|
||||
}
|
||||
} else if (value instanceof Map) {
|
||||
// 多个字节点
|
||||
Entry entry;
|
||||
for (Object obj : ((Map) value).entrySet()) {
|
||||
entry = (Entry) obj;
|
||||
setParam(childEle, entry.getKey().toString(), entry.getValue(), prefix);
|
||||
}
|
||||
} else {
|
||||
// 单个值
|
||||
childEle.setValue(value.toString());
|
||||
}
|
||||
}
|
||||
|
||||
return childEle;
|
||||
}
|
||||
// -------------------------------------------------------------------------------------------------------- Private method end
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
package aiyh.utils.tool.cn.hutool.http.webservice;
|
||||
|
||||
import javax.xml.soap.SOAPConstants;
|
||||
|
||||
/**
|
||||
* SOAP协议版本枚举
|
||||
*
|
||||
* @author looly
|
||||
*/
|
||||
public enum SoapProtocol {
|
||||
/** SOAP 1.1协议 */
|
||||
SOAP_1_1(SOAPConstants.SOAP_1_1_PROTOCOL),
|
||||
/** SOAP 1.2协议 */
|
||||
SOAP_1_2(SOAPConstants.SOAP_1_2_PROTOCOL);
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param value {@link SOAPConstants} 中的协议版本值
|
||||
*/
|
||||
SoapProtocol(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
private final String value;
|
||||
|
||||
/**
|
||||
* 获取版本值信息
|
||||
*
|
||||
* @return 版本值信息
|
||||
*/
|
||||
public String getValue() {
|
||||
return this.value;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
package aiyh.utils.tool.cn.hutool.http.webservice;
|
||||
|
||||
import aiyh.utils.tool.cn.hutool.core.util.StrUtil;
|
||||
|
||||
/**
|
||||
* SOAP异常
|
||||
*
|
||||
* @author xiaoleilu
|
||||
*/
|
||||
public class SoapRuntimeException extends RuntimeException {
|
||||
private static final long serialVersionUID = 8247610319171014183L;
|
||||
|
||||
public SoapRuntimeException(Throwable e) {
|
||||
super(e.getMessage(), e);
|
||||
}
|
||||
|
||||
public SoapRuntimeException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public SoapRuntimeException(String messageTemplate, Object... params) {
|
||||
super(StrUtil.format(messageTemplate, params));
|
||||
}
|
||||
|
||||
public SoapRuntimeException(String message, Throwable throwable) {
|
||||
super(message, throwable);
|
||||
}
|
||||
|
||||
public SoapRuntimeException(Throwable throwable, String messageTemplate, Object... params) {
|
||||
super(StrUtil.format(messageTemplate, params), throwable);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,91 @@
|
|||
package aiyh.utils.tool.cn.hutool.http.webservice;
|
||||
|
||||
import aiyh.utils.tool.cn.hutool.core.exceptions.UtilException;
|
||||
import aiyh.utils.tool.cn.hutool.core.util.CharsetUtil;
|
||||
import aiyh.utils.tool.cn.hutool.core.util.XmlUtil;
|
||||
|
||||
import javax.xml.soap.SOAPException;
|
||||
import javax.xml.soap.SOAPMessage;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
/**
|
||||
* SOAP相关工具类
|
||||
*
|
||||
* @author looly
|
||||
* @since 4.5.7
|
||||
*/
|
||||
public class SoapUtil {
|
||||
|
||||
/**
|
||||
* 创建SOAP客户端,默认使用soap1.1版本协议
|
||||
*
|
||||
* @param url WS的URL地址
|
||||
* @return {@link aiyh.utils.tool.cn.hutool.http.webservice.SoapClient}
|
||||
*/
|
||||
public static aiyh.utils.tool.cn.hutool.http.webservice.SoapClient createClient(String url) {
|
||||
return aiyh.utils.tool.cn.hutool.http.webservice.SoapClient.create(url);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建SOAP客户端
|
||||
*
|
||||
* @param url WS的URL地址
|
||||
* @param protocol 协议,见{@link SoapProtocol}
|
||||
* @return {@link aiyh.utils.tool.cn.hutool.http.webservice.SoapClient}
|
||||
*/
|
||||
public static aiyh.utils.tool.cn.hutool.http.webservice.SoapClient createClient(String url, SoapProtocol protocol) {
|
||||
return aiyh.utils.tool.cn.hutool.http.webservice.SoapClient.create(url, protocol);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建SOAP客户端
|
||||
*
|
||||
* @param url WS的URL地址
|
||||
* @param protocol 协议,见{@link SoapProtocol}
|
||||
* @param namespaceURI 方法上的命名空间URI
|
||||
* @return {@link aiyh.utils.tool.cn.hutool.http.webservice.SoapClient}
|
||||
* @since 4.5.6
|
||||
*/
|
||||
public static aiyh.utils.tool.cn.hutool.http.webservice.SoapClient createClient(String url, SoapProtocol protocol, String namespaceURI) {
|
||||
return SoapClient.create(url, protocol, namespaceURI);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link SOAPMessage} 转为字符串
|
||||
*
|
||||
* @param message SOAP消息对象
|
||||
* @param pretty 是否格式化
|
||||
* @return SOAP XML字符串
|
||||
*/
|
||||
public static String toString(SOAPMessage message, boolean pretty) {
|
||||
return toString(message, pretty, CharsetUtil.CHARSET_UTF_8);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link SOAPMessage} 转为字符串
|
||||
*
|
||||
* @param message SOAP消息对象
|
||||
* @param pretty 是否格式化
|
||||
* @param charset 编码
|
||||
* @return SOAP XML字符串
|
||||
* @since 4.5.7
|
||||
*/
|
||||
public static String toString(SOAPMessage message, boolean pretty, Charset charset) {
|
||||
final ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
try {
|
||||
message.writeTo(out);
|
||||
} catch (SOAPException | IOException e) {
|
||||
throw new SoapRuntimeException(e);
|
||||
}
|
||||
String messageToString;
|
||||
try {
|
||||
messageToString = out.toString(charset.toString());
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new UtilException(e);
|
||||
}
|
||||
return pretty ? XmlUtil.format(messageToString) : messageToString;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
/**
|
||||
* Webservice客户端封装实现
|
||||
*
|
||||
* @author looly
|
||||
*/
|
||||
package aiyh.utils.tool.cn.hutool.http.webservice;
|
|
@ -0,0 +1,61 @@
|
|||
package com.api.youhong.ai.ihgzhouji.taskele.contoller;
|
||||
|
||||
import aiyh.utils.ApiResult;
|
||||
import aiyh.utils.Util;
|
||||
import com.api.youhong.ai.ihgzhouji.taskele.service.TaskElementService;
|
||||
import io.swagger.v3.oas.annotations.parameters.RequestBody;
|
||||
import org.apache.log4j.Logger;
|
||||
import weaver.hrm.HrmUserVarify;
|
||||
import weaver.hrm.User;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.ws.rs.*;
|
||||
import javax.ws.rs.core.Context;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* <h1>任务列表元素</h1>
|
||||
*
|
||||
* <p>create: 2023/5/4 17:13</p>
|
||||
*
|
||||
* @author youHong.ai
|
||||
*/
|
||||
@Path("aiyh/ihg/task")
|
||||
public class TaskElementController {
|
||||
private final Logger log = Util.getLogger();
|
||||
|
||||
private final TaskElementService service = new TaskElementService();
|
||||
|
||||
@Path("/list-get")
|
||||
@GET
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
public String getList(@Context HttpServletRequest request, @Context HttpServletResponse response) {
|
||||
User user = HrmUserVarify.getUser(request, response);
|
||||
try {
|
||||
return ApiResult.success(service.getList(user));
|
||||
} catch (Exception e) {
|
||||
log.error("get task list error!\n" + Util.getErrString(e));
|
||||
return ApiResult.error("system error!");
|
||||
}
|
||||
}
|
||||
|
||||
@Path("/search-list")
|
||||
@POST
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
public String searchList(@Context HttpServletRequest request,
|
||||
@Context HttpServletResponse response,
|
||||
@RequestBody Map<String, Object> params,
|
||||
@QueryParam("id") String configId) {
|
||||
User user = HrmUserVarify.getUser(request, response);
|
||||
try {
|
||||
return ApiResult.success(service.getList(user, params,configId));
|
||||
} catch (Exception e) {
|
||||
log.error("get task list error!\n" + Util.getErrString(e));
|
||||
return ApiResult.error("system error!");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
package com.api.youhong.ai.ihgzhouji.taskele.entity;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
|
||||
/**
|
||||
* <h1>配置表</h1>
|
||||
*
|
||||
* <p>create: 2023/5/4 17:35</p>
|
||||
*
|
||||
* @author youHong.ai
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@ToString
|
||||
@EqualsAndHashCode
|
||||
public class IhgTaskElementConfigItem {
|
||||
/** id */
|
||||
private String id;
|
||||
/** 标题 */
|
||||
private String title;
|
||||
/** 单位 */
|
||||
private String unit;
|
||||
/** 标题英文 */
|
||||
private String titleEn;
|
||||
/** 单位英文 */
|
||||
private String unitEn;
|
||||
/** icon */
|
||||
private String icon;
|
||||
/** 激活icon */
|
||||
private String iconActive;
|
||||
/** 查看权限 */
|
||||
private String viewAuthority;
|
||||
/** 数据来源 */
|
||||
private String dataSource;
|
||||
|
||||
/** 跳转链接 */
|
||||
private String link;
|
||||
/** 数据来源值 */
|
||||
private String customerValue;
|
||||
}
|
|
@ -0,0 +1,97 @@
|
|||
package com.api.youhong.ai.ihgzhouji.taskele.mapper;
|
||||
|
||||
import aiyh.utils.annotation.recordset.*;
|
||||
import com.api.youhong.ai.ihgzhouji.taskele.entity.IhgTaskElementConfigItem;
|
||||
import weaver.hrm.User;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* <h1></h1>
|
||||
*
|
||||
* <p>create: 2023/5/4 17:23</p>
|
||||
*
|
||||
* @author youHong.ai
|
||||
*/
|
||||
@SqlMapper
|
||||
public interface TaskElementMapper {
|
||||
|
||||
|
||||
/**
|
||||
* <h2>查询配置信息</h2>
|
||||
*
|
||||
* @return 配置信息
|
||||
*/
|
||||
@Select("select * from uf_ihg_el_config ")
|
||||
@Associations({
|
||||
@Association(
|
||||
property = "icon",
|
||||
column = "icon",
|
||||
id = @Id(methodId = 1, value = String.class)),
|
||||
@Association(
|
||||
property = "iconActive",
|
||||
column = "icon_active",
|
||||
id = @Id(methodId = 1, value = String.class))
|
||||
})
|
||||
List<IhgTaskElementConfigItem> selectConfig();
|
||||
|
||||
/**
|
||||
* <h2>查询配置信息 </h2>
|
||||
*
|
||||
* @param configId 配置id
|
||||
* @return 配置信息
|
||||
*/
|
||||
@Select("select * from uf_ihg_el_config where id = #{configId}")
|
||||
@Associations({
|
||||
@Association(
|
||||
property = "icon",
|
||||
column = "icon",
|
||||
id = @Id(methodId = 1, value = String.class)),
|
||||
@Association(
|
||||
property = "iconActive",
|
||||
column = "icon_active",
|
||||
id = @Id(methodId = 1, value = String.class))
|
||||
})
|
||||
IhgTaskElementConfigItem selectConfig(@ParamMapper("configId") String configId);
|
||||
|
||||
|
||||
/**
|
||||
* <h2>查询图片地址</h2>
|
||||
*
|
||||
* @param id docId
|
||||
* @return 图片地址
|
||||
*/
|
||||
@Select("select concat('/weaver/weaver.file.FileDownload?fileid=',IMAGEFILEID) from docimagefile where DOCID = #{id}")
|
||||
@AssociationMethod(1)
|
||||
String selectImageFileId(String id);
|
||||
|
||||
/**
|
||||
* <h2>查询权限信息</h2>
|
||||
*
|
||||
* @param viewAuthoritySql 自定义权限sql
|
||||
* @param user 用户
|
||||
* @param map 参数
|
||||
* @param where 条件参数
|
||||
* @return 权限信息
|
||||
*/
|
||||
@Select(custom = true)
|
||||
List<Map<String, Object>> selectAuthority(@SqlString String viewAuthoritySql,
|
||||
@ParamMapper("user") User user,
|
||||
@ParamMapper("param") Map<String, Object> map);
|
||||
|
||||
/**
|
||||
* <h2>查询任务信息</h2>
|
||||
*
|
||||
* @param customerValue 自定义sql
|
||||
* @param item 参数
|
||||
* @param user 用户
|
||||
* @param where 条件参数
|
||||
* @return 任务信息
|
||||
*/
|
||||
@Select(custom = true)
|
||||
List<Map<String, Object>> selectWorkList(@SqlString String customerValue,
|
||||
@ParamMapper("param") Map<String, Object> item,
|
||||
@ParamMapper("user") User user,
|
||||
@ParamMapper("where") Map<String, Object> where);
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
package com.api.youhong.ai.ihgzhouji.taskele.mapstruct;
|
||||
|
||||
import com.api.youhong.ai.ihgzhouji.taskele.entity.IhgTaskElementConfigItem;
|
||||
import com.api.youhong.ai.ihgzhouji.taskele.vo.IhgTaskElementVo;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
/**
|
||||
* <h1>vo、pojo映射类</h1>
|
||||
*
|
||||
* <p>create: 2023/5/6 11:11</p>
|
||||
*
|
||||
* @author youHong.ai
|
||||
*/
|
||||
@Mapper
|
||||
public interface TaskElementMapstruct {
|
||||
|
||||
TaskElementMapstruct INSTANCE = Mappers.getMapper(TaskElementMapstruct.class);
|
||||
|
||||
/**
|
||||
* <h2>entity转换为vo对象</h2>
|
||||
*
|
||||
* @param item 配置数据
|
||||
* @return vo对象
|
||||
*/
|
||||
@Mapping(target = "size", ignore = true)
|
||||
@Mapping(target = "list", ignore = true)
|
||||
IhgTaskElementVo entity2Vo(IhgTaskElementConfigItem item);
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
package com.api.youhong.ai.ihgzhouji.taskele.service;
|
||||
|
||||
import com.api.youhong.ai.ihgzhouji.taskele.entity.IhgTaskElementConfigItem;
|
||||
import weaver.hrm.User;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* <h1>自定义获取值接口</h1>
|
||||
*
|
||||
* <p>create: 2023/5/5 17:42</p>
|
||||
*
|
||||
* @author youHong.ai
|
||||
*/
|
||||
public interface TaskElementGetValueInterface {
|
||||
|
||||
List<Map<String, Object>> getValue(IhgTaskElementConfigItem taskElementConfigItem,
|
||||
Map<String, Object> param, User user);
|
||||
}
|
|
@ -0,0 +1,160 @@
|
|||
package com.api.youhong.ai.ihgzhouji.taskele.service;
|
||||
|
||||
import aiyh.utils.Util;
|
||||
import aiyh.utils.excention.CustomerException;
|
||||
import aiyh.utils.tool.cn.hutool.core.collection.CollectionUtil;
|
||||
import aiyh.utils.tool.cn.hutool.core.lang.Assert;
|
||||
import aiyh.utils.tool.cn.hutool.core.util.StrUtil;
|
||||
import com.api.youhong.ai.ihgzhouji.taskele.entity.IhgTaskElementConfigItem;
|
||||
import com.api.youhong.ai.ihgzhouji.taskele.mapper.TaskElementMapper;
|
||||
import com.api.youhong.ai.ihgzhouji.taskele.mapstruct.TaskElementMapstruct;
|
||||
import com.api.youhong.ai.ihgzhouji.taskele.vo.IhgTaskElementVo;
|
||||
import weaver.hrm.User;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* <h1>任务列表元素service</h1>
|
||||
*
|
||||
* <p>create: 2023/5/4 17:17</p>
|
||||
*
|
||||
* @author youHong.ai
|
||||
*/
|
||||
public class TaskElementService {
|
||||
private final TaskElementMapper mapper = Util.getMapper(TaskElementMapper.class);
|
||||
|
||||
public List<IhgTaskElementVo> getList(User user) {
|
||||
List<IhgTaskElementConfigItem> ihgTaskElementConfItemList = mapper.selectConfig();
|
||||
if (CollectionUtil.isEmpty(ihgTaskElementConfItemList)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
Map<IhgTaskElementConfigItem, List<Map<String, Object>>> taskConfigMap = new HashMap<>();
|
||||
// 设置查询基础参数
|
||||
Map<String, Object> map = new HashMap<>(8);
|
||||
map.put("currentDate", Util.getTime("yyyy-MM-dd"));
|
||||
map.put("currentTime", Util.getTime("HH:mm:ss"));
|
||||
// 查询每个item的可查看权限并进行过滤
|
||||
for (IhgTaskElementConfigItem ihgTaskElementConfItem : ihgTaskElementConfItemList) {
|
||||
String viewAuthoritySql = ihgTaskElementConfItem.getViewAuthority();
|
||||
List<Map<String, Object>> authorityList = mapper.selectAuthority(viewAuthoritySql, user, map);
|
||||
if (CollectionUtil.isNotEmpty(authorityList)) {
|
||||
taskConfigMap.put(ihgTaskElementConfItem, authorityList);
|
||||
}
|
||||
}
|
||||
// 查询数据
|
||||
List<IhgTaskElementVo> result = new ArrayList<>();
|
||||
for (Map.Entry<IhgTaskElementConfigItem, List<Map<String, Object>>> entry : taskConfigMap.entrySet()) {
|
||||
IhgTaskElementConfigItem taskElementConfigItem = entry.getKey();
|
||||
List<Map<String, Object>> authorityList = entry.getValue();
|
||||
List<Map<String, Object>> workList = queryWorkList(taskElementConfigItem, authorityList, user, "", Collections.emptyMap());
|
||||
IhgTaskElementVo ihgTaskElementVo = TaskElementMapstruct.INSTANCE.entity2Vo(taskElementConfigItem);
|
||||
ihgTaskElementVo.setList(workList);
|
||||
ihgTaskElementVo.setSize(workList.size());
|
||||
if (user.getLanguage() == 8) {
|
||||
ihgTaskElementVo.setTitle(taskElementConfigItem.getTitleEn());
|
||||
ihgTaskElementVo.setUnit(taskElementConfigItem.getUnitEn());
|
||||
}
|
||||
result.add(ihgTaskElementVo);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private List<Map<String, Object>> queryWorkList(IhgTaskElementConfigItem taskElementConfigItem,
|
||||
List<Map<String, Object>> authorityList,
|
||||
User user, String whereStr,
|
||||
Map<String, Object> whereMap) {
|
||||
String dataSource = taskElementConfigItem.getDataSource();
|
||||
String customerValue = taskElementConfigItem.getCustomerValue();
|
||||
if (StrUtil.isBlank(customerValue)) {
|
||||
throw new CustomerException("自定义sql或自定义接口不能为空!");
|
||||
}
|
||||
List<Map<String, Object>> result = new ArrayList<>();
|
||||
for (Map<String, Object> item : authorityList) {
|
||||
if ("0".equals(dataSource)) {
|
||||
// 自定义sql
|
||||
customerValue += " " + whereStr;
|
||||
List<Map<String, Object>> list = mapper.selectWorkList(customerValue, item, user, whereMap);
|
||||
if (CollectionUtil.isNotEmpty(list)) {
|
||||
result.addAll(list);
|
||||
}
|
||||
} else {
|
||||
// 自定义接口
|
||||
Map<String, String> map = Util.parseCusInterfacePathParam(customerValue);
|
||||
map.put("whereStr", whereStr);
|
||||
if (CollectionUtil.isNotEmpty(whereMap)) {
|
||||
for (Map.Entry<String, Object> entry : whereMap.entrySet()) {
|
||||
map.put(entry.getKey(), Util.null2String(entry.getValue()));
|
||||
}
|
||||
}
|
||||
String classPath = map.remove("_ClassPath");
|
||||
item.putAll(map);
|
||||
TaskElementGetValueInterface instance = Util.getClassInstance(classPath, TaskElementGetValueInterface.class);
|
||||
List<Map<String, Object>> list = instance.getValue(taskElementConfigItem, item, user);
|
||||
if (CollectionUtil.isNotEmpty(list)) {
|
||||
result.addAll(list);
|
||||
}
|
||||
}
|
||||
}
|
||||
// 去重
|
||||
result = result.stream()
|
||||
.filter(distinctByKey(item -> item.get("id")))
|
||||
.collect(Collectors.toList());
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
static <T> Predicate<T> distinctByKey(Function<? super T, ?> keyExtractor) {
|
||||
Map<Object, Boolean> seen = new ConcurrentHashMap<>(8);
|
||||
return t -> seen.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null;
|
||||
}
|
||||
|
||||
/**
|
||||
* <h2>查询自定义的数据</h2>
|
||||
*
|
||||
* @param user 用户
|
||||
* @param params 参数
|
||||
* @return 结果
|
||||
*/
|
||||
public IhgTaskElementVo getList(User user, Map<String, Object> params, String configId) {
|
||||
Assert.notBlank(configId, "查询配置表Id为空!");
|
||||
IhgTaskElementConfigItem ihgTaskElementConfItem = mapper.selectConfig(configId);
|
||||
if (Objects.isNull(ihgTaskElementConfItem)) {
|
||||
return null;
|
||||
}
|
||||
// 设置查询基础参数
|
||||
Map<String, Object> map = new HashMap<>(8);
|
||||
map.put("currentDate", Util.getTime("yyyy-MM-dd"));
|
||||
map.put("currentTime", Util.getTime("HH:mm:ss"));
|
||||
StringBuilder builder = new StringBuilder();
|
||||
for (Map.Entry<String, Object> entry : params.entrySet()) {
|
||||
Map<String, Object> valueMap = (Map<String, Object>) entry.getValue();
|
||||
String key = Util.null2String(valueMap.get("key"));
|
||||
String value = Util.null2String(valueMap.get("value"));
|
||||
if (StrUtil.isNotBlank(value)) {
|
||||
builder.append(" and ").append(key).append(" = ").append("#{where.").append(key).append("}");
|
||||
map.put(key, value);
|
||||
}
|
||||
}
|
||||
String whereStr = builder.toString();
|
||||
// 查询可查看权限并进行过滤
|
||||
String viewAuthoritySql = ihgTaskElementConfItem.getViewAuthority();
|
||||
List<Map<String, Object>> authorityList = mapper.selectAuthority(viewAuthoritySql, user, map);
|
||||
if (CollectionUtil.isEmpty(authorityList)) {
|
||||
return null;
|
||||
}
|
||||
// 查询数据
|
||||
List<Map<String, Object>> workList = this.queryWorkList(ihgTaskElementConfItem, authorityList, user, whereStr, map);
|
||||
IhgTaskElementVo ihgTaskElementVo = TaskElementMapstruct.INSTANCE.entity2Vo(ihgTaskElementConfItem);
|
||||
ihgTaskElementVo.setList(workList);
|
||||
ihgTaskElementVo.setSize(workList.size());
|
||||
if (user.getLanguage() == 8) {
|
||||
ihgTaskElementVo.setTitle(ihgTaskElementConfItem.getTitleEn());
|
||||
ihgTaskElementVo.setUnit(ihgTaskElementConfItem.getUnitEn());
|
||||
}
|
||||
return ihgTaskElementVo;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
package com.api.youhong.ai.ihgzhouji.taskele.vo;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* <h1>配置表</h1>
|
||||
*
|
||||
* <p>create: 2023/5/4 17:35</p>
|
||||
*
|
||||
* @author youHong.ai
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@ToString
|
||||
public class IhgTaskElementVo {
|
||||
|
||||
/** id */
|
||||
private String id;
|
||||
/** 标题 */
|
||||
private String title;
|
||||
/** 单位 */
|
||||
private String unit;
|
||||
|
||||
/** icon */
|
||||
private String icon;
|
||||
|
||||
/** 激活icon */
|
||||
private String iconActive;
|
||||
/** 跳转链接 */
|
||||
private String link;
|
||||
|
||||
/** 数据长度 */
|
||||
private Integer size;
|
||||
/** 数据 */
|
||||
private List<Map<String, Object>> list;
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
package com.api.youhong.ai.yashilandai.openbill.controller;
|
||||
|
||||
import aiyh.utils.ApiResult;
|
||||
import aiyh.utils.Util;
|
||||
import com.api.youhong.ai.yashilandai.openbill.service.OpenTheBillService;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import javax.ws.rs.*;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.StreamingOutput;
|
||||
import java.net.URLEncoder;
|
||||
|
||||
/**
|
||||
* <h1>拆单api接口</h1>
|
||||
*
|
||||
* <p>create: 2023/4/25 10:09</p>
|
||||
*
|
||||
* @author youHong.ai
|
||||
*/
|
||||
|
||||
@Path("/ayh/estee-Lauder/open-bill")
|
||||
public class OpenTheBillController {
|
||||
|
||||
private final OpenTheBillService service = new OpenTheBillService();
|
||||
private final Logger log = Util.getLogger();
|
||||
|
||||
@Path("/data/get")
|
||||
@GET
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
public String dataGet(@QueryParam("startDate") String startDate, @QueryParam("endDate") String endDate,
|
||||
@QueryParam("orderNo") String orderNo) {
|
||||
try {
|
||||
return ApiResult.success(service.getOpenBillListData(startDate, endDate, orderNo));
|
||||
} catch (Exception e) {
|
||||
log.error("get open the bill data error!" + Util.getErrString(e));
|
||||
return ApiResult.error("system error!");
|
||||
}
|
||||
}
|
||||
|
||||
@Path("/data/export")
|
||||
@GET
|
||||
@Produces(MediaType.APPLICATION_OCTET_STREAM)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@SuppressWarnings("all")
|
||||
public Response export(@QueryParam("startDate") String startDate, @QueryParam("endDate") String endDate,
|
||||
@QueryParam("orderNo") String orderNo) {
|
||||
try {
|
||||
// File file = service.exportExcel();
|
||||
// StreamingOutput streamingOutput = output -> {
|
||||
// try (InputStream input = Files.newInputStream(file.toPath())) {
|
||||
// IOUtils.copy(input, output);
|
||||
// }
|
||||
// };
|
||||
// // 获取文件大小
|
||||
// long fileSize = file.length();
|
||||
StreamingOutput streamingOutput = outputStream -> {
|
||||
service.exportExcel(outputStream, startDate, endDate,orderNo);
|
||||
};
|
||||
String fileName = new String(("疑似拆单数据-" + Util.getTime("yyyy-MM-dd") + ".xlsx"));
|
||||
String encodedFileName = URLEncoder.encode(fileName, "UTF-8");
|
||||
Response.ResponseBuilder builder = Response.ok(streamingOutput)
|
||||
// 指定编码方式为 UTF-8
|
||||
.header("Content-Disposition", "attachment; filename*=UTF-8''" + encodedFileName);
|
||||
return builder.build();
|
||||
} catch (Exception e) {
|
||||
log.error("导出excel文件失败!" + Util.getErrString(e));
|
||||
return Response.ok(ApiResult.error("导出文件失败!"), MediaType.APPLICATION_JSON).build();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
package com.api.youhong.ai.yashilandai.openbill.controller;
|
||||
|
||||
import aiyh.utils.ApiResult;
|
||||
import aiyh.utils.Util;
|
||||
import com.api.youhong.ai.yashilandai.openbill.service.OpenThenBillService;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import javax.ws.rs.Consumes;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.Produces;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
|
||||
/**
|
||||
* <h1>拆单api接口</h1>
|
||||
*
|
||||
* <p>create: 2023/4/25 10:09</p>
|
||||
*
|
||||
* @author youHong.ai
|
||||
*/
|
||||
|
||||
@Path("/ayh/estee-Lauder/open-bill")
|
||||
public class OpenThenBillController {
|
||||
|
||||
private final OpenThenBillService service = new OpenThenBillService();
|
||||
private final Logger log = Util.getLogger();
|
||||
|
||||
@Path("/data/get")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
public String dataGet() {
|
||||
try {
|
||||
return ApiResult.success(service.getOpenBillListData());
|
||||
} catch (Exception e) {
|
||||
log.error("get open the bill data error!" + Util.getErrString(e));
|
||||
return ApiResult.error("system error!");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
package com.api.youhong.ai.yashilandai.openbill.mapper;
|
||||
|
||||
import aiyh.utils.annotation.recordset.*;
|
||||
import com.api.youhong.ai.yashilandai.openbill.pojo.ConditionDetail;
|
||||
import com.api.youhong.ai.yashilandai.openbill.pojo.ConditionEntity;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* <h1></h1>
|
||||
*
|
||||
* <p>create: 2023/4/25 10:15</p>
|
||||
*
|
||||
* @author youHong.ai
|
||||
*/
|
||||
@SqlMapper
|
||||
public interface OpenTheBillMapper {
|
||||
|
||||
/**
|
||||
* <h2>查询数据列表</h2>
|
||||
*
|
||||
* @param tableName 表名
|
||||
* @return 数据列表
|
||||
*/
|
||||
@Select("select * from $t{tableName}")
|
||||
List<Map<String, Object>> selectList(@ParamMapper("tableName") String tableName);
|
||||
|
||||
@Select("select * from $t{tableName} where $t{dateField} between #{startDate} and #{endDate} $t{condition}")
|
||||
List<Map<String, Object>> selectList(@ParamMapper("tableName") String tableName,
|
||||
@ParamMapper("startDate") String startDate,
|
||||
@ParamMapper("endDate") String endDate,
|
||||
@ParamMapper("dateField") String dateField,
|
||||
@ParamMapper("condition") String condition);
|
||||
|
||||
/**
|
||||
* <h2>查询条件参数信息</h2>
|
||||
*
|
||||
* @param id 条件参数id
|
||||
* @return 条件参数信息
|
||||
*/
|
||||
@Select("select * from uf_ncotjcs where id = #{id}")
|
||||
@CollectionMappings({
|
||||
@CollectionMapping(property = "list",
|
||||
column = "id",
|
||||
id = @Id(methodId = 1, value = String.class))
|
||||
})
|
||||
ConditionEntity selectCondition(@ParamMapper("id") String id);
|
||||
|
||||
/**
|
||||
* <h2>查询条件参数信息明细表</h2>
|
||||
*
|
||||
* @param mainId 主表id
|
||||
* @return 条件参数明细表
|
||||
*/
|
||||
@Select("select * from uf_ncotjcs_dt1 where mainid = #{mainId}")
|
||||
@CollectionMethod(1)
|
||||
List<ConditionDetail> selectConditionDetail(@ParamMapper("mainId") String mainId);
|
||||
|
||||
/**
|
||||
* <h2>查询workflowId 通过requestId</h2>
|
||||
*
|
||||
* @param requestId requestId
|
||||
* @return workflowId
|
||||
*/
|
||||
@Select("select WORKFLOWID from workflow_requestbase where REQUESTID = #{requestId}")
|
||||
String selectWorkflowId(@ParamMapper("requestId") String requestId);
|
||||
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
package com.api.youhong.ai.yashilandai.openbill.mapper;
|
||||
|
||||
import aiyh.utils.annotation.recordset.Select;
|
||||
import aiyh.utils.annotation.recordset.SqlMapper;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* <h1></h1>
|
||||
*
|
||||
* <p>create: 2023/4/25 10:15</p>
|
||||
*
|
||||
* @author youHong.ai
|
||||
*/
|
||||
@SqlMapper
|
||||
public interface OpenThenBillMapper {
|
||||
|
||||
/**
|
||||
* <h2>查询数据列表</h2>
|
||||
*
|
||||
* @return 数据列表
|
||||
*/
|
||||
@Select("select * from v_online")
|
||||
List<Map<String, Object>> selectList();
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
package com.api.youhong.ai.yashilandai.openbill.pojo;
|
||||
|
||||
import aiyh.utils.annotation.recordset.SqlDbFieldAnn;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@ToString
|
||||
public class ConditionDetail {
|
||||
private Integer id;
|
||||
@SqlDbFieldAnn("tjmc")
|
||||
private String conditionName;
|
||||
@SqlDbFieldAnn("tjzdmc")
|
||||
private String conditionFieldName;
|
||||
|
||||
@SqlDbFieldAnn("tjgx")
|
||||
private Integer conditionType;
|
||||
|
||||
@SqlDbFieldAnn("tjzdz")
|
||||
private String conditionValue;
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
package com.api.youhong.ai.yashilandai.openbill.pojo;
|
||||
|
||||
import aiyh.utils.annotation.recordset.SqlDbFieldAnn;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* <h1>条件参数配置表</h1>
|
||||
*
|
||||
* <p>create: 2023/4/25 15:07</p>
|
||||
*
|
||||
* @author youHong.ai
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@ToString
|
||||
public class ConditionEntity {
|
||||
private Integer id;
|
||||
@SqlDbFieldAnn("mxbjgx")
|
||||
private String condition;
|
||||
private List<ConditionDetail> list;
|
||||
}
|
||||
|
|
@ -0,0 +1,572 @@
|
|||
package com.api.youhong.ai.yashilandai.openbill.service;
|
||||
|
||||
import aiyh.utils.ScriptUtil;
|
||||
import aiyh.utils.Util;
|
||||
import aiyh.utils.excention.CustomerException;
|
||||
import aiyh.utils.tool.Assert;
|
||||
import aiyh.utils.tool.cn.hutool.core.collection.CollectionUtil;
|
||||
import aiyh.utils.tool.cn.hutool.core.util.StrUtil;
|
||||
import aiyh.utils.tool.org.apache.commons.jexl3.JexlException;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.api.youhong.ai.yashilandai.openbill.mapper.OpenTheBillMapper;
|
||||
import com.api.youhong.ai.yashilandai.openbill.pojo.ConditionDetail;
|
||||
import com.api.youhong.ai.yashilandai.openbill.pojo.ConditionEntity;
|
||||
import com.api.youhong.ai.yashilandai.openbill.util.ExcelCell;
|
||||
import com.api.youhong.ai.yashilandai.openbill.util.ExcelPort;
|
||||
import com.api.youhong.ai.yashilandai.openbill.util.ExcelRow;
|
||||
import org.apache.log4j.Logger;
|
||||
import org.apache.poi.hssf.util.HSSFColor;
|
||||
import org.apache.poi.ss.usermodel.*;
|
||||
import org.apache.poi.xssf.streaming.SXSSFCell;
|
||||
import org.apache.poi.xssf.streaming.SXSSFRow;
|
||||
import org.apache.poi.xssf.streaming.SXSSFSheet;
|
||||
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
|
||||
|
||||
import java.io.OutputStream;
|
||||
import java.text.DateFormat;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
|
||||
/**
|
||||
* <h1>拆单service</h1>
|
||||
*
|
||||
* <p>create: 2023/4/25 10:12</p>
|
||||
*
|
||||
* @author youHong.ai
|
||||
*/
|
||||
public class OpenTheBillService {
|
||||
|
||||
private final OpenTheBillMapper mapper = Util.getMapper(OpenTheBillMapper.class);
|
||||
|
||||
private final Logger log = Util.getLogger();
|
||||
|
||||
public Map<String, Object> getOpenBillListData(String startDate, String endDate, String orderNo) {
|
||||
return getData(startDate, endDate, orderNo);
|
||||
}
|
||||
|
||||
|
||||
public void exportExcel(OutputStream outputStream, String startDate, String endDate, String orderNo) {
|
||||
Map<String, Object> data = getData(startDate, endDate, orderNo);
|
||||
Map<String, Object> head = (Map<String, Object>) data.get("head");
|
||||
List<Map<String, Object>> body = (List<Map<String, Object>>) data.get("body");
|
||||
List<ExcelRow> list = new ArrayList<>();
|
||||
List<ExcelCell> excelHeadCellList = new ArrayList<>();
|
||||
List<String> fieldList = new ArrayList<>();
|
||||
ExcelRow excelRow = new ExcelRow();
|
||||
excelRow.setRowHeight(21F);
|
||||
excelRow.setDataList(excelHeadCellList);
|
||||
list.add(excelRow);
|
||||
List<String> headFieldList = (List<String>) head.get("field");
|
||||
List<String> headTitleList = (List<String>) head.get("title");
|
||||
for (int i = 0; i < headFieldList.size(); i++) {
|
||||
String title = headTitleList.get(i);
|
||||
String field = headFieldList.get(i);
|
||||
ExcelCell excelCell = new ExcelCell();
|
||||
excelCell.setValue(title);
|
||||
excelHeadCellList.add(excelCell);
|
||||
fieldList.add(field);
|
||||
}
|
||||
for (Map<String, Object> item : body) {
|
||||
ExcelRow excelBodyRow = new ExcelRow();
|
||||
List<ExcelCell> excelCellList = new ArrayList<>();
|
||||
for (String key : fieldList) {
|
||||
Object value = item.get(key);
|
||||
ExcelCell excelCell = new ExcelCell();
|
||||
excelCell.setValue(value);
|
||||
excelCellList.add(excelCell);
|
||||
}
|
||||
excelBodyRow.setDataList(excelCellList);
|
||||
list.add(excelBodyRow);
|
||||
}
|
||||
Map<Integer, CellStyle> headStyle = new HashMap<>();
|
||||
Map<Integer, CellStyle> singularLine = new HashMap<>();
|
||||
Map<Integer, CellStyle> evenNumberLine = new HashMap<>();
|
||||
ExcelPort.exportFile("疑似拆单-" + Util.getTime("yyyy-MM-dd"), list,
|
||||
outputStream,
|
||||
this::setHeaderStyle,
|
||||
this::setBodyStyle,
|
||||
headStyle, singularLine, evenNumberLine);
|
||||
}
|
||||
|
||||
|
||||
private CellStyle setHeaderStyle(SXSSFWorkbook workbook,
|
||||
Integer rowIndex,
|
||||
Integer colIndex,
|
||||
SXSSFRow row,
|
||||
SXSSFCell cell,
|
||||
SXSSFSheet sheet, Map<Integer, CellStyle> headStyle,
|
||||
Map<Integer, CellStyle> headStyle2) {
|
||||
|
||||
if (headStyle.containsKey(colIndex)) {
|
||||
return headStyle.get(colIndex);
|
||||
}
|
||||
// 设置表格单元格格式
|
||||
CellStyle cellStyle = workbook.createCellStyle();
|
||||
cellStyle.setAlignment(HorizontalAlignment.CENTER);
|
||||
cellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
|
||||
cellStyle.setBorderRight(BorderStyle.THIN);
|
||||
cellStyle.setRightBorderColor(IndexedColors.BLACK.getIndex());
|
||||
cellStyle.setBorderLeft(BorderStyle.THIN);
|
||||
cellStyle.setLeftBorderColor(IndexedColors.BLACK.getIndex());
|
||||
cellStyle.setBorderTop(BorderStyle.THIN);
|
||||
cellStyle.setTopBorderColor(IndexedColors.BLACK.getIndex());
|
||||
cellStyle.setBorderBottom(BorderStyle.THIN);
|
||||
cellStyle.setBottomBorderColor(IndexedColors.BLACK.getIndex());
|
||||
cellStyle.setWrapText(true);
|
||||
// 设置字体格式
|
||||
Font font = workbook.createFont();
|
||||
font.setFontName("微软雅黑");
|
||||
font.setFontHeightInPoints((short) 14);
|
||||
font.setBold(true);
|
||||
font.setColor(HSSFColor.HSSFColorPredefined.WHITE.getIndex());
|
||||
int columnWidth = sheet.getColumnWidth(colIndex);
|
||||
String value = cell.getStringCellValue();
|
||||
/** 计算字符串中中文字符的数量 */
|
||||
int count = chineseCharCountOf(value);
|
||||
/**在该列字符长度的基础上加上汉字个数计算列宽 */
|
||||
int length = (value.length() - count) * 256 + (count + 4) * 512;
|
||||
length = length * font.getFontHeightInPoints() / 11;
|
||||
if (length >= columnWidth && length < 256 * 256) {
|
||||
sheet.setColumnWidth(colIndex, length);
|
||||
}
|
||||
cellStyle.setFont(font);
|
||||
cellStyle.setFillBackgroundColor(HSSFColor.HSSFColorPredefined.BLACK.getIndex());
|
||||
cellStyle.setFillPattern(FillPatternType.BRICKS);
|
||||
headStyle.put(colIndex, cellStyle);
|
||||
return cellStyle;
|
||||
}
|
||||
|
||||
|
||||
private CellStyle setBodyStyle(SXSSFWorkbook workbook,
|
||||
Integer rowIndex,
|
||||
Integer colIndex,
|
||||
SXSSFRow row,
|
||||
SXSSFCell cell,
|
||||
SXSSFSheet sheet, Map<Integer, CellStyle> singularLine, Map<Integer, CellStyle> evenNumberLine) {
|
||||
|
||||
int columnWidth = sheet.getColumnWidth(colIndex);
|
||||
String value = cell.getStringCellValue();
|
||||
/** 计算字符串中中文字符的数量 */
|
||||
int count = chineseCharCountOf(value);
|
||||
/**在该列字符长度的基础上加上汉字个数计算列宽 */
|
||||
int length = (value.length() - count) * 256 + (count + 4) * 512;
|
||||
length = length * 10 / 11;
|
||||
if (length >= columnWidth && length < 256 * 256) {
|
||||
sheet.setColumnWidth(colIndex, length);
|
||||
}
|
||||
if (rowIndex % 2 == 1) {
|
||||
if (singularLine.containsKey(colIndex)) {
|
||||
return singularLine.get(colIndex);
|
||||
}
|
||||
CellStyle cellStyle = getCellStyle(workbook, rowIndex, colIndex, cell, sheet);
|
||||
singularLine.put(colIndex, cellStyle);
|
||||
return cellStyle;
|
||||
} else {
|
||||
if (evenNumberLine.containsKey(colIndex)) {
|
||||
return evenNumberLine.get(colIndex);
|
||||
}
|
||||
CellStyle cellStyle = getCellStyle(workbook, rowIndex, colIndex, cell, sheet);
|
||||
// 设置字体格式
|
||||
Font font = workbook.createFont();
|
||||
font.setFontName("微软雅黑");
|
||||
font.setFontHeightInPoints((short) 10);
|
||||
|
||||
cellStyle.setFont(font);
|
||||
evenNumberLine.put(colIndex, cellStyle);
|
||||
return cellStyle;
|
||||
}
|
||||
}
|
||||
|
||||
private CellStyle getCellStyle(SXSSFWorkbook workbook, Integer rowIndex, Integer colIndex, SXSSFCell cell, SXSSFSheet sheet) {
|
||||
|
||||
|
||||
// 设置表格单元格格式
|
||||
CellStyle style = workbook.createCellStyle();
|
||||
style.setVerticalAlignment(VerticalAlignment.CENTER);
|
||||
style.setAlignment(HorizontalAlignment.CENTER);
|
||||
style.setBorderRight(BorderStyle.THIN);
|
||||
style.setRightBorderColor(IndexedColors.BLACK.getIndex());
|
||||
style.setBorderLeft(BorderStyle.THIN);
|
||||
style.setLeftBorderColor(IndexedColors.BLACK.getIndex());
|
||||
style.setBorderTop(BorderStyle.THIN);
|
||||
style.setTopBorderColor(IndexedColors.BLACK.getIndex());
|
||||
style.setBorderBottom(BorderStyle.THIN);
|
||||
style.setBottomBorderColor(IndexedColors.BLACK.getIndex());
|
||||
style.setWrapText(true);
|
||||
// if (rowIndex % 2 == 1) {
|
||||
// XSSFColor color = new XSSFColor(new java.awt.Color(242, 242, 242), new DefaultIndexedColorMap());
|
||||
// style.setFillBackgroundColor(color.getIndex());
|
||||
// style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
|
||||
// }
|
||||
return style;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算字符串中中文字符的数量
|
||||
* 参见 <a hrft="https://www.cnblogs.com/straybirds/p/6392306.html">《汉字unicode编码范围》</a>
|
||||
*
|
||||
* @param input
|
||||
* @return
|
||||
*/
|
||||
private static int chineseCharCountOf(String input) {
|
||||
// 汉字数量
|
||||
int count = 0;
|
||||
if (null != input) {
|
||||
String regEx = "[\\u4e00-\\u9fa5]";
|
||||
Pattern p = Pattern.compile(regEx);
|
||||
Matcher m = p.matcher(input);
|
||||
int len = m.groupCount();
|
||||
// 获取汉字个数
|
||||
while (m.find()) {
|
||||
for (int i = 0; i <= len; i++) {
|
||||
count = count + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
public Map<String, Object> getData(String startDate, String endDate, String orderNo) {
|
||||
Map<String, Object> config = Util.readProperties2Map("esteeLauderExcelExport", "export");
|
||||
Assert.notEmpty(config, "esteeLauderExcelExport.properties文件读取配置为空,请检查配置信息");
|
||||
List<Map<String, Object>> dataList;
|
||||
String condition = "";
|
||||
if (StrUtil.isNotBlank(orderNo)) {
|
||||
condition = " and " + Util.null2String(config.get("orderNo")) + " like '%" + orderNo.replace("%'", "''") + "'";
|
||||
}
|
||||
if (StrUtil.isNotBlank(startDate) && StrUtil.isNotBlank(endDate)) {
|
||||
dataList = mapper.selectList(Util.null2String(config.get("tableName")),
|
||||
startDate, endDate,
|
||||
Util.null2String(config.get("createDate")),
|
||||
condition);
|
||||
} else {
|
||||
dataList = mapper.selectList(Util.null2String(config.get("tableName")));
|
||||
}
|
||||
Object head = config.get("head");
|
||||
Map<String, Object> result = new HashMap<>(16);
|
||||
result.put("head", head);
|
||||
if (CollectionUtil.isEmpty(dataList)) {
|
||||
return result;
|
||||
}
|
||||
calculationCondition(dataList, config);
|
||||
List<List<Map<String, Object>>> groupData = groupData(dataList, config);
|
||||
List<Map<String, Object>> groupDataList = new ArrayList<>();
|
||||
AtomicInteger n = new AtomicInteger(1);
|
||||
for (int i = 0; i < groupData.size(); i++) {
|
||||
List<Map<String, Object>> list = groupData.get(i);
|
||||
int finalI = i;
|
||||
list.forEach(item -> {
|
||||
item.put("groupId", finalI);
|
||||
item.put("no", n.getAndIncrement());
|
||||
});
|
||||
calculationOpenBill(list, config);
|
||||
groupDataList.addAll(list);
|
||||
}
|
||||
// for (List<Map<String, Object>> list : groupData) {
|
||||
// calculationOpenBill(list, config);
|
||||
//
|
||||
// groupDataList.addAll(list);
|
||||
// }
|
||||
result.put("body", groupDataList);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
private void calculationOpenBill(List<Map<String, Object>> list, Map<String, Object> config) {
|
||||
String orderTypeKey = Util.null2String(config.get("orderType"));
|
||||
String finalOrderType = "";
|
||||
for (Map<String, Object> item : list) {
|
||||
String orderType = Util.null2String(item.get(orderTypeKey));
|
||||
if (StrUtil.isNotBlank(finalOrderType)) {
|
||||
if (!finalOrderType.equals(orderType)) {
|
||||
throw new CustomerException("分组条件中存在不同流程数据,请检查分组逻辑以及分组条件组合逻辑是否保证分组正确!");
|
||||
}
|
||||
} else {
|
||||
finalOrderType = orderType;
|
||||
}
|
||||
}
|
||||
boolean flag = false;
|
||||
String finalBgm = "";
|
||||
for (Map<String, Object> item : list) {
|
||||
String bgm = Util.null2String(item.get(Util.null2String(config.get("bgmKey"))));
|
||||
if (StrUtil.isNotBlank(finalBgm)) {
|
||||
if (!finalBgm.equals(bgm)) {
|
||||
flag = true;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
finalBgm = bgm;
|
||||
}
|
||||
}
|
||||
if (!flag) {
|
||||
// 所有分组bgm都一样
|
||||
if (finalBgm.equals("Y")) {
|
||||
// 都过了bgm
|
||||
for (Map<String, Object> item : list) {
|
||||
item.put(Util.null2String(config.get("openBillKey")), "");
|
||||
}
|
||||
} else {
|
||||
// 都没过bgm,计算条件
|
||||
calculationOpenBillN(list, config);
|
||||
}
|
||||
} else {
|
||||
// 不全部都是y或者n
|
||||
for (Map<String, Object> item : list) {
|
||||
item.put(Util.null2String(config.get("openBillKey")), "全部订单总金额达到GM审批标准,拆分后无法到达GM审批节点。");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void calculationOpenBillN(List<Map<String, Object>> list, Map<String, Object> config) {
|
||||
Map<String, Object> fristMap = list.get(0);
|
||||
String orderType = Util.null2String(fristMap.get(Util.null2String(config.get("orderType"))));
|
||||
Map<String, Object> condition = (Map<String, Object>) config.get("condition");
|
||||
Map<String, Object> conditionInfo = (Map<String, Object>) condition.get(orderType);
|
||||
if (Objects.isNull(conditionInfo)) {
|
||||
return;
|
||||
}
|
||||
String id = Util.null2String(conditionInfo.get("id"));
|
||||
ConditionEntity conditionEntity = mapper.selectCondition(id);
|
||||
if (Objects.isNull(conditionEntity)) {
|
||||
return;
|
||||
}
|
||||
List<ConditionDetail> conditionDetailList = conditionEntity.getList();
|
||||
if (CollectionUtil.isEmpty(conditionDetailList)) {
|
||||
return;
|
||||
}
|
||||
List<Boolean> booleanList = new ArrayList<>();
|
||||
Map<String, Object> mapping = (Map<String, Object>) conditionInfo.get("mapping");
|
||||
Map<String, Object> totalMap = new HashMap<>();
|
||||
for (Map.Entry<String, Object> entry : mapping.entrySet()) {
|
||||
String key = entry.getKey();
|
||||
double totalValue = 0.0;
|
||||
for (Map<String, Object> item : list) {
|
||||
String value = Util.null2String(entry.getValue());
|
||||
Object o = "";
|
||||
if (StrUtil.isNotBlank(value)) {
|
||||
try {
|
||||
o = ScriptUtil.invokeScript(value, item);
|
||||
} catch (JexlException e) {
|
||||
log.error("执行脚本失败:=》" + e.getMessage());
|
||||
}
|
||||
}
|
||||
try {
|
||||
double v = Double.parseDouble(Util.null2String("".equals(Util.null2String(o)) ? null : Util.null2String(o), "0.0"));
|
||||
totalValue += v;
|
||||
} catch (Exception e) {
|
||||
log.error("求和转换失败:" + JSON.toJSONString(entry));
|
||||
log.error("item:" + JSON.toJSONString(item));
|
||||
throw new CustomerException("求和字段转化失败!", e);
|
||||
}
|
||||
}
|
||||
totalMap.put(key, totalValue);
|
||||
}
|
||||
for (Map.Entry<String, Object> entry : totalMap.entrySet()) {
|
||||
for (ConditionDetail conditionDetail : conditionDetailList) {
|
||||
Integer conditionType = conditionDetail.getConditionType();
|
||||
String value = Util.null2String(entry.getValue());
|
||||
String conditionValue = conditionDetail.getConditionValue();
|
||||
boolean b = this.compareCondition(conditionType, value, conditionValue);
|
||||
booleanList.add(b);
|
||||
}
|
||||
}
|
||||
boolean flag = false;
|
||||
String conditionType = conditionEntity.getCondition();
|
||||
if ("0".equals(conditionType)) {
|
||||
// 或者
|
||||
for (Boolean aBoolean : booleanList) {
|
||||
if (aBoolean) {
|
||||
flag = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if ("1".equals(conditionType)) {
|
||||
// 且
|
||||
for (Boolean aBoolean : booleanList) {
|
||||
if (!aBoolean) {
|
||||
flag = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// 拆单
|
||||
if (flag) {
|
||||
for (Map<String, Object> item : list) {
|
||||
item.put(Util.null2String(config.get("openBillKey")), "全部订单总金额达到GM审批标准,拆分后无法到达GM审批节点。");
|
||||
}
|
||||
} else {
|
||||
for (Map<String, Object> item : list) {
|
||||
item.put(Util.null2String(config.get("openBillKey")), "");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean compareCondition(Integer conditionType, String value, String conditionValue) {
|
||||
boolean res = false;
|
||||
double valueNum = 0.0;
|
||||
double conditionValueNum = 0.0;
|
||||
if (conditionType < 5) {
|
||||
valueNum = Double.parseDouble(value);
|
||||
conditionValueNum = Double.parseDouble(conditionValue);
|
||||
}
|
||||
|
||||
switch (conditionType) {
|
||||
case 0:
|
||||
if (conditionValueNum > valueNum) {
|
||||
res = true;
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
if (conditionValueNum >= valueNum) {
|
||||
res = true;
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
if (conditionValueNum < valueNum) {
|
||||
res = true;
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
if (conditionValueNum <= valueNum) {
|
||||
res = true;
|
||||
}
|
||||
break;
|
||||
case 4:
|
||||
if (conditionValueNum == valueNum) {
|
||||
res = true;
|
||||
}
|
||||
break;
|
||||
case 5:
|
||||
if (!Objects.equals(conditionValue, "") && conditionValue.contains(value)) {
|
||||
res = true;
|
||||
}
|
||||
break;
|
||||
case 6:
|
||||
if (Objects.equals(conditionValue, "")) {
|
||||
res = true;
|
||||
} else if (!conditionValue.contains(value)) {
|
||||
res = true;
|
||||
}
|
||||
break;
|
||||
case 7:
|
||||
if (!Objects.equals(conditionValue, "") && conditionValue.equals(value)) {
|
||||
res = true;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
private void calculationCondition(List<Map<String, Object>> dataList, Map<String, Object> config) {
|
||||
for (Map<String, Object> map : dataList) {
|
||||
// 订单分类
|
||||
String orderType = Util.null2String(map.get(Util.null2String(config.get("orderType"))));
|
||||
// 订单用途
|
||||
String orderUse = Util.null2String(map.get(Util.null2String(config.get("orderUse"))));
|
||||
// shipTo
|
||||
String shipTo = Util.null2String(map.get(Util.null2String(config.get("shipTo"))));
|
||||
// 成本中心
|
||||
String costCenter = Util.null2String(map.get(Util.null2String(config.get("costCenter"))));
|
||||
// sku
|
||||
String sku = Util.null2String(map.get(Util.null2String(config.get("sku"))));
|
||||
String condition = "";
|
||||
switch (orderType) {
|
||||
case "2": {
|
||||
// 内部领用
|
||||
// orderUse + " " + shipTo + " " + costCenter + " " + sku
|
||||
// condition = "订单用途:" + orderUse + " " + "ShipTo:" + shipTo + " CostCenter:" + costCenter + " sku:" + sku;
|
||||
condition = orderUse + " :" + shipTo + ":" + costCenter + ":" + sku;
|
||||
}
|
||||
break;
|
||||
case "1":
|
||||
case "3":
|
||||
case "4": {
|
||||
// 第三方 + 柜台订单
|
||||
// orderUse + ":" + shipTo + ":" + sku
|
||||
// condition = "订单用途:" + orderUse + " " + "ShipTo:" + shipTo + " sku:" + sku + " 订单分类:" + orderType;
|
||||
condition = orderUse + ":" + shipTo + ":" + sku + ":" + orderType;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
map.put(Util.null2String(config.get("conditionKey")), condition);
|
||||
}
|
||||
}
|
||||
|
||||
private List<List<Map<String, Object>>> groupData(List<Map<String, Object>> dataList, Map<String, Object> config) {
|
||||
// 根据condition进行分组
|
||||
Map<String, List<Map<String, Object>>> typeMap =
|
||||
dataList.stream()
|
||||
.collect(Collectors.groupingBy(
|
||||
item -> String.valueOf(
|
||||
item.get(
|
||||
Util.null2String(config.get("conditionKey"))
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
// 对每个type的数据进行日期分组
|
||||
List<List<Map<String, Object>>> result = new ArrayList<>();
|
||||
DateFormat dateFormat = new SimpleDateFormat(Util.null2String(config.get("createType")));
|
||||
for (String type : typeMap.keySet()) {
|
||||
List<Map<String, Object>> typeDataList = typeMap.get(type);
|
||||
typeDataList.sort((o1, o2) -> {
|
||||
try {
|
||||
Date date1 = dateFormat.parse(o1.get(Util.null2String(config.get("createDate"))).toString());
|
||||
Date date2 = dateFormat.parse(o2.get(Util.null2String(config.get("createDate"))).toString());
|
||||
return date1.compareTo(date2);
|
||||
} catch (ParseException e) {
|
||||
log.error("日期格式化出错,排序失败!");
|
||||
return 0;
|
||||
}
|
||||
});
|
||||
Map<String, Object> baseData = typeDataList.get(0);
|
||||
Date baseDate;
|
||||
try {
|
||||
baseDate = dateFormat.parse(baseData.get(Util.null2String(config.get("createDate"))).toString());
|
||||
} catch (ParseException e) {
|
||||
throw new CustomerException("日期转换异常!", e);
|
||||
}
|
||||
List<Map<String, Object>> groupList = new ArrayList<>();
|
||||
groupList.add(baseData);
|
||||
for (int i = 1; i < typeDataList.size(); i++) {
|
||||
Map<String, Object> data = typeDataList.get(i);
|
||||
Date date;
|
||||
try {
|
||||
date = dateFormat.parse(data.get(Util.null2String(config.get("createDate"))).toString());
|
||||
} catch (ParseException e) {
|
||||
throw new CustomerException("日期转换异常!", e);
|
||||
}
|
||||
|
||||
if (date == null || baseDate == null) {
|
||||
continue;
|
||||
}
|
||||
if (Math.abs(date.getTime() - baseDate.getTime()) <= 7 * 24 * 60 * 60 * 1000) {
|
||||
groupList.add(data);
|
||||
} else {
|
||||
baseDate = date;
|
||||
result.add(groupList);
|
||||
groupList = new ArrayList<>();
|
||||
groupList.add(data);
|
||||
}
|
||||
}
|
||||
if (groupList.size() > 0) {
|
||||
result.add(groupList);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
package com.api.youhong.ai.yashilandai.openbill.service;
|
||||
|
||||
import aiyh.utils.Util;
|
||||
import com.api.youhong.ai.yashilandai.openbill.mapper.OpenThenBillMapper;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* <h1></h1>
|
||||
*
|
||||
* <p>create: 2023/4/25 10:12</p>
|
||||
*
|
||||
* @author youHong.ai
|
||||
*/
|
||||
public class OpenThenBillService {
|
||||
|
||||
|
||||
private final OpenThenBillMapper mapper = Util.getMapper(OpenThenBillMapper.class);
|
||||
|
||||
public Object getOpenBillListData() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public List<Map<String, Object>> getData() {
|
||||
List<Map<String, Object>> dataList = mapper.selectList();
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
package com.api.youhong.ai.yashilandai.openbill.util;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
|
||||
/**
|
||||
* <h1>表内容</h1>
|
||||
*
|
||||
* <p>create: 2023/4/19 10:48</p>
|
||||
*
|
||||
* @author youHong.ai
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@ToString
|
||||
public class ExcelBody extends ExcelCell {
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
package com.api.youhong.ai.yashilandai.openbill.util;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
|
||||
/**
|
||||
* <h1>单元格内容</h1>
|
||||
*
|
||||
* <p>create: 2023/4/19 10:49</p>
|
||||
*
|
||||
* @author youHong.ai
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@ToString
|
||||
public class ExcelCell {
|
||||
/** 表头名称 */
|
||||
private Object value;
|
||||
|
||||
/** 单元格格式 */
|
||||
private IExcelCellStyleCreator cellStyle;
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
package com.api.youhong.ai.yashilandai.openbill.util;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
|
||||
/**
|
||||
* <h1>excel表头</h1>
|
||||
*
|
||||
* <p>create: 2023/4/19 10:46</p>
|
||||
*
|
||||
* @author youHong.ai
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@ToString
|
||||
public class ExcelHead extends ExcelCell {
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,135 @@
|
|||
package com.api.youhong.ai.yashilandai.openbill.util;
|
||||
|
||||
import aiyh.utils.Util;
|
||||
import aiyh.utils.tool.cn.hutool.core.collection.CollectionUtil;
|
||||
import org.apache.poi.ss.usermodel.CellStyle;
|
||||
import org.apache.poi.xssf.streaming.SXSSFCell;
|
||||
import org.apache.poi.xssf.streaming.SXSSFRow;
|
||||
import org.apache.poi.xssf.streaming.SXSSFSheet;
|
||||
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
|
||||
import org.apache.poi.xssf.usermodel.XSSFRichTextString;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* <h1>excel导出</h1>
|
||||
*
|
||||
* <p>create: 2023/4/19 10:39</p>
|
||||
*
|
||||
* @author youHong.ai
|
||||
*/
|
||||
public class ExcelPort {
|
||||
|
||||
public static void exportFile(String sheetName, List<ExcelRow> dataList,
|
||||
OutputStream outputStream,
|
||||
IExcelCellStyleCreator headStyle,
|
||||
IExcelCellStyleCreator bodyStyle,
|
||||
Map<Integer, CellStyle> headStyleMap,
|
||||
Map<Integer, CellStyle> singularLine,
|
||||
Map<Integer, CellStyle> evenNumberLine) {
|
||||
SXSSFWorkbook workbook = new SXSSFWorkbook();
|
||||
createSheet(sheetName, workbook, dataList, headStyle, bodyStyle, headStyleMap, singularLine, evenNumberLine);
|
||||
// String excel = Util.getTempFilePath("excel", ".xlsx");
|
||||
try {
|
||||
workbook.write(outputStream);
|
||||
// workbook.write(Files.newOutputStream(Paths.get(excel)));
|
||||
// return new File(excel);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static String export(String sheetName, List<ExcelRow> dataList,
|
||||
IExcelCellStyleCreator headStyle,
|
||||
IExcelCellStyleCreator bodyStyle, Map<Integer, CellStyle> headStyleMap,
|
||||
Map<Integer, CellStyle> singularLine,
|
||||
Map<Integer, CellStyle> evenNumberLine) {
|
||||
SXSSFWorkbook workbook = new SXSSFWorkbook();
|
||||
createSheet(sheetName, workbook, dataList, headStyle, bodyStyle, headStyleMap, singularLine, evenNumberLine);
|
||||
String excel = Util.getTempFilePath("excel", ".xlsx");
|
||||
try {
|
||||
workbook.write(Files.newOutputStream(Paths.get(excel)));
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return excel;
|
||||
}
|
||||
|
||||
|
||||
public static String export(List<ExcelSheet> sheetList,
|
||||
IExcelCellStyleCreator headStyle,
|
||||
IExcelCellStyleCreator bodyStyle, Map<Integer, CellStyle> headStyleMap,
|
||||
Map<Integer, CellStyle> singularLine,
|
||||
Map<Integer, CellStyle> evenNumberLine) {
|
||||
SXSSFWorkbook workbook = new SXSSFWorkbook();
|
||||
for (ExcelSheet excelSheet : sheetList) {
|
||||
createSheet(excelSheet.getSheetName(), workbook, excelSheet.getDataList(), headStyle, bodyStyle, headStyleMap, singularLine, evenNumberLine);
|
||||
}
|
||||
String excel = Util.getTempFilePath("excel", ".xlsx");
|
||||
try {
|
||||
workbook.write(Files.newOutputStream(Paths.get(excel)));
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return excel;
|
||||
}
|
||||
|
||||
|
||||
private static void createSheet(String sheetName, SXSSFWorkbook wb, List<ExcelRow> dataList,
|
||||
IExcelCellStyleCreator headStyle,
|
||||
IExcelCellStyleCreator bodyStyle,
|
||||
Map<Integer, CellStyle> headStyleMap,
|
||||
Map<Integer, CellStyle> singularLine,
|
||||
Map<Integer, CellStyle> evenNumberLine) {
|
||||
SXSSFSheet sheet = wb.createSheet(sheetName);
|
||||
createDate(sheet, wb, dataList, headStyle, bodyStyle, headStyleMap, singularLine, evenNumberLine);
|
||||
}
|
||||
|
||||
private static void createDate(SXSSFSheet sheet, SXSSFWorkbook wb, List<ExcelRow> dataList,
|
||||
IExcelCellStyleCreator headStyle,
|
||||
IExcelCellStyleCreator bodyStyle,
|
||||
Map<Integer, CellStyle> headStyleMap,
|
||||
Map<Integer, CellStyle> singularLine,
|
||||
Map<Integer, CellStyle> evenNumberLine) {
|
||||
if (CollectionUtil.isEmpty(dataList) || dataList.size() < 1) {
|
||||
return;
|
||||
}
|
||||
for (int i = 0; i < dataList.size(); i++) {
|
||||
ExcelRow excelRow = dataList.get(i);
|
||||
SXSSFRow row = sheet.createRow(i);
|
||||
if (!Objects.isNull(excelRow.getRowHeight()) && excelRow.getRowHeight() > 0) {
|
||||
row.setHeightInPoints(excelRow.getRowHeight());
|
||||
}
|
||||
List<ExcelCell> rowData = excelRow.getDataList();
|
||||
for (int j = 0; j < rowData.size(); j++) {
|
||||
ExcelCell cellValue = rowData.get(j);
|
||||
SXSSFCell cell = row.createCell(j);
|
||||
CellStyle style = null;
|
||||
XSSFRichTextString text = new XSSFRichTextString(Util.null2String(cellValue.getValue()));
|
||||
cell.setCellValue(text);
|
||||
// sheet.trackAllColumnsForAutoSizing();
|
||||
// sheet.autoSizeColumn(i);
|
||||
if (i == 0) {
|
||||
if (Objects.nonNull(headStyle)) {
|
||||
style = headStyle.createStyle(wb, i, j, row, cell, sheet, headStyleMap, null);
|
||||
}
|
||||
} else {
|
||||
if (Objects.nonNull(bodyStyle)) {
|
||||
style = bodyStyle.createStyle(wb, i, j, row, cell, sheet, singularLine, evenNumberLine);
|
||||
}
|
||||
}
|
||||
if (Objects.nonNull(style)) {
|
||||
cell.setCellStyle(style);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
package com.api.youhong.ai.yashilandai.openbill.util;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* <h1>表行</h1>
|
||||
*
|
||||
* <p>create: 2023/4/19 10:56</p>
|
||||
*
|
||||
* @author youHong.ai
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@ToString
|
||||
public class ExcelRow {
|
||||
|
||||
private Float rowHeight;
|
||||
private List<ExcelCell> dataList;
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
package com.api.youhong.ai.yashilandai.openbill.util;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* <h1>excel表</h1>
|
||||
*
|
||||
* <p>create: 2023/4/19 11:14</p>
|
||||
*
|
||||
* @author youHong.ai
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@ToString
|
||||
public class ExcelSheet {
|
||||
private String sheetName;
|
||||
private List<ExcelRow> dataList;
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
package com.api.youhong.ai.yashilandai.openbill.util;
|
||||
|
||||
import org.apache.poi.ss.usermodel.CellStyle;
|
||||
import org.apache.poi.xssf.streaming.SXSSFCell;
|
||||
import org.apache.poi.xssf.streaming.SXSSFRow;
|
||||
import org.apache.poi.xssf.streaming.SXSSFSheet;
|
||||
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* <h1>创建样式</h1>
|
||||
*
|
||||
* <p>create: 2023/4/19 13:53</p>
|
||||
*
|
||||
* @author youHong.ai
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface IExcelCellStyleCreator {
|
||||
|
||||
CellStyle createStyle(SXSSFWorkbook workbook,
|
||||
Integer rowIndex,
|
||||
Integer colIndex,
|
||||
SXSSFRow row,
|
||||
SXSSFCell cell,
|
||||
SXSSFSheet sheet, Map<Integer, CellStyle> style2,
|
||||
Map<Integer, CellStyle> style1);
|
||||
}
|
|
@ -756,7 +756,7 @@ public class DealWithMapping extends ToolUtil {
|
|||
} else {
|
||||
value = Util.null2String(mainMap.get(fieldName));
|
||||
if ("".equals(value)) {
|
||||
value = Util.null2String(detailMap.get(fieldNameLower));
|
||||
value = Util.null2String(mainMap.get(fieldNameLower));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -172,22 +172,19 @@ public class VoucherPayableNewAction extends SafeCusBaseAction {
|
|||
}
|
||||
headKeys = sortKey(headKeys);
|
||||
for (String headKey : headKeys) {
|
||||
sb.append(heads.get(headKey)).append("\t");
|
||||
sb.append(Util.null2String(heads.get(headKey)).replace(" ", " ")).append("\t");
|
||||
}
|
||||
sb.deleteCharAt(sb.lastIndexOf("\t"));
|
||||
sb.append("\r\n");
|
||||
log.info("头写入的数据:" + sb);
|
||||
// 写入借方信息
|
||||
writeList(debit, sb);
|
||||
log.info("debit写入的数据:" + sb);
|
||||
// 写入贷方信息
|
||||
writeList(creditSide, sb);
|
||||
log.info("creditSide写入的数据:" + sb);
|
||||
String filePath = getFilePath();
|
||||
try {
|
||||
OutputStreamWriter out = new OutputStreamWriter(
|
||||
Files.newOutputStream(Paths.get(filePath)), StandardCharsets.UTF_8);
|
||||
out.write(sb.toString());
|
||||
out.write(sb.toString().replace(" ", " "));
|
||||
out.close();
|
||||
|
||||
} catch (IOException e) {
|
||||
|
|
|
@ -84,7 +84,7 @@ public class VoucherPayableService {
|
|||
StringBuilder voucherDetailBuilder = new StringBuilder();
|
||||
List<List<VoucherItem>> voucherDetail = voucherData.getVoucherDetail();
|
||||
for (List<VoucherItem> voucherItems : voucherDetail) {
|
||||
voucherDetailBuilder.append(appendVoucherItems(voucherItems))
|
||||
voucherDetailBuilder.append(appendVoucherItems(voucherItems).replace(" ", " "))
|
||||
.append("\r\n");
|
||||
}
|
||||
String voucherDetailStr = voucherDetailBuilder.substring(0, voucherDetailBuilder.lastIndexOf("\r\n"));
|
||||
|
@ -95,8 +95,8 @@ public class VoucherPayableService {
|
|||
// ));
|
||||
OutputStreamWriter out = new OutputStreamWriter(
|
||||
Files.newOutputStream(Paths.get(filePath)), StandardCharsets.UTF_8);
|
||||
out.write(voucherHeadStr);
|
||||
out.write(voucherDetailStr);
|
||||
out.write(voucherHeadStr.replace(" ", " "));
|
||||
out.write(voucherDetailStr.replace(" ", " "));
|
||||
out.close();
|
||||
// writer.write(voucherHeadStr);
|
||||
// writer.write(voucherDetailStr);
|
||||
|
@ -118,7 +118,7 @@ public class VoucherPayableService {
|
|||
List<VoucherItem> voucherItems = voucherDetail.get(i);
|
||||
if (i == 0) {
|
||||
for (VoucherItem voucherItem : voucherItems) {
|
||||
voucherHeadBuilder.append(voucherItem.getName())
|
||||
voucherHeadBuilder.append(voucherItem.getName().replace(" ", " "))
|
||||
.append("\t");
|
||||
}
|
||||
voucherHeadBuilder = new StringBuilder(voucherHeadBuilder
|
||||
|
@ -126,7 +126,7 @@ public class VoucherPayableService {
|
|||
|
||||
}
|
||||
for (VoucherItem voucherItem : voucherItems) {
|
||||
voucherHeadBuilder.append(voucherItem.getValue())
|
||||
voucherHeadBuilder.append(Util.null2String(voucherItem.getValue()).replace(" ", " "))
|
||||
.append("\t");
|
||||
}
|
||||
if (i < voucherDetail.size() - 1) {
|
||||
|
@ -142,7 +142,7 @@ public class VoucherPayableService {
|
|||
try {
|
||||
OutputStreamWriter out = new OutputStreamWriter(
|
||||
Files.newOutputStream(Paths.get(filePath)), StandardCharsets.UTF_8);
|
||||
out.write(voucherHaredStr);
|
||||
out.write(voucherHaredStr.replace(" ", " "));
|
||||
out.close();
|
||||
// BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(
|
||||
// Files.newOutputStream(Paths.get(filePath))
|
||||
|
@ -186,7 +186,7 @@ public class VoucherPayableService {
|
|||
// ));
|
||||
OutputStreamWriter out = new OutputStreamWriter(
|
||||
Files.newOutputStream(Paths.get(filePath)), StandardCharsets.UTF_8);
|
||||
out.write(voucherHeadStr);
|
||||
out.write(voucherHeadStr.replace(" ", " "));
|
||||
// writer.write(voucherHeadStr);
|
||||
// writer.flush();
|
||||
// writer.close();
|
||||
|
|
|
@ -88,12 +88,12 @@ public class CaElectronicSignatureAction extends SafeCusBaseAction {
|
|||
}
|
||||
Map<String, Object> responseMap = responeVo.getResponseMap();
|
||||
String documentNo = Util.null2String(responseMap.get("document_no"));
|
||||
String pdf = Util.null2String(responseMap.get("pdf"));
|
||||
String pdf = Util.null2String(responseMap.get("ofd"));
|
||||
InputStream inputStream = base64ContentToFile(pdf);
|
||||
String docCategorys = Util.getDocCategorysByTable(String.valueOf(workflowId), signFileField, billTable);
|
||||
String[] docCategoryArr = docCategorys.split(",");
|
||||
int docCategory = Integer.parseInt(docCategoryArr[docCategoryArr.length - 1]);
|
||||
int docId = Util.createDoc(Strings.isNullOrEmpty(docName.get()) ? "sign.pdf" : docName.get(), docCategory, inputStream, 1);
|
||||
int docId = Util.createDoc(Strings.isNullOrEmpty(docName.get()) ? "sign.ofd" : docName.get(), docCategory, inputStream, 1);
|
||||
docName.remove();
|
||||
writeBack(documentNo, billTable, requestId, docId);
|
||||
} catch (Exception e) {
|
||||
|
|
|
@ -9,6 +9,7 @@ import weaver.file.ImageFileManager;
|
|||
import weaver.xiao.commons.config.interfacies.CusInterfaceGetValue;
|
||||
import weaver.youhong.ai.intellectualproperty.action.CaElectronicSignatureAction;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.util.Base64;
|
||||
import java.util.Map;
|
||||
|
@ -35,9 +36,14 @@ public class FileToBase64CusGetValue implements CusInterfaceGetValue {
|
|||
}
|
||||
DocImageInfo docImageInfo = Util.selectImageInfoByDocId(currentValue);
|
||||
InputStream inputStream = ImageFileManager.getInputStreamById(docImageInfo.getImageFileId());
|
||||
byte[] src = new byte[inputStream.available()];
|
||||
inputStream.read(src);
|
||||
String fileBase64 = Base64.getEncoder().encodeToString(src);
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
byte[] buffer = new byte[4096];
|
||||
int bytesRead;
|
||||
while ((bytesRead = inputStream.read(buffer)) != -1) {
|
||||
outputStream.write(buffer, 0, bytesRead);
|
||||
}
|
||||
byte[] data = outputStream.toByteArray();
|
||||
String fileBase64 = Base64.getEncoder().encodeToString(data);
|
||||
CaElectronicSignatureAction.docName.set(docImageInfo.getImageFileName());
|
||||
return fileBase64;
|
||||
} catch (Exception e) {
|
||||
|
|
|
@ -0,0 +1,112 @@
|
|||
#导出设置
|
||||
export.tableName=v_yscd
|
||||
#订单分类
|
||||
export.orderType=ddlx
|
||||
#订单用途
|
||||
export.orderUse=ddyt
|
||||
# 点单编号
|
||||
export.orderNo=ddbh
|
||||
#shipto
|
||||
export.shipTo=shipto
|
||||
# 疑似拆单
|
||||
export.openBillKey=openBill
|
||||
# 日期时间字段
|
||||
export.createDate=createdate
|
||||
# 日期格式化类型
|
||||
export.createType=yyyy-MM-dd
|
||||
#成本中心
|
||||
export.costCenter=cbzxyh
|
||||
# 条件key
|
||||
export.conditionKey=condition
|
||||
#流水号-requestId
|
||||
export.requestId=lcid
|
||||
# bgm字段
|
||||
export.bgmKey=bgm
|
||||
# 条件参数id配置
|
||||
export.condition.1.id=42
|
||||
export.condition.2.id=42
|
||||
export.condition.3.id=42
|
||||
export.condition.4.id=42
|
||||
#条件参数字段映射关系
|
||||
export.condition.1.mapping.giftsQty=sl
|
||||
export.condition.1.mapping.giftsTotalCost=kxpmxzlsj
|
||||
export.condition.2.mapping.giftsQty=sl
|
||||
export.condition.2.mapping.giftsTotalCost=kxpmxzlsj
|
||||
export.condition.3.mapping.giftsQty=sl
|
||||
export.condition.3.mapping.giftsTotalCost=kxpmxzlsj
|
||||
export.condition.4.mapping.giftsQty=sl
|
||||
export.condition.4.mapping.giftsTotalCost=kxpmxzlsj
|
||||
#sku
|
||||
export.sku=sku
|
||||
#导出表头设置
|
||||
export.head.title[0]=序号
|
||||
export.head.field[0]=no
|
||||
export.head.title[1]=分组编号
|
||||
export.head.field[1]=groupId
|
||||
export.head.title[2]=ID
|
||||
export.head.field[2]=id
|
||||
export.head.title[3]=条件
|
||||
export.head.field[3]=condition
|
||||
export.head.title[4]=疑似拆单
|
||||
export.head.field[4]=openBill
|
||||
export.head.title[5]=流水号
|
||||
export.head.field[5]=lcid
|
||||
export.head.title[6]=shipTo
|
||||
export.head.field[6]=shipto
|
||||
export.head.title[7]=推送时间
|
||||
export.head.field[7]=insertTime
|
||||
export.head.title[8]=审批开始时间
|
||||
export.head.field[8]=createdate
|
||||
export.head.title[9]=申请提交时间
|
||||
export.head.field[9]=sqtjrq
|
||||
export.head.title[10]=审批开始时间
|
||||
export.head.field[10]=LASTOPERATEDATE
|
||||
export.head.title[11]=收方限制
|
||||
export.head.field[11]=ddlxms
|
||||
export.head.title[12]=部门团队
|
||||
export.head.field[12]=SPART
|
||||
export.head.title[13]=品牌部门
|
||||
export.head.field[13]=yjpp
|
||||
export.head.title[14]=订单编号
|
||||
export.head.field[14]=ddbh
|
||||
export.head.title[15]=订单说明
|
||||
export.head.field[15]=ddsm
|
||||
export.head.title[16]=备注
|
||||
export.head.field[16]=bz
|
||||
export.head.title[17]=成本中心-sap
|
||||
export.head.field[17]=KOSTL
|
||||
export.head.title[18]=成本中心-用户
|
||||
export.head.field[18]=cbzxyh
|
||||
export.head.title[19]=财务科目
|
||||
export.head.field[19]=cwkm
|
||||
export.head.title[20]=审批开始时间
|
||||
export.head.field[20]=LASTOPERATEDATE
|
||||
export.head.title[21]=活动编号
|
||||
export.head.field[21]=hdbh
|
||||
export.head.title[22]=businessKey
|
||||
export.head.field[22]=businesskey
|
||||
export.head.title[23]=申请人
|
||||
export.head.field[23]=SQRRLZY
|
||||
export.head.title[24]=订单类型
|
||||
export.head.field[24]=AUART
|
||||
export.head.title[25]=订单用途
|
||||
export.head.field[25]=VKAUS
|
||||
export.head.title[26]=收货人
|
||||
export.head.field[26]=KUNNRSHIPTO
|
||||
export.head.title[27]=收货人地址
|
||||
export.head.field[27]=shdz
|
||||
export.head.title[28]=收货编号
|
||||
export.head.field[28]=hwbh
|
||||
export.head.title[29]=收货类型
|
||||
export.head.field[29]=fl
|
||||
export.head.title[30]=收货中文名
|
||||
export.head.field[30]=ZWMS
|
||||
export.head.title[31]=数量
|
||||
export.head.field[31]=SL
|
||||
export.head.title[32]=零售总额
|
||||
export.head.field[32]=kxpmxzlsj
|
||||
export.head.title[33]=订单分类
|
||||
export.head.field[33]=ddlx
|
||||
export.head.title[34]=gbm
|
||||
export.head.field[34]=bgm
|
||||
|
|
@ -1,7 +1,10 @@
|
|||
package com.api.aiyh_pcn.common_fadada.contraller;
|
||||
|
||||
import aiyh.utils.Util;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.api.aiyh_pcn.common_fadada.service.CommonFaService;
|
||||
import io.swagger.v3.oas.annotations.parameters.RequestBody;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import javax.ws.rs.Consumes;
|
||||
import javax.ws.rs.POST;
|
||||
|
@ -21,6 +24,9 @@ import java.util.Map;
|
|||
@Path("/common_fadada/callback")
|
||||
public class CommonFaCallbackController {
|
||||
|
||||
private final CommonFaService service = new CommonFaService();
|
||||
private final Logger log = Util.getLogger();
|
||||
|
||||
@Path("/dealers/callback")
|
||||
@POST
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
|
@ -31,4 +37,21 @@ public class CommonFaCallbackController {
|
|||
result.put("msg", "操作成功!");
|
||||
return JSON.toJSONString(result);
|
||||
}
|
||||
|
||||
|
||||
@Path("/submit/callback")
|
||||
@POST
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
public String submitCallback(@RequestBody Map<String, Object> params) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("code", 200);
|
||||
result.put("msg", "操作成功!");
|
||||
try {
|
||||
service.submitCallback(params);
|
||||
} catch (Exception e) {
|
||||
log.error("流程回调处理失败!" + Util.getErrString(e));
|
||||
}
|
||||
return JSON.toJSONString(result);
|
||||
}
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue