diff --git a/javascript/common/Utils.js b/javascript/common/Utils.js index 6c960d1..5b7d700 100644 --- a/javascript/common/Utils.js +++ b/javascript/common/Utils.js @@ -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() diff --git a/javascript/common/dev.js b/javascript/common/dev.js index cb46f64..331fa30 100644 --- a/javascript/common/dev.js +++ b/javascript/common/dev.js @@ -1,3 +1,10 @@ +const ecodeSDK = {} +ecodeSDK.setCom = (id, name, Com) => { +} +ecodeSDK.imp = (obj) => { +} +ecodeSDK.exp = (obj) => { +} const WfForm = { isMobile: () => { // true表示是eMobile、微信、钉钉等移动终端,false代表PC端 diff --git a/javascript/youhong.ai/pcn/workflow_code_block.js b/javascript/youhong.ai/pcn/workflow_code_block.js index ff52a8a..f99dbcc 100644 --- a/javascript/youhong.ai/pcn/workflow_code_block.js +++ b/javascript/youhong.ai/pcn/workflow_code_block.js @@ -680,4 +680,62 @@ $(() => { }) -/* ******************* 明细数据数量统计添加 end ******************* */ \ No newline at end of file +/* ******************* 明细数据数量统计添加 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 ******************* */ \ No newline at end of file diff --git a/src/main/java/aiyh/utils/ScriptUtil.java b/src/main/java/aiyh/utils/ScriptUtil.java new file mode 100644 index 0000000..7f6cbd1 --- /dev/null +++ b/src/main/java/aiyh/utils/ScriptUtil.java @@ -0,0 +1,25 @@ +package aiyh.utils; + +import aiyh.utils.tool.org.apache.commons.jexl3.*; + +import java.util.Map; + +/** + *

脚本工具

+ * + *

create: 2023/3/3 23:03

+ * + * @author youHong.ai + */ +public class ScriptUtil { + private static final JexlEngine jexl = new JexlBuilder().create(); + + public static Object invokeScript(String script, Map params) { + JexlContext jc = new MapContext(); + for (Map.Entry entry : params.entrySet()) { + jc.set(entry.getKey(), entry.getValue()); + } + JexlExpression expression = jexl.createExpression(script); + return expression.evaluate(jc); + } +} diff --git a/src/main/java/aiyh/utils/Util.java b/src/main/java/aiyh/utils/Util.java index 61f38fc..fd7046e 100644 --- a/src/main/java/aiyh/utils/Util.java +++ b/src/main/java/aiyh/utils/Util.java @@ -3751,6 +3751,32 @@ public class Util extends weaver.general.Util { return pathParamMap; } + public static T getClassInstance(String classPath, Class 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; diff --git a/src/main/java/aiyh/utils/recordset/ResultMapper.java b/src/main/java/aiyh/utils/recordset/ResultMapper.java index 0e3b2f3..cd3a2d7 100644 --- a/src/main/java/aiyh/utils/recordset/ResultMapper.java +++ b/src/main/java/aiyh/utils/recordset/ResultMapper.java @@ -569,7 +569,9 @@ public class ResultMapper { cassociationValue = paramType.get(declaredField.getType()).apply(String.valueOf(cassociationValue)); } try { - propertyDescriptor.getWriteMethod().invoke(o, cassociationValue); + 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 { - propertyDescriptor.getWriteMethod().invoke(o, collection); + if (Objects.nonNull(value)) { + propertyDescriptor.getWriteMethod().invoke(o, collection); + } } catch (Exception e) { Util.getLogger().error("实体数据写入报错:" + fieldName + " => " + value); if (value != null) { diff --git a/src/main/java/aiyh/utils/tool/cn/hutool/http/ContentType.java b/src/main/java/aiyh/utils/tool/cn/hutool/http/ContentType.java new file mode 100644 index 0000000..c4b4665 --- /dev/null +++ b/src/main/java/aiyh/utils/tool/cn/hutool/http/ContentType.java @@ -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类型,支持的类型有: + * + *
+	 * 1. application/json
+	 * 1. application/xml
+	 * 
+ * + * @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); + } +} diff --git a/src/main/java/aiyh/utils/tool/cn/hutool/http/GlobalHeaders.java b/src/main/java/aiyh/utils/tool/cn/hutool/http/GlobalHeaders.java new file mode 100644 index 0000000..49de06c --- /dev/null +++ b/src/main/java/aiyh/utils/tool/cn/hutool/http/GlobalHeaders.java @@ -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; + +/** + * 全局头部信息
+ * 所有Http请求将共用此全局头部信息,除非在{@link HttpRequest}中自定义头部信息覆盖之 + * + * @author looly + */ +public enum GlobalHeaders { + INSTANCE; + + /** + * 存储头信息 + */ + final Map> 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 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 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
+ * 如果覆盖模式,则替换之前的值,否则加入到值列表中 + * + * @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 values = headers.get(name.trim()); + if (isOverride || CollectionUtil.isEmpty(values)) { + final ArrayList valueList = new ArrayList<>(); + valueList.add(value); + headers.put(name.trim(), valueList); + } else { + values.add(value.trim()); + } + } + return this; + } + + /** + * 设置一个header
+ * 如果覆盖模式,则替换之前的值,否则加入到值列表中 + * + * @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
+ * 覆盖模式,则替换之前的值 + * + * @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
+ * 覆盖模式,则替换之前的值 + * + * @param name Header名 + * @param value Header值 + * @return this + */ + public GlobalHeaders header(String name, String value) { + return header(name, value, true); + } + + /** + * 设置请求头
+ * 不覆盖原有请求头 + * + * @param headers 请求头 + * @return this + */ + public GlobalHeaders header(Map> headers) { + if (MapUtil.isEmpty(headers)) { + return this; + } + + String name; + for (Entry> 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> headers() { + return Collections.unmodifiableMap(headers); + } + + /** + * 清除所有头信息,包括全局头信息 + * + * @return this + * @since 5.7.13 + */ + synchronized public GlobalHeaders clearHeaders() { + this.headers.clear(); + return this; + } + // ---------------------------------------------------------------- Headers end + +} diff --git a/src/main/java/aiyh/utils/tool/cn/hutool/http/GlobalInterceptor.java b/src/main/java/aiyh/utils/tool/cn/hutool/http/GlobalInterceptor.java new file mode 100755 index 0000000..8c740fb --- /dev/null +++ b/src/main/java/aiyh/utils/tool/cn/hutool/http/GlobalInterceptor.java @@ -0,0 +1,94 @@ +package aiyh.utils.tool.cn.hutool.http; + +/** + * 全局的拦截器
+ * 包括请求拦截器和响应拦截器 + * + * @author looly + * @since 5.8.0 + */ +public enum GlobalInterceptor { + INSTANCE; + + private final HttpInterceptor.Chain requestInterceptors = new HttpInterceptor.Chain<>(); + private final HttpInterceptor.Chain responseInterceptors = new HttpInterceptor.Chain<>(); + + /** + * 设置拦截器,用于在请求前重新编辑请求 + * + * @param interceptor 拦截器实现 + * @return this + */ + synchronized public GlobalInterceptor addRequestInterceptor(HttpInterceptor interceptor) { + this.requestInterceptors.addChain(interceptor); + return this; + } + + /** + * 设置拦截器,用于在响应读取后完成编辑或读取 + * + * @param interceptor 拦截器实现 + * @return this + */ + synchronized public GlobalInterceptor addResponseInterceptor(HttpInterceptor 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 getCopiedRequestInterceptor() { + final HttpInterceptor.Chain copied = new HttpInterceptor.Chain<>(); + for (HttpInterceptor interceptor : this.requestInterceptors) { + copied.addChain(interceptor); + } + return copied; + } + + /** + * 复制响应过滤器列表 + * + * @return {@link HttpInterceptor.Chain} + */ + HttpInterceptor.Chain getCopiedResponseInterceptor() { + final HttpInterceptor.Chain copied = new HttpInterceptor.Chain<>(); + for (HttpInterceptor interceptor : this.responseInterceptors) { + copied.addChain(interceptor); + } + return copied; + } +} diff --git a/src/main/java/aiyh/utils/tool/cn/hutool/http/HTMLFilter.java b/src/main/java/aiyh/utils/tool/cn/hutool/http/HTMLFilter.java new file mode 100644 index 0000000..fcea8de --- /dev/null +++ b/src/main/java/aiyh/utils/tool/cn/hutool/http/HTMLFilter.java @@ -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) 漏洞隐患。 + * + *

+ * 此类中的方法非线程安全 + *

+ * + *
+ *     String clean = new HTMLFilter().filter(input);
+ * 
+ *

+ * 此类来自: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 P_REMOVE_PAIR_BLANKS = new SafeConcurrentHashMap<>(); + private static final ConcurrentMap P_REMOVE_SELF_BLANKS = new SafeConcurrentHashMap<>(); + + /** + * set of allowed html elements, along with allowed attributes for each element + **/ + private final Map> vAllowed; + /** + * counts of open tags for each (allowable) html element + **/ + private final Map 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 a_atts = new ArrayList<>(); + a_atts.add("href"); + a_atts.add("target"); + vAllowed.put("a", a_atts); + + final ArrayList 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 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 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>) 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("")); + } + 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(""); + } + } + 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[^>]*)?>")); + } + 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 ""; + } + } + } + } + + // 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 paramNames = new ArrayList<>(); + final List 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)); + } +} diff --git a/src/main/java/aiyh/utils/tool/cn/hutool/http/Header.java b/src/main/java/aiyh/utils/tool/cn/hutool/http/Header.java new file mode 100644 index 0000000..3723c1f --- /dev/null +++ b/src/main/java/aiyh/utils/tool/cn/hutool/http/Header.java @@ -0,0 +1,153 @@ +package aiyh.utils.tool.cn.hutool.http; + +/** + * Http 头域 + * + * @author Looly + */ +public enum Header { + + //------------------------------------------------------------- 通用头域 + /** + * 提供验证头,例如: + *

+	 * Authorization: Basic YWxhZGRpbjpvcGVuc2VzYW1l
+	 * 
+ */ + AUTHORIZATION("Authorization"), + /** + * 提供给代理服务器的用于身份验证的凭证,例如: + *
+	 * Proxy-Authorization: Basic YWxhZGRpbjpvcGVuc2VzYW1l
+	 * 
+ */ + 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(); + } +} diff --git a/src/main/java/aiyh/utils/tool/cn/hutool/http/HtmlUtil.java b/src/main/java/aiyh/utils/tool/cn/hutool/http/HtmlUtil.java new file mode 100755 index 0000000..7b9f040 --- /dev/null +++ b/src/main/java/aiyh/utils/tool/cn/hutool/http/HtmlUtil.java @@ -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工具类 + * + *

+ * 比如我们在使用爬虫爬取HTML页面后,需要对返回页面的HTML内容做一定处理,
+ * 比如去掉指定标签(例如广告栏等)、去除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字符为安全的字符,以下字符被转义: + *

    + *
  • ' 替换为 &#039; (&apos; doesn't work in HTML4)
  • + *
  • " 替换为 &quot;
  • + *
  • & 替换为 &amp;
  • + *
  • < 替换为 &lt;
  • + *
  • > 替换为 &gt;
  • + *
+ * + * @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标签和被标签包围的内容
+ * 不区分大小写 + * + * @param content 文本 + * @param tagNames 要清除的标签 + * @return 去除标签后的文本 + */ + public static String removeHtmlTag(String content, String... tagNames) { + return removeHtmlTag(content, true, tagNames); + } + + /** + * 清除指定HTML标签,不包括内容
+ * 不区分大小写 + * + * @param content 文本 + * @param tagNames 要清除的标签 + * @return 去除标签后的文本 + */ + public static String unwrapHtmlTag(String content, String... tagNames) { + return removeHtmlTag(content, false, tagNames); + } + + /** + * 清除指定HTML标签
+ * 不区分大小写 + * + * @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); + } +} diff --git a/src/main/java/aiyh/utils/tool/cn/hutool/http/HttpBase.java b/src/main/java/aiyh/utils/tool/cn/hutool/http/HttpBase.java new file mode 100644 index 0000000..38042ca --- /dev/null +++ b/src/main/java/aiyh/utils/tool/cn/hutool/http/HttpBase.java @@ -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 子类类型,方便链式编程 + * @author Looly + */ +@SuppressWarnings("unchecked") +public abstract class HttpBase { + + /** + * 默认的请求编码、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> headers = new HashMap<>(); + /** + * 编码 + */ + protected Charset charset = DEFAULT_CHARSET; + /** + * http版本 + */ + protected String httpVersion = HTTP_1_1; + /** + * 存储主体 + */ + protected byte[] bodyBytes; + + // ---------------------------------------------------------------- Headers start + + /** + * 根据name获取头信息
+ * 根据RFC2616规范,header的name不区分大小写 + * + * @param name Header名 + * @return Header值 + */ + public String header(String name) { + final List 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 headerList(String name) { + if (StrUtil.isBlank(name)) { + return null; + } + + final CaseInsensitiveMap> 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
+ * 如果覆盖模式,则替换之前的值,否则加入到值列表中 + * + * @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 values = headers.get(name.trim()); + if (isOverride || CollectionUtil.isEmpty(values)) { + final ArrayList valueList = new ArrayList<>(); + valueList.add(value); + headers.put(name.trim(), valueList); + } else { + values.add(value.trim()); + } + } + return (T) this; + } + + /** + * 设置一个header
+ * 如果覆盖模式,则替换之前的值,否则加入到值列表中 + * + * @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
+ * 覆盖模式,则替换之前的值 + * + * @param name Header名 + * @param value Header值 + * @return T 本身 + */ + public T header(Header name, String value) { + return header(name.toString(), value, true); + } + + /** + * 设置一个header
+ * 覆盖模式,则替换之前的值 + * + * @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 headers, boolean isOverride) { + if (MapUtil.isEmpty(headers)) { + return (T) this; + } + + for (Entry entry : headers.entrySet()) { + this.header(entry.getKey(), StrUtil.nullToEmpty(entry.getValue()), isOverride); + } + return (T) this; + } + + /** + * 设置请求头
+ * 不覆盖原有请求头 + * + * @param headers 请求头 + * @return this + */ + public T header(Map> headers) { + return header(headers, false); + } + + /** + * 设置请求头 + * + * @param headers 请求头 + * @param isOverride 是否覆盖已有头信息 + * @return this + * @since 4.0.8 + */ + public T header(Map> headers, boolean isOverride) { + if (MapUtil.isEmpty(headers)) { + return (T) this; + } + + String name; + for (Entry> entry : headers.entrySet()) { + name = entry.getKey(); + for (String value : entry.getValue()) { + this.header(name, StrUtil.nullToEmpty(value), isOverride); + } + } + return (T) this; + } + + /** + * 新增请求头
+ * 不覆盖原有请求头 + * + * @param headers 请求头 + * @return this + * @since 4.0.3 + */ + public T addHeaders(Map headers) { + if (MapUtil.isEmpty(headers)) { + return (T) this; + } + + for (Entry 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> 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> 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(); + } +} diff --git a/src/main/java/aiyh/utils/tool/cn/hutool/http/HttpConfig.java b/src/main/java/aiyh/utils/tool/cn/hutool/http/HttpConfig.java new file mode 100755 index 0000000..ac885fb --- /dev/null +++ b/src/main/java/aiyh/utils/tool/cn/hutool/http/HttpConfig.java @@ -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异常。
+ * 在Http协议中,对于Transfer-Encoding: Chunked在正常情况下末尾会写入一个Length为0的的chunk标识完整结束。
+ * 如果服务端未遵循这个规范或响应没有正常结束,会报EOF异常,此选项用于是否忽略这个异常。 + */ + boolean ignoreEOFError = HttpGlobalConfig.isIgnoreEOFError(); + /** + * 获取是否忽略解码URL,包括URL中的Path部分和Param部分。
+ * 在构建Http请求时,用户传入的URL可能有编码后和未编码的内容混合在一起,如果此参数为{@code true},则会统一解码编码后的参数,
+ * 按照RFC3986规范,在发送请求时,全部编码之。如果为{@code false},则不会解码已经编码的内容,在请求时只编码需要编码的部分。 + */ + boolean decodeUrl = HttpGlobalConfig.isDecodeUrl(); + + /** + * 请求前的拦截器,用于在请求前重新编辑请求 + */ + final HttpInterceptor.Chain requestInterceptors = GlobalInterceptor.INSTANCE.getCopiedRequestInterceptor(); + /** + * 响应后的拦截器,用于在响应后处理逻辑 + */ + final HttpInterceptor.Chain responseInterceptors = GlobalInterceptor.INSTANCE.getCopiedResponseInterceptor(); + + /** + * 重定向时是否使用拦截器 + */ + boolean interceptorOnRedirect; + + /** + * 设置超时,单位:毫秒
+ * 超时包括: + * + *
+	 * 1. 连接超时
+	 * 2. 读取响应超时
+	 * 
+ * + * @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; + } + + /** + * 设置最大重定向次数
+ * 如果次数小于1则表示不重定向,大于等于1表示打开重定向 + * + * @param maxRedirectCount 最大重定向次数 + * @return this + */ + public HttpConfig setMaxRedirectCount(int maxRedirectCount) { + this.maxRedirectCount = Math.max(maxRedirectCount, 0); + return this; + } + + /** + * 设置域名验证器
+ * 只针对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
+ * 只针对HTTPS请求,如果不设置,使用默认的SSLSocketFactory
+ * 默认SSLSocketFactory为:SSLSocketFactoryBuilder.create().build(); + * + * @param ssf SSLScketFactory + * @return this + */ + public HttpConfig setSSLSocketFactory(SSLSocketFactory ssf) { + this.ssf = ssf; + return this; + } + + /** + * 设置HTTPS安全连接协议,只针对HTTPS请求,可以使用的协议包括:
+ * 此方法调用后{@link #setSSLSocketFactory(SSLSocketFactory)} 将被覆盖。 + * + *
+	 * 1. TLSv1.2
+	 * 2. TLSv1.1
+	 * 3. SSLv3
+	 * ...
+	 * 
+ * + * @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; + } + + /** + * 采用流方式上传数据,无需本地缓存数据。
+ * HttpUrlConnection默认是将所有数据读到本地缓存,然后再发送给服务器,这样上传大文件时就会导致内存溢出。 + * + * @param blockSize 块大小(bytes数),0或小于0表示不设置Chuncked模式 + * @return this + */ + public HttpConfig setBlockSize(int blockSize) { + this.blockSize = blockSize; + return this; + } + + /** + * 设置是否忽略响应读取时可能的EOF异常。
+ * 在Http协议中,对于Transfer-Encoding: Chunked在正常情况下末尾会写入一个Length为0的的chunk标识完整结束。
+ * 如果服务端未遵循这个规范或响应没有正常结束,会报EOF异常,此选项用于是否忽略这个异常。 + * + * @param ignoreEOFError 是否忽略响应读取时可能的EOF异常。 + * @return this + * @since 5.7.20 + */ + public HttpConfig setIgnoreEOFError(boolean ignoreEOFError) { + this.ignoreEOFError = ignoreEOFError; + return this; + } + + /** + * 设置是否忽略解码URL,包括URL中的Path部分和Param部分。
+ * 在构建Http请求时,用户传入的URL可能有编码后和未编码的内容混合在一起,如果此参数为{@code true},则会统一解码编码后的参数,
+ * 按照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 interceptor) { + this.requestInterceptors.addChain(interceptor); + return this; + } + + /** + * 设置拦截器,用于在请求前重新编辑请求 + * + * @param interceptor 拦截器实现 + * @return this + */ + public HttpConfig addResponseInterceptor(HttpInterceptor interceptor) { + this.responseInterceptors.addChain(interceptor); + return this; + } + + /** + * 重定向时是否使用拦截器 + * + * @param interceptorOnRedirect 重定向时是否使用拦截器 + * @return this + */ + public HttpConfig setInterceptorOnRedirect(boolean interceptorOnRedirect) { + this.interceptorOnRedirect = interceptorOnRedirect; + return this; + } +} diff --git a/src/main/java/aiyh/utils/tool/cn/hutool/http/HttpConnection.java b/src/main/java/aiyh/utils/tool/cn/hutool/http/HttpConnection.java new file mode 100644 index 0000000..571c8d2 --- /dev/null +++ b/src/main/java/aiyh/utils/tool/cn/hutool/http/HttpConnection.java @@ -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 + + /** + * 设置请求头
+ * 当请求头存在时,覆盖之 + * + * @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; + } + + /** + * 设置请求头
+ * 当请求头存在时,覆盖之 + * + * @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); + } + + /** + * 设置请求头
+ * 不覆盖原有请求头 + * + * @param headerMap 请求头 + * @param isOverride 是否覆盖 + * @return this + */ + public HttpConnection header(Map> headerMap, boolean isOverride) { + if (MapUtil.isNotEmpty(headerMap)) { + String name; + for (Entry> 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> headers() { + return this.conn.getHeaderFields(); + } + + // ---------------------------------------------------------------- Headers end + + /** + * 设置https请求参数
+ * 有些时候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; + } + + /** + * 采用流方式上传数据,无需本地缓存数据。
+ * 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; + } + + /** + * 获得输入流对象
+ * 输入流对象用于读取数据 + * + * @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; + } + + /** + * 获得字符集编码
+ * 从Http连接的头信息中获得字符集
+ * 从ContentType中获取 + * + * @return 字符集编码 + */ + public String getCharsetName() { + return HttpUtil.getCharset(conn); + } + + /** + * 获取字符集编码
+ * 从Http连接的头信息中获得字符集
+ * 从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> entry : this.conn.getHeaderFields().entrySet()) { + // sb.append(" ").append(entry).append(StrUtil.CRLF); + // } + + return sb.toString(); + } + + // --------------------------------------------------------------- Private Method start + + /** + * 初始化http或https请求参数
+ * 有些时候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 +} diff --git a/src/main/java/aiyh/utils/tool/cn/hutool/http/HttpDownloader.java b/src/main/java/aiyh/utils/tool/cn/hutool/http/HttpDownloader.java new file mode 100644 index 0000000..d330a70 --- /dev/null +++ b/src/main/java/aiyh/utils/tool/cn/hutool/http/HttpDownloader.java @@ -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); + } + + /** + * 下载文件-避免未完成的文件
+ * 来自:https://gitee.com/dromara/hutool/pulls/407
+ * 此方法原理是先在目标文件同级目录下创建临时文件,下载之,等下载完毕后重命名,避免因下载错误导致的文件不完整。 + * + * @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()); + } +} diff --git a/src/main/java/aiyh/utils/tool/cn/hutool/http/HttpException.java b/src/main/java/aiyh/utils/tool/cn/hutool/http/HttpException.java new file mode 100644 index 0000000..11153cb --- /dev/null +++ b/src/main/java/aiyh/utils/tool/cn/hutool/http/HttpException.java @@ -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); + } +} diff --git a/src/main/java/aiyh/utils/tool/cn/hutool/http/HttpGlobalConfig.java b/src/main/java/aiyh/utils/tool/cn/hutool/http/HttpGlobalConfig.java new file mode 100755 index 0000000..5e8e524 --- /dev/null +++ b/src/main/java/aiyh/utils/tool/cn/hutool/http/HttpGlobalConfig.java @@ -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; + } + + /** + * 设置默认的连接和读取超时时长
+ * -1: 含义,永不超时。
+ * 如果:设置timeout = 3s(3000 ms), 那一次请求最大超时:就是:6s
+ * 官方含义:timeout of zero is interpreted as an infinite timeout. (0的超时被解释为无限超时。)
+ * 这里实际项目一定要进行修改,防止把系统拖死.
+ * 底层调用:{@link HttpURLConnection#setReadTimeout(int)} 同时设置: 读取超时
+ * 底层调用:{@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表示不重定向
+ * 如果设置为1,表示重定向一次,即请求两次 + * + * @return 全局默认的最大重定向次数 + * @since 5.7.19 + */ + public static int getMaxRedirectCount() { + return maxRedirectCount; + } + + /** + * 设置默认全局默认的最大重定向次数,如设置0表示不重定向
+ * 如果设置为1,表示重定向一次,即请求两次 + * + * @param customMaxRedirectCount 全局默认的最大重定向次数 + * @since 5.7.19 + */ + synchronized public static void setMaxRedirectCount(int customMaxRedirectCount) { + maxRedirectCount = customMaxRedirectCount; + } + + /** + * 获取是否忽略响应读取时可能的EOF异常。
+ * 在Http协议中,对于Transfer-Encoding: Chunked在正常情况下末尾会写入一个Length为0的的chunk标识完整结束。
+ * 如果服务端未遵循这个规范或响应没有正常结束,会报EOF异常,此选项用于是否忽略这个异常。 + * + * @return 是否忽略响应读取时可能的EOF异常 + * @since 5.7.20 + */ + public static boolean isIgnoreEOFError() { + return ignoreEOFError; + } + + /** + * 设置是否忽略响应读取时可能的EOF异常。
+ * 在Http协议中,对于Transfer-Encoding: Chunked在正常情况下末尾会写入一个Length为0的的chunk标识完整结束。
+ * 如果服务端未遵循这个规范或响应没有正常结束,会报EOF异常,此选项用于是否忽略这个异常。 + * + * @param customIgnoreEOFError 是否忽略响应读取时可能的EOF异常。 + * @since 5.7.20 + */ + synchronized public static void setIgnoreEOFError(boolean customIgnoreEOFError) { + ignoreEOFError = customIgnoreEOFError; + } + + /** + * 获取是否忽略解码URL,包括URL中的Path部分和Param部分。
+ * 在构建Http请求时,用户传入的URL可能有编码后和未编码的内容混合在一起,如果此参数为{@code true},则会统一解码编码后的参数,
+ * 按照RFC3986规范,在发送请求时,全部编码之。如果为{@code false},则不会解码已经编码的内容,在请求时只编码需要编码的部分。 + * + * @return 是否忽略解码URL + * @since 5.7.22 + */ + public static boolean isDecodeUrl() { + return decodeUrl; + } + + /** + * 设置是否忽略解码URL,包括URL中的Path部分和Param部分。
+ * 在构建Http请求时,用户传入的URL可能有编码后和未编码的内容混合在一起,如果此参数为{@code true},则会统一解码编码后的参数,
+ * 按照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方法
+ * 此方法通过注入方式修改{@link HttpURLConnection}中的methods静态属性,增加PATCH方法
+ * see: https://stackoverflow.com/questions/25163131/httpurlconnection-invalid-http-method-patch + * + * @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; + } +} diff --git a/src/main/java/aiyh/utils/tool/cn/hutool/http/HttpInputStream.java b/src/main/java/aiyh/utils/tool/cn/hutool/http/HttpInputStream.java new file mode 100644 index 0000000..d1c2dce --- /dev/null +++ b/src/main/java/aiyh/utils/tool/cn/hutool/http/HttpInputStream.java @@ -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)); + } + } +} diff --git a/src/main/java/aiyh/utils/tool/cn/hutool/http/HttpInterceptor.java b/src/main/java/aiyh/utils/tool/cn/hutool/http/HttpInterceptor.java new file mode 100644 index 0000000..4b4507c --- /dev/null +++ b/src/main/java/aiyh/utils/tool/cn/hutool/http/HttpInterceptor.java @@ -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 过滤参数类型,HttpRequest或者HttpResponse + * @author looly + * @since 5.7.16 + */ +@FunctionalInterface +public interface HttpInterceptor> { + + /** + * 处理请求 + * + * @param httpObj 请求或响应对象 + */ + void process(T httpObj); + + /** + * 拦截器链 + * + * @param 过滤参数类型,HttpRequest或者HttpResponse + * @author looly + * @since 5.7.16 + */ + class Chain> implements aiyh.utils.tool.cn.hutool.core.lang.Chain, Chain> { + private final List> interceptors = new LinkedList<>(); + + @Override + public Chain addChain(HttpInterceptor element) { + interceptors.add(element); + return this; + } + + @Override + public Iterator> iterator() { + return interceptors.iterator(); + } + + /** + * 清空 + * + * @return this + * @since 5.8.0 + */ + public Chain clear() { + interceptors.clear(); + return this; + } + } +} diff --git a/src/main/java/aiyh/utils/tool/cn/hutool/http/HttpRequest.java b/src/main/java/aiyh/utils/tool/cn/hutool/http/HttpRequest.java new file mode 100755 index 0000000..61f77c8 --- /dev/null +++ b/src/main/java/aiyh/utils/tool/cn/hutool/http/HttpRequest.java @@ -0,0 +1,1393 @@ +package aiyh.utils.tool.cn.hutool.http; + +import aiyh.utils.tool.cn.hutool.core.collection.CollUtil; +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.resource.BytesResource; +import aiyh.utils.tool.cn.hutool.core.io.resource.FileResource; +import aiyh.utils.tool.cn.hutool.core.io.resource.MultiFileResource; +import aiyh.utils.tool.cn.hutool.core.io.resource.Resource; +import aiyh.utils.tool.cn.hutool.core.lang.Assert; +import aiyh.utils.tool.cn.hutool.core.map.MapUtil; +import aiyh.utils.tool.cn.hutool.core.map.TableMap; +import aiyh.utils.tool.cn.hutool.core.net.SSLUtil; +import aiyh.utils.tool.cn.hutool.core.net.url.UrlBuilder; +import aiyh.utils.tool.cn.hutool.core.net.url.UrlQuery; +import aiyh.utils.tool.cn.hutool.core.util.ArrayUtil; +import aiyh.utils.tool.cn.hutool.core.util.ObjectUtil; +import aiyh.utils.tool.cn.hutool.core.util.StrUtil; +import aiyh.utils.tool.cn.hutool.http.body.BytesBody; +import aiyh.utils.tool.cn.hutool.http.body.FormUrlEncodedBody; +import aiyh.utils.tool.cn.hutool.http.body.MultipartBody; +import aiyh.utils.tool.cn.hutool.http.body.RequestBody; +import aiyh.utils.tool.cn.hutool.http.cookie.GlobalCookieManager; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.SSLSocketFactory; +import java.io.File; +import java.io.IOException; +import java.net.*; +import java.nio.charset.Charset; +import java.util.Collection; +import java.util.Map; +import java.util.function.Consumer; +import java.util.function.Function; + +/** + * http请求类
+ * Http请求类用于构建Http请求并同步获取结果,此类通过CookieManager持有域名对应的Cookie值,再次请求时会自动附带Cookie信息 + * + * @author Looly + */ +public class HttpRequest extends HttpBase { + + // ---------------------------------------------------------------- static Http Method start + + /** + * POST请求 + * + * @param url URL + * @return HttpRequest + */ + public static HttpRequest post(String url) { + return of(url).method(aiyh.utils.tool.cn.hutool.http.Method.POST); + } + + /** + * GET请求 + * + * @param url URL + * @return HttpRequest + */ + public static HttpRequest get(String url) { + return of(url).method(aiyh.utils.tool.cn.hutool.http.Method.GET); + } + + /** + * HEAD请求 + * + * @param url URL + * @return HttpRequest + */ + public static HttpRequest head(String url) { + return of(url).method(aiyh.utils.tool.cn.hutool.http.Method.HEAD); + } + + /** + * OPTIONS请求 + * + * @param url URL + * @return HttpRequest + */ + public static HttpRequest options(String url) { + return of(url).method(aiyh.utils.tool.cn.hutool.http.Method.OPTIONS); + } + + /** + * PUT请求 + * + * @param url URL + * @return HttpRequest + */ + public static HttpRequest put(String url) { + return of(url).method(aiyh.utils.tool.cn.hutool.http.Method.PUT); + } + + /** + * PATCH请求 + * + * @param url URL + * @return HttpRequest + * @since 3.0.9 + */ + public static HttpRequest patch(String url) { + return of(url).method(aiyh.utils.tool.cn.hutool.http.Method.PATCH); + } + + /** + * DELETE请求 + * + * @param url URL + * @return HttpRequest + */ + public static HttpRequest delete(String url) { + return of(url).method(aiyh.utils.tool.cn.hutool.http.Method.DELETE); + } + + /** + * TRACE请求 + * + * @param url URL + * @return HttpRequest + */ + public static HttpRequest trace(String url) { + return of(url).method(aiyh.utils.tool.cn.hutool.http.Method.TRACE); + } + + /** + * 构建一个HTTP请求
+ * 对于传入的URL,可以自定义是否解码已经编码的内容,设置见{@link HttpGlobalConfig#setDecodeUrl(boolean)}
+ * 在构建Http请求时,用户传入的URL可能有编码后和未编码的内容混合在一起,如果{@link HttpGlobalConfig#isDecodeUrl()}为{@code true},则会统一解码编码后的参数,
+ * 按照RFC3986规范,在发送请求时,全部编码之。如果为{@code false},则不会解码已经编码的内容,在请求时只编码需要编码的部分。 + * + * @param url URL链接,默认自动编码URL中的参数等信息 + * @return HttpRequest + * @since 5.7.18 + */ + public static HttpRequest of(String url) { + return of(url, HttpGlobalConfig.isDecodeUrl() ? DEFAULT_CHARSET : null); + } + + /** + * 构建一个HTTP请求
+ * 对于传入的URL,可以自定义是否解码已经编码的内容。
+ * 在构建Http请求时,用户传入的URL可能有编码后和未编码的内容混合在一起,如果charset参数不为{@code null},则会统一解码编码后的参数,
+ * 按照RFC3986规范,在发送请求时,全部编码之。如果为{@code false},则不会解码已经编码的内容,在请求时只编码需要编码的部分。 + * + * @param url URL链接 + * @param charset 编码,如果为{@code null}不自动解码编码URL + * @return HttpRequest + * @since 5.7.18 + */ + public static HttpRequest of(String url, Charset charset) { + return of(UrlBuilder.ofHttp(url, charset)); + } + + /** + * 构建一个HTTP请求
+ * + * @param url {@link UrlBuilder} + * @return HttpRequest + * @since 5.8.0 + */ + public static HttpRequest of(UrlBuilder url) { + return new HttpRequest(url); + } + + /** + * 设置全局默认的连接和读取超时时长 + * + * @param customTimeout 超时时长 + * @see HttpGlobalConfig#setTimeout(int) + * @since 4.6.2 + */ + public static void setGlobalTimeout(int customTimeout) { + HttpGlobalConfig.setTimeout(customTimeout); + } + + /** + * 获取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 + */ + public static void setCookieManager(CookieManager customCookieManager) { + GlobalCookieManager.setCookieManager(customCookieManager); + } + + /** + * 关闭Cookie + * + * @see GlobalCookieManager#setCookieManager(CookieManager) + * @since 4.1.9 + */ + public static void closeCookie() { + GlobalCookieManager.setCookieManager(null); + } + // ---------------------------------------------------------------- static Http Method end + + private HttpConfig config = HttpConfig.create(); + private UrlBuilder url; + private URLStreamHandler urlHandler; + private aiyh.utils.tool.cn.hutool.http.Method method = aiyh.utils.tool.cn.hutool.http.Method.GET; + /** + * 连接对象 + */ + private HttpConnection httpConnection; + + /** + * 存储表单数据 + */ + private Map form; + /** + * Cookie + */ + private String cookie; + /** + * 是否为Multipart表单 + */ + private boolean isMultiPart; + /** + * 是否是REST请求模式 + */ + private boolean isRest; + /** + * 重定向次数计数器,内部使用 + */ + private int redirectCount; + + /** + * 构造,URL编码默认使用UTF-8 + * + * @param url URL + * @deprecated 请使用 {@link #of(String)} + */ + @Deprecated + public HttpRequest(String url) { + this(UrlBuilder.ofHttp(url)); + } + + /** + * 构造 + * + * @param url {@link UrlBuilder} + */ + public HttpRequest(UrlBuilder url) { + this.url = Assert.notNull(url, "URL must be not null!"); + // 给定默认URL编码 + final Charset charset = url.getCharset(); + if (null != charset) { + this.charset(charset); + } + // 给定一个默认头信息 + this.header(GlobalHeaders.INSTANCE.headers); + } + + /** + * 获取请求URL + * + * @return URL字符串 + * @since 4.1.8 + */ + public String getUrl() { + return url.toString(); + } + + /** + * 设置URL + * + * @param url url字符串 + * @return this + * @since 4.1.8 + */ + public HttpRequest setUrl(String url) { + return setUrl(UrlBuilder.ofHttp(url, this.charset)); + } + + /** + * 设置URL + * + * @param urlBuilder url字符串 + * @return this + * @since 5.3.1 + */ + public HttpRequest setUrl(UrlBuilder urlBuilder) { + this.url = urlBuilder; + return this; + } + + /** + * 设置{@link URLStreamHandler} + *

+ * 部分环境下需要单独设置此项,例如当 WebLogic Server 实例充当 SSL 客户端角色(它会尝试通过 SSL 连接到其他服务器或应用程序)时,
+ * 它会验证 SSL 服务器在数字证书中返回的主机名是否与用于连接 SSL 服务器的 URL 主机名相匹配。如果主机名不匹配,则删除此连接。
+ * 因此weblogic不支持https的sni协议的主机名验证,此时需要将此值设置为sun.net.www.protocol.https.Handler对象。 + *

+ * 相关issue见:https://gitee.com/dromara/hutool/issues/IMD1X + * + * @param urlHandler {@link URLStreamHandler} + * @return this + * @since 4.1.9 + */ + public HttpRequest setUrlHandler(URLStreamHandler urlHandler) { + this.urlHandler = urlHandler; + return this; + } + + /** + * 获取Http请求方法 + * + * @return {@link aiyh.utils.tool.cn.hutool.http.Method} + * @since 4.1.8 + */ + public aiyh.utils.tool.cn.hutool.http.Method getMethod() { + return this.method; + } + + /** + * 设置请求方法 + * + * @param method HTTP方法 + * @return HttpRequest + * @see #method(aiyh.utils.tool.cn.hutool.http.Method) + * @since 4.1.8 + */ + public HttpRequest setMethod(aiyh.utils.tool.cn.hutool.http.Method method) { + return method(method); + } + + /** + * 获取{@link HttpConnection}
+ * 在{@link #execute()} 执行前此对象为null + * + * @return {@link HttpConnection} + * @since 4.2.2 + */ + public HttpConnection getConnection() { + return this.httpConnection; + } + + /** + * 设置请求方法 + * + * @param method HTTP方法 + * @return HttpRequest + */ + public HttpRequest method(aiyh.utils.tool.cn.hutool.http.Method method) { + this.method = method; + return this; + } + + // ---------------------------------------------------------------- Http Request Header start + + /** + * 设置contentType + * + * @param contentType contentType + * @return HttpRequest + */ + public HttpRequest contentType(String contentType) { + header(aiyh.utils.tool.cn.hutool.http.Header.CONTENT_TYPE, contentType); + return this; + } + + /** + * 设置是否为长连接 + * + * @param isKeepAlive 是否长连接 + * @return HttpRequest + */ + public HttpRequest keepAlive(boolean isKeepAlive) { + header(aiyh.utils.tool.cn.hutool.http.Header.CONNECTION, isKeepAlive ? "Keep-Alive" : "Close"); + return this; + } + + /** + * @return 获取是否为长连接 + */ + public boolean isKeepAlive() { + String connection = header(aiyh.utils.tool.cn.hutool.http.Header.CONNECTION); + if (connection == null) { + return !HTTP_1_0.equalsIgnoreCase(httpVersion); + } + + return !"close".equalsIgnoreCase(connection); + } + + /** + * 获取内容长度 + * + * @return String + */ + public String contentLength() { + return header(aiyh.utils.tool.cn.hutool.http.Header.CONTENT_LENGTH); + } + + /** + * 设置内容长度 + * + * @param value 长度 + * @return HttpRequest + */ + public HttpRequest contentLength(int value) { + header(aiyh.utils.tool.cn.hutool.http.Header.CONTENT_LENGTH, String.valueOf(value)); + return this; + } + + /** + * 设置Cookie
+ * 自定义Cookie后会覆盖Hutool的默认Cookie行为 + * + * @param cookies Cookie值数组,如果为{@code null}则设置无效,使用默认Cookie行为 + * @return this + * @since 5.4.1 + */ + public HttpRequest cookie(Collection cookies) { + return cookie(CollUtil.isEmpty(cookies) ? null : cookies.toArray(new HttpCookie[0])); + } + + /** + * 设置Cookie
+ * 自定义Cookie后会覆盖Hutool的默认Cookie行为 + * + * @param cookies Cookie值数组,如果为{@code null}则设置无效,使用默认Cookie行为 + * @return this + * @since 3.1.1 + */ + public HttpRequest cookie(HttpCookie... cookies) { + if (ArrayUtil.isEmpty(cookies)) { + return disableCookie(); + } + // 名称/值对之间用分号和空格 ('; ') + // https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Cookie + return cookie(ArrayUtil.join(cookies, "; ")); + } + + /** + * 设置Cookie
+ * 自定义Cookie后会覆盖Hutool的默认Cookie行为 + * + * @param cookie Cookie值,如果为{@code null}则设置无效,使用默认Cookie行为 + * @return this + * @since 3.0.7 + */ + public HttpRequest cookie(String cookie) { + this.cookie = cookie; + return this; + } + + /** + * 禁用默认Cookie行为,此方法调用后会将Cookie置为空。
+ * 如果想重新启用Cookie,请调用:{@link #cookie(String)}方法自定义Cookie。
+ * 如果想启动默认的Cookie行为(自动回填服务器传回的Cookie),则调用{@link #enableDefaultCookie()} + * + * @return this + * @since 3.0.7 + */ + public HttpRequest disableCookie() { + return cookie(StrUtil.EMPTY); + } + + /** + * 打开默认的Cookie行为(自动回填服务器传回的Cookie) + * + * @return this + */ + public HttpRequest enableDefaultCookie() { + return cookie((String) null); + } + // ---------------------------------------------------------------- Http Request Header end + + // ---------------------------------------------------------------- Form start + + /** + * 设置表单数据
+ * + * @param name 名 + * @param value 值 + * @return this + */ + public HttpRequest form(String name, Object value) { + if (StrUtil.isBlank(name) || ObjectUtil.isNull(value)) { + return this; // 忽略非法的form表单项内容; + } + + // 停用body + this.bodyBytes = null; + + if (value instanceof File) { + // 文件上传 + return this.form(name, (File) value); + } + + if (value instanceof Resource) { + return form(name, (Resource) value); + } + + // 普通值 + String strValue; + if (value instanceof Iterable) { + // 列表对象 + strValue = CollUtil.join((Iterable) value, ","); + } else if (ArrayUtil.isArray(value)) { + if (File.class == ArrayUtil.getComponentType(value)) { + // 多文件 + return this.form(name, (File[]) value); + } + // 数组对象 + strValue = ArrayUtil.join((Object[]) value, ","); + } else { + // 其他对象一律转换为字符串 + strValue = Convert.toStr(value, null); + } + + return putToForm(name, strValue); + } + + /** + * 设置表单数据 + * + * @param name 名 + * @param value 值 + * @param parameters 参数对,奇数为名,偶数为值 + * @return this + */ + public HttpRequest form(String name, Object value, Object... parameters) { + form(name, value); + + for (int i = 0; i < parameters.length; i += 2) { + form(parameters[i].toString(), parameters[i + 1]); + } + return this; + } + + /** + * 设置map类型表单数据 + * + * @param formMap 表单内容 + * @return this + */ + public HttpRequest form(Map formMap) { + if (MapUtil.isNotEmpty(formMap)) { + formMap.forEach(this::form); + } + return this; + } + + /** + * 设置map<String, String>类型表单数据 + * + * @param formMapStr 表单内容 + * @return this + * @since 5.6.7 + */ + public HttpRequest formStr(Map formMapStr) { + if (MapUtil.isNotEmpty(formMapStr)) { + formMapStr.forEach(this::form); + } + return this; + } + + /** + * 文件表单项
+ * 一旦有文件加入,表单变为multipart/form-data + * + * @param name 名 + * @param files 需要上传的文件,为空跳过 + * @return this + */ + public HttpRequest form(String name, File... files) { + if (ArrayUtil.isEmpty(files)) { + return this; + } + if (1 == files.length) { + final File file = files[0]; + return form(name, file, file.getName()); + } + return form(name, new MultiFileResource(files)); + } + + /** + * 文件表单项
+ * 一旦有文件加入,表单变为multipart/form-data + * + * @param name 名 + * @param file 需要上传的文件 + * @return this + */ + public HttpRequest form(String name, File file) { + return form(name, file, file.getName()); + } + + /** + * 文件表单项
+ * 一旦有文件加入,表单变为multipart/form-data + * + * @param name 名 + * @param file 需要上传的文件 + * @param fileName 文件名,为空使用文件默认的文件名 + * @return this + */ + public HttpRequest form(String name, File file, String fileName) { + if (null != file) { + form(name, new FileResource(file, fileName)); + } + return this; + } + + /** + * 文件byte[]表单项
+ * 一旦有文件加入,表单变为multipart/form-data + * + * @param name 名 + * @param fileBytes 需要上传的文件 + * @param fileName 文件名 + * @return this + * @since 4.1.0 + */ + public HttpRequest form(String name, byte[] fileBytes, String fileName) { + if (null != fileBytes) { + form(name, new BytesResource(fileBytes, fileName)); + } + return this; + } + + /** + * 文件表单项
+ * 一旦有文件加入,表单变为multipart/form-data + * + * @param name 名 + * @param resource 数据源,文件可以使用{@link FileResource}包装使用 + * @return this + * @since 4.0.9 + */ + public HttpRequest form(String name, Resource resource) { + if (null != resource) { + if (!isKeepAlive()) { + keepAlive(true); + } + + this.isMultiPart = true; + return putToForm(name, resource); + } + return this; + } + + /** + * 获取表单数据 + * + * @return 表单Map + */ + public Map form() { + return this.form; + } + + /** + * 获取文件表单数据 + * + * @return 文件表单Map + * @since 3.3.0 + */ + public Map fileForm() { + final Map result = MapUtil.newHashMap(); + this.form.forEach((key, value) -> { + if (value instanceof Resource) { + result.put(key, (Resource) value); + } + }); + return result; + } + // ---------------------------------------------------------------- Form end + + // ---------------------------------------------------------------- Body start + + /** + * 设置内容主体
+ * 请求体body参数支持两种类型: + * + *

+	 * 1. 标准参数,例如 a=1&b=2 这种格式
+	 * 2. Rest模式,此时body需要传入一个JSON或者XML字符串,Hutool会自动绑定其对应的Content-Type
+	 * 
+ * + * @param body 请求体 + * @return this + */ + public HttpRequest body(String body) { + return this.body(body, null); + } + + /** + * 设置内容主体
+ * 请求体body参数支持两种类型: + * + *
+	 * 1. 标准参数,例如 a=1&b=2 这种格式
+	 * 2. Rest模式,此时body需要传入一个JSON或者XML字符串,Hutool会自动绑定其对应的Content-Type
+	 * 
+ * + * @param body 请求体 + * @param contentType 请求体类型,{@code null}表示自动判断类型 + * @return this + */ + public HttpRequest body(String body, String contentType) { + byte[] bytes = StrUtil.bytes(body, this.charset); + body(bytes); + this.form = null; // 当使用body时,停止form的使用 + + if (null != contentType) { + // Content-Type自定义设置 + this.contentType(contentType); + } else { + // 在用户未自定义的情况下自动根据内容判断 + contentType = HttpUtil.getContentTypeByRequestBody(body); + if (null != contentType && ContentType.isDefault(this.header(aiyh.utils.tool.cn.hutool.http.Header.CONTENT_TYPE))) { + if (null != this.charset) { + // 附加编码信息 + contentType = ContentType.build(contentType, this.charset); + } + this.contentType(contentType); + } + } + + // 判断是否为rest请求 + if (StrUtil.containsAnyIgnoreCase(contentType, "json", "xml")) { + this.isRest = true; + contentLength(bytes.length); + } + return this; + } + + /** + * 设置主体字节码
+ * 需在此方法调用前使用charset方法设置编码,否则使用默认编码UTF-8 + * + * @param bodyBytes 主体 + * @return this + */ + public HttpRequest body(byte[] bodyBytes) { + if (null != bodyBytes) { + this.bodyBytes = bodyBytes; + } + return this; + } + // ---------------------------------------------------------------- Body end + + /** + * 将新的配置加入
+ * 注意加入的配置可能被修改 + * + * @param config 配置 + * @return this + */ + public HttpRequest setConfig(HttpConfig config) { + this.config = config; + return this; + } + + /** + * 设置超时,单位:毫秒
+ * 超时包括: + * + *
+	 * 1. 连接超时
+	 * 2. 读取响应超时
+	 * 
+ * + * @param milliseconds 超时毫秒数 + * @return this + * @see #setConnectionTimeout(int) + * @see #setReadTimeout(int) + */ + public HttpRequest timeout(int milliseconds) { + config.timeout(milliseconds); + return this; + } + + /** + * 设置连接超时,单位:毫秒 + * + * @param milliseconds 超时毫秒数 + * @return this + * @since 4.5.6 + */ + public HttpRequest setConnectionTimeout(int milliseconds) { + config.setConnectionTimeout(milliseconds); + return this; + } + + /** + * 设置连接超时,单位:毫秒 + * + * @param milliseconds 超时毫秒数 + * @return this + * @since 4.5.6 + */ + public HttpRequest setReadTimeout(int milliseconds) { + config.setReadTimeout(milliseconds); + return this; + } + + /** + * 禁用缓存 + * + * @return this + */ + public HttpRequest disableCache() { + config.disableCache(); + return this; + } + + /** + * 设置是否打开重定向,如果打开默认重定向次数为2
+ * 此方法效果与{@link #setMaxRedirectCount(int)} 一致 + * + *

+ * 需要注意的是,当设置为{@code true}时,如果全局重定向次数非0,直接复用,否则设置默认2次。
+ * 当设置为{@code false}时,无论全局是否设置次数,都设置为0。
+ * 不调用此方法的情况下,使用全局默认的次数。 + *

+ * + * @param isFollowRedirects 是否打开重定向 + * @return this + */ + public HttpRequest setFollowRedirects(boolean isFollowRedirects) { + if (isFollowRedirects) { + if (config.maxRedirectCount <= 0) { + // 默认两次跳转 + return setMaxRedirectCount(2); + } + } else { + // 手动强制关闭重定向,此时不受全局重定向设置影响 + if (config.maxRedirectCount < 0) { + return setMaxRedirectCount(0); + } + } + return this; + } + + /** + * 设置最大重定向次数
+ * 如果次数小于1则表示不重定向,大于等于1表示打开重定向 + * + * @param maxRedirectCount 最大重定向次数 + * @return this + * @since 3.3.0 + */ + public HttpRequest setMaxRedirectCount(int maxRedirectCount) { + config.setMaxRedirectCount(maxRedirectCount); + return this; + } + + /** + * 设置域名验证器
+ * 只针对HTTPS请求,如果不设置,不做验证,所有域名被信任 + * + * @param hostnameVerifier HostnameVerifier + * @return this + */ + public HttpRequest setHostnameVerifier(HostnameVerifier hostnameVerifier) { + config.setHostnameVerifier(hostnameVerifier); + return this; + } + + /** + * 设置Http代理 + * + * @param host 代理 主机 + * @param port 代理 端口 + * @return this + * @since 5.4.5 + */ + public HttpRequest setHttpProxy(String host, int port) { + config.setHttpProxy(host, port); + return this; + } + + /** + * 设置代理 + * + * @param proxy 代理 {@link Proxy} + * @return this + */ + public HttpRequest setProxy(Proxy proxy) { + config.setProxy(proxy); + return this; + } + + /** + * 设置SSLSocketFactory
+ * 只针对HTTPS请求,如果不设置,使用默认的SSLSocketFactory
+ * 默认SSLSocketFactory为:SSLSocketFactoryBuilder.create().build(); + * + * @param ssf SSLScketFactory + * @return this + */ + public HttpRequest setSSLSocketFactory(SSLSocketFactory ssf) { + config.setSSLSocketFactory(ssf); + return this; + } + + /** + * 设置HTTPS安全连接协议,只针对HTTPS请求,可以使用的协议包括:
+ * 此方法调用后{@link #setSSLSocketFactory(SSLSocketFactory)} 将被覆盖。 + * + *
+	 * 1. TLSv1.2
+	 * 2. TLSv1.1
+	 * 3. SSLv3
+	 * ...
+	 * 
+ * + * @param protocol 协议 + * @return this + * @see SSLUtil#createSSLContext(String) + * @see #setSSLSocketFactory(SSLSocketFactory) + */ + public HttpRequest setSSLProtocol(String protocol) { + config.setSSLProtocol(protocol); + return this; + } + + /** + * 设置是否rest模式
+ * rest模式下get请求不会把参数附加到URL之后 + * + * @param isRest 是否rest模式 + * @return this + * @since 4.5.0 + */ + public HttpRequest setRest(boolean isRest) { + this.isRest = isRest; + return this; + } + + /** + * 采用流方式上传数据,无需本地缓存数据。
+ * HttpUrlConnection默认是将所有数据读到本地缓存,然后再发送给服务器,这样上传大文件时就会导致内存溢出。 + * + * @param blockSize 块大小(bytes数),0或小于0表示不设置Chuncked模式 + * @return this + * @since 4.6.5 + */ + public HttpRequest setChunkedStreamingMode(int blockSize) { + config.setBlockSize(blockSize); + return this; + } + + /** + * 设置拦截器,用于在请求前重新编辑请求 + * + * @param interceptor 拦截器实现 + * @return this + * @see #addRequestInterceptor(HttpInterceptor) + * @since 5.7.16 + */ + public HttpRequest addInterceptor(HttpInterceptor interceptor) { + return addRequestInterceptor(interceptor); + } + + /** + * 设置拦截器,用于在请求前重新编辑请求 + * + * @param interceptor 拦截器实现 + * @return this + * @since 5.8.0 + */ + public HttpRequest addRequestInterceptor(HttpInterceptor interceptor) { + config.addRequestInterceptor(interceptor); + return this; + } + + /** + * 设置拦截器,用于在请求前重新编辑请求 + * + * @param interceptor 拦截器实现 + * @return this + * @since 5.8.0 + */ + public HttpRequest addResponseInterceptor(HttpInterceptor interceptor) { + config.addResponseInterceptor(interceptor); + return this; + } + + /** + * 执行Reuqest请求 + * + * @return this + */ + public aiyh.utils.tool.cn.hutool.http.HttpResponse execute() { + return this.execute(false); + } + + /** + * 异步请求
+ * 异步请求后获取的{@link aiyh.utils.tool.cn.hutool.http.HttpResponse} 为异步模式,执行完此方法后发送请求到服务器,但是并不立即读取响应内容。
+ * 此时保持Http连接不关闭,直调用获取内容方法为止。 + * + *

+ * 一般执行完execute之后会把响应内容全部读出来放在一个 byte数组里,如果你响应的内容太多内存就爆了,此法是发送完请求不直接读响应内容,等有需要的时候读。 + * + * @return 异步对象,使用get方法获取HttpResponse对象 + */ + public aiyh.utils.tool.cn.hutool.http.HttpResponse executeAsync() { + return this.execute(true); + } + + /** + * 执行Reuqest请求 + * + * @param isAsync 是否异步 + * @return this + */ + public aiyh.utils.tool.cn.hutool.http.HttpResponse execute(boolean isAsync) { + return doExecute(isAsync, config.requestInterceptors, config.responseInterceptors); + } + + /** + * 执行Request请求后,对响应内容后续处理
+ * 处理结束后关闭连接 + * + * @param consumer 响应内容处理函数 + * @since 5.7.8 + */ + public void then(Consumer consumer) { + try (final aiyh.utils.tool.cn.hutool.http.HttpResponse response = execute(true)) { + consumer.accept(response); + } + } + + /** + * 执行Request请求后,对响应内容后续处理
+ * 处理结束后关闭连接 + * + * @param 处理结果类型 + * @param function 响应内容处理函数 + * @return 处理结果 + * @since 5.8.5 + */ + public T thenFunction(Function function) { + try (final aiyh.utils.tool.cn.hutool.http.HttpResponse response = execute(true)) { + return function.apply(response); + } + } + + /** + * 简单验证,生成的头信息类似于: + *

+	 * Authorization: Basic YWxhZGRpbjpvcGVuc2VzYW1l
+	 * 
+ * + * @param username 用户名 + * @param password 密码 + * @return this + */ + public HttpRequest basicAuth(String username, String password) { + return auth(HttpUtil.buildBasicAuth(username, password, charset)); + } + + /** + * 简单代理验证,生成的头信息类似于: + *
+	 * Proxy-Authorization: Basic YWxhZGRpbjpvcGVuc2VzYW1l
+	 * 
+ * + * @param username 用户名 + * @param password 密码 + * @return this + * @since 5.4.6 + */ + public HttpRequest basicProxyAuth(String username, String password) { + return proxyAuth(HttpUtil.buildBasicAuth(username, password, charset)); + } + + /** + * 令牌验证,生成的头类似于:"Authorization: Bearer XXXXX",一般用于JWT + * + * @param token 令牌内容 + * @return HttpRequest + * @since 5.5.3 + */ + public HttpRequest bearerAuth(String token) { + return auth("Bearer " + token); + } + + /** + * 验证,简单插入Authorization头 + * + * @param content 验证内容 + * @return HttpRequest + * @since 5.2.4 + */ + public HttpRequest auth(String content) { + header(aiyh.utils.tool.cn.hutool.http.Header.AUTHORIZATION, content, true); + return this; + } + + /** + * 验证,简单插入Authorization头 + * + * @param content 验证内容 + * @return HttpRequest + * @since 5.4.6 + */ + public HttpRequest proxyAuth(String content) { + header(aiyh.utils.tool.cn.hutool.http.Header.PROXY_AUTHORIZATION, content, true); + return this; + } + + @Override + public String toString() { + StringBuilder sb = StrUtil.builder(); + sb.append("Request Url: ").append(this.url.setCharset(this.charset)).append(StrUtil.CRLF); + sb.append(super.toString()); + return sb.toString(); + } + + // ---------------------------------------------------------------- Private method start + + /** + * 执行Reuqest请求 + * + * @param isAsync 是否异步 + * @param requestInterceptors 请求拦截器列表 + * @param responseInterceptors 响应拦截器列表 + * @return this + */ + private aiyh.utils.tool.cn.hutool.http.HttpResponse doExecute(boolean isAsync, HttpInterceptor.Chain requestInterceptors, + HttpInterceptor.Chain responseInterceptors) { + if (null != requestInterceptors) { + for (HttpInterceptor interceptor : requestInterceptors) { + interceptor.process(this); + } + } + + // 初始化URL + urlWithParamIfGet(); + // 初始化 connection + initConnection(); + // 发送请求 + send(); + + // 手动实现重定向 + aiyh.utils.tool.cn.hutool.http.HttpResponse httpResponse = sendRedirectIfPossible(isAsync); + + // 获取响应 + if (null == httpResponse) { + httpResponse = new aiyh.utils.tool.cn.hutool.http.HttpResponse(this.httpConnection, this.config, this.charset, isAsync, isIgnoreResponseBody()); + } + + // 拦截响应 + if (null != responseInterceptors) { + for (HttpInterceptor interceptor : responseInterceptors) { + interceptor.process(httpResponse); + } + } + + return httpResponse; + } + + /** + * 初始化网络连接 + */ + private void initConnection() { + if (null != this.httpConnection) { + // 执行下次请求时自动关闭上次请求(常用于转发) + this.httpConnection.disconnectQuietly(); + } + + this.httpConnection = HttpConnection + // issue#I50NHQ + // 在生成正式URL前,设置自定义编码 + .create(this.url.setCharset(this.charset).toURL(this.urlHandler), config.proxy)// + .setConnectTimeout(config.connectionTimeout)// + .setReadTimeout(config.readTimeout)// + .setMethod(this.method)// + .setHttpsInfo(config.hostnameVerifier, config.ssf)// + // 关闭JDK自动转发,采用手动转发方式 + .setInstanceFollowRedirects(false) + // 流方式上传数据 + .setChunkedStreamingMode(config.blockSize) + // 覆盖默认Header + .header(this.headers, true); + + if (null != this.cookie) { + // 当用户自定义Cookie时,全局Cookie自动失效 + this.httpConnection.setCookie(this.cookie); + } else { + // 读取全局Cookie信息并附带到请求中 + GlobalCookieManager.add(this.httpConnection); + } + + // 是否禁用缓存 + if (config.isDisableCache) { + this.httpConnection.disableCache(); + } + } + + /** + * 对于GET请求将参数加到URL中
+ * 此处不对URL中的特殊字符做单独编码
+ * 对于非rest的GET请求,且处于重定向时,参数丢弃 + */ + private void urlWithParamIfGet() { + if (aiyh.utils.tool.cn.hutool.http.Method.GET.equals(method) && !this.isRest && this.redirectCount <= 0) { + UrlQuery query = this.url.getQuery(); + if (null == query) { + query = new UrlQuery(); + this.url.setQuery(query); + } + + // 优先使用body形式的参数,不存在使用form + if (ArrayUtil.isNotEmpty(this.bodyBytes)) { + query.parse(StrUtil.str(this.bodyBytes, this.charset), this.charset); + } else { + query.addAll(this.form); + } + } + } + + /** + * 调用转发,如果需要转发返回转发结果,否则返回{@code null} + * + * @param isAsync 是否异步 + * @return {@link aiyh.utils.tool.cn.hutool.http.HttpResponse},无转发返回 {@code null} + */ + private HttpResponse sendRedirectIfPossible(boolean isAsync) { + // 手动实现重定向 + if (config.maxRedirectCount > 0) { + int responseCode; + try { + responseCode = httpConnection.responseCode(); + } catch (IOException e) { + // 错误时静默关闭连接 + this.httpConnection.disconnectQuietly(); + throw new HttpException(e); + } + + if (responseCode != HttpURLConnection.HTTP_OK) { + if (HttpStatus.isRedirected(responseCode)) { + final UrlBuilder redirectUrl; + String location = httpConnection.header(aiyh.utils.tool.cn.hutool.http.Header.LOCATION); + if (!HttpUtil.isHttp(location) && !HttpUtil.isHttps(location)) { + // issue#I5TPSY + // location可能为相对路径 + if (!location.startsWith("/")) { + location = StrUtil.addSuffixIfNot(this.url.getPathStr(), "/") + location; + } + redirectUrl = UrlBuilder.of(this.url.getScheme(), this.url.getHost(), this.url.getPort() + , location, null, null, this.charset); + } else { + redirectUrl = UrlBuilder.ofHttpWithoutEncode(location); + } + setUrl(redirectUrl); + if (redirectCount < config.maxRedirectCount) { + redirectCount++; + // 重定向不再走过滤器 + return doExecute(isAsync, config.interceptorOnRedirect ? config.requestInterceptors : null, + config.interceptorOnRedirect ? config.responseInterceptors : null); + } + } + } + } + return null; + } + + /** + * 发送数据流 + * + * @throws IORuntimeException IO异常 + */ + private void send() throws IORuntimeException { + try { + if (aiyh.utils.tool.cn.hutool.http.Method.POST.equals(this.method) // + || aiyh.utils.tool.cn.hutool.http.Method.PUT.equals(this.method) // + || aiyh.utils.tool.cn.hutool.http.Method.DELETE.equals(this.method) // + || this.isRest) { + if (isMultipart()) { + sendMultipart(); // 文件上传表单 + } else { + sendFormUrlEncoded();// 普通表单 + } + } else { + this.httpConnection.connect(); + } + } catch (IOException e) { + // 异常时关闭连接 + this.httpConnection.disconnectQuietly(); + throw new IORuntimeException(e); + } + } + + /** + * 发送普通表单
+ * 发送数据后自动关闭输出流 + * + * @throws IOException IO异常 + */ + private void sendFormUrlEncoded() throws IOException { + if (StrUtil.isBlank(this.header(aiyh.utils.tool.cn.hutool.http.Header.CONTENT_TYPE))) { + // 如果未自定义Content-Type,使用默认的application/x-www-form-urlencoded + this.httpConnection.header(aiyh.utils.tool.cn.hutool.http.Header.CONTENT_TYPE, ContentType.FORM_URLENCODED.toString(this.charset), true); + } + + // Write的时候会优先使用body中的内容,write时自动关闭OutputStream + RequestBody body; + if (ArrayUtil.isNotEmpty(this.bodyBytes)) { + body = BytesBody.create(this.bodyBytes); + } else { + body = FormUrlEncodedBody.create(this.form, this.charset); + } + body.writeClose(this.httpConnection.getOutputStream()); + } + + /** + * 发送多组件请求(例如包含文件的表单)
+ * 发送数据后自动关闭输出流 + * + * @throws IOException IO异常 + */ + private void sendMultipart() throws IOException { + final MultipartBody multipartBody = MultipartBody.create(this.form, this.charset); + // 设置表单类型为Multipart(文件上传) + this.httpConnection.header(aiyh.utils.tool.cn.hutool.http.Header.CONTENT_TYPE, multipartBody.getContentType(), true); + multipartBody.writeClose(this.httpConnection.getOutputStream()); + } + + /** + * 是否忽略读取响应body部分
+ * HEAD、CONNECT、OPTIONS、TRACE方法将不读取响应体 + * + * @return 是否需要忽略响应body部分 + * @since 3.1.2 + */ + private boolean isIgnoreResponseBody() { + return aiyh.utils.tool.cn.hutool.http.Method.HEAD == this.method // + || aiyh.utils.tool.cn.hutool.http.Method.CONNECT == this.method // + || aiyh.utils.tool.cn.hutool.http.Method.OPTIONS == this.method // + || Method.TRACE == this.method; + } + + /** + * 判断是否为multipart/form-data表单,条件如下: + * + *
+	 *     1. 存在资源对象(fileForm非空)
+	 *     2. 用户自定义头为multipart/form-data开头
+	 * 
+ * + * @return 是否为multipart/form-data表单 + * @since 5.3.5 + */ + private boolean isMultipart() { + if (this.isMultiPart) { + return true; + } + + final String contentType = header(Header.CONTENT_TYPE); + return StrUtil.isNotEmpty(contentType) && + contentType.startsWith(ContentType.MULTIPART.getValue()); + } + + /** + * 将参数加入到form中,如果form为空,新建之。 + * + * @param name 表单属性名 + * @param value 属性值 + * @return this + */ + private HttpRequest putToForm(String name, Object value) { + if (null == name || null == value) { + return this; + } + if (null == this.form) { + this.form = new TableMap<>(16); + } + this.form.put(name, value); + return this; + } + // ---------------------------------------------------------------- Private method end + +} diff --git a/src/main/java/aiyh/utils/tool/cn/hutool/http/HttpResource.java b/src/main/java/aiyh/utils/tool/cn/hutool/http/HttpResource.java new file mode 100644 index 0000000..1b345f9 --- /dev/null +++ b/src/main/java/aiyh/utils/tool/cn/hutool/http/HttpResource.java @@ -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; + } +} diff --git a/src/main/java/aiyh/utils/tool/cn/hutool/http/HttpResponse.java b/src/main/java/aiyh/utils/tool/cn/hutool/http/HttpResponse.java new file mode 100755 index 0000000..379f947 --- /dev/null +++ b/src/main/java/aiyh/utils/tool/cn/hutool/http/HttpResponse.java @@ -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响应类
+ * 非线程安全对象 + * + * @author Looly + */ +public class HttpResponse extends HttpBase 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; + } + + /** + * 同步
+ * 如果为异步状态,则暂时不读取服务器中响应的内容,而是持有Http链接的{@link InputStream}。
+ * 当调用此方法时,异步状态转为同步状态,此时从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); + } + + /** + * 获取内容长度,以下情况长度无效: + *
    + *
  • Transfer-Encoding: Chunked
  • + *
  • Content-Encoding: XXX
  • + *
+ * 参考: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 getCookies() { + return GlobalCookieManager.getCookies(this.httpConnection); + } + + /** + * 获取Cookie + * + * @param name Cookie名 + * @return {@link HttpCookie} + * @since 4.1.4 + */ + public HttpCookie getCookie(String name) { + List 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 + + /** + * 获得服务区响应流
+ * 异步模式下获取Http原生流,同步模式下获取获取到的在内存中的副本
+ * 如果想在同步模式下获取流,请先调用{@link #sync()}方法强制同步
+ * 流获取后处理完毕需关闭此类 + * + * @return 响应流 + */ + public InputStream bodyStream() { + if (isAsync) { + return this.in; + } + return new ByteArrayInputStream(this.bodyBytes); + } + + /** + * 获取响应流字节码
+ * 此方法会转为同步模式 + * + * @return byte[] + */ + @Override + public byte[] bodyBytes() { + sync(); + return this.bodyBytes; + } + + /** + * 设置主体字节码
+ * 需在此方法调用前使用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}
+ * 异步模式下直接读取Http流写出,同步模式下将存储在内存中的响应内容写出
+ * 写出后会关闭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); + } + } + } + + /** + * 将响应内容写出到文件
+ * 异步模式下直接读取Http流写出,同步模式下将存储在内存中的响应内容写出
+ * 写出后会关闭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); + } + + /** + * 将响应内容写出到文件-避免未完成的文件
+ * 异步模式下直接读取Http流写出,同步模式下将存储在内存中的响应内容写出
+ * 写出后会关闭Http流(异步模式)
+ * 来自:https://gitee.com/dromara/hutool/pulls/407
+ * 此方法原理是先在目标文件同级目录下创建临时文件,下载之,等下载完毕后重命名,避免因下载错误导致的文件不完整。 + * + * @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; + } + + /** + * 将响应内容写出到文件
+ * 异步模式下直接读取Http流写出,同步模式下将存储在内存中的响应内容写出
+ * 写出后会关闭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; + } + + /** + * 将响应内容写出到文件
+ * 异步模式下直接读取Http流写出,同步模式下将存储在内存中的响应内容写出
+ * 写出后会关闭Http流(异步模式) + * + * @param targetFileOrDir 写出到的文件或目录 + * @return 写出bytes数 + * @since 3.3.2 + */ + public long writeBody(File targetFileOrDir) { + return writeBody(targetFileOrDir, null); + } + + /** + * 将响应内容写出到文件
+ * 异步模式下直接读取Http流写出,同步模式下将存储在内存中的响应内容写出
+ * 写出后会关闭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> 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响应,并在报错时关闭连接。
+ * 初始化包括: + * + *
+	 * 1、读取Http状态
+	 * 2、读取头信息
+	 * 3、持有Http流,并不关闭流
+	 * 
+ * + * @return this + * @throws HttpException IO异常 + */ + private HttpResponse initWithDisconnect() throws HttpException { + try { + init(); + } catch (HttpException e) { + this.httpConnection.disconnectQuietly(); + throw e; + } + return this; + } + + /** + * 初始化Http响应
+ * 初始化包括: + * + *
+	 * 1、读取Http状态
+	 * 2、读取头信息
+	 * 3、持有Http流,并不关闭流
+	 * 
+ * + * @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(); + } + + /** + * 强制同步,用于初始化
+ * 强制同步后变化如下: + * + *
+	 * 1、读取body内容到内存
+	 * 2、异步状态设为false(变为同步状态)
+	 * 3、关闭Http流
+	 * 4、断开与服务器连接
+	 * 
+ * + * @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}
+ * 异步模式下直接读取Http流写出,同步模式下将存储在内存中的响应内容写出
+ * 写出后会关闭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 +} diff --git a/src/main/java/aiyh/utils/tool/cn/hutool/http/HttpStatus.java b/src/main/java/aiyh/utils/tool/cn/hutool/http/HttpStatus.java new file mode 100644 index 0000000..90c856c --- /dev/null +++ b/src/main/java/aiyh/utils/tool/cn/hutool/http/HttpStatus.java @@ -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.
+ * 见:RFC-7231 + */ + public static final int HTTP_TEMP_REDIRECT = 307; + + /** + * HTTP 1.1 Status-Code 308: Permanent Redirect 永久重定向
+ * 见: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; + + } +} diff --git a/src/main/java/aiyh/utils/tool/cn/hutool/http/HttpUtil.java b/src/main/java/aiyh/utils/tool/cn/hutool/http/HttpUtil.java new file mode 100755 index 0000000..51393c9 --- /dev/null +++ b/src/main/java/aiyh/utils/tool/cn/hutool/http/HttpUtil.java @@ -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("]*?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 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 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 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 paramMap, int timeout) { + return aiyh.utils.tool.cn.hutool.http.HttpRequest.post(urlString).form(paramMap).timeout(timeout).execute().body(); + } + + /** + * 发送post请求
+ * 请求体body参数支持两种类型: + * + *
+	 * 1. 标准参数,例如 a=1&b=2 这种格式
+	 * 2. Rest模式,此时body需要传入一个JSON或者XML字符串,Hutool会自动绑定其对应的Content-Type
+	 * 
+ * + * @param urlString 网址 + * @param body post表单数据 + * @return 返回数据 + */ + public static String post(String urlString, String body) { + return post(urlString, body, HttpGlobalConfig.getTimeout()); + } + + /** + * 发送post请求
+ * 请求体body参数支持两种类型: + * + *
+	 * 1. 标准参数,例如 a=1&b=2 这种格式
+	 * 2. Rest模式,此时body需要传入一个JSON或者XML字符串,Hutool会自动绑定其对应的Content-Type
+	 * 
+ * + * @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 paramMap) { + return toParams(paramMap, CharsetUtil.CHARSET_UTF_8); + } + + /** + * 将Map形式的Form表单数据转换为Url参数形式
+ * 编码键和值对 + * + * @param paramMap 表单数据 + * @param charsetName 编码 + * @return url参数 + * @deprecated 请使用 {@link #toParams(Map, Charset)} + */ + @Deprecated + public static String toParams(Map paramMap, String charsetName) { + return toParams(paramMap, CharsetUtil.charset(charsetName)); + } + + /** + * 将Map形式的Form表单数据转换为Url参数形式
+ * paramMap中如果key为空(null和"")会被忽略,如果value为null,会被做为空白符("")
+ * 会自动url编码键和值
+ * 此方法用于拼接URL中的Query部分,并不适用于POST请求中的表单 + * + *
+	 * key1=v1&key2=&key3=v3
+	 * 
+ * + * @param paramMap 表单数据 + * @param charset 编码,{@code null} 表示不encode键值对 + * @return url参数 + * @see #toParams(Map, Charset, boolean) + */ + public static String toParams(Map paramMap, Charset charset) { + return toParams(paramMap, charset, false); + } + + /** + * 将Map形式的Form表单数据转换为Url参数形式
+ * paramMap中如果key为空(null和"")会被忽略,如果value为null,会被做为空白符("")
+ * 会自动url编码键和值 + * + *
+	 * key1=v1&key2=&key3=v3
+	 * 
+ * + * @param paramMap 表单数据 + * @param charset 编码,null表示不encode键值对 + * @param isFormUrlEncoded 是否为x-www-form-urlencoded模式,此模式下空格会编码为'+' + * @return url参数 + * @since 5.7.16 + */ + public static String toParams(Map paramMap, Charset charset, boolean isFormUrlEncoded) { + return UrlQuery.of(paramMap, isFormUrlEncoded).build(charset); + } + + /** + * 对URL参数做编码,只编码键和值
+ * 提供的值可以是url附带参数,但是不能只是url + * + *

注意,此方法只能标准化整个URL,并不适合于单独编码参数值

+ * + * @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中?后的部分 + * + *

注意,此方法只能标准化整个URL,并不适合于单独编码参数值

+ * + * @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 decodeParamMap(String paramsStr, Charset charset) { + final Map 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> 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> 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> 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> decodeParams(String paramsStr, Charset charset, boolean isFormUrlEncoded) { + final Map queryMap = + UrlQuery.of(paramsStr, charset, true, isFormUrlEncoded).getQueryMap(); + if (MapUtil.isEmpty(queryMap)) { + return MapUtil.empty(); + } + + final Map> params = new LinkedHashMap<>(); + queryMap.forEach((key, value) -> { + final List values = params.computeIfAbsent(StrUtil.str(key), k -> new ArrayList<>(1)); + // 一般是一个参数 + values.add(StrUtil.str(value)); + }); + return params; + } + + /** + * 将表单数据加到URL中(用于GET表单提交)
+ * 表单的键值对会被url编码,但是url中原参数不会被编码 + * + * @param url URL + * @param form 表单数据 + * @param charset 编码 + * @param isEncodeParams 是否对键和值做转义处理 + * @return 合成后的URL + */ + public static String urlWithForm(String url, Map 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连接的头信息中获得字符集
+ * 从ContentType中获取 + * + * @param conn HTTP连接对象 + * @return 字符集 + */ + public static String getCharset(HttpURLConnection conn) { + if (conn == null) { + return null; + } + return getCharset(conn.getContentType()); + } + + /** + * 从Http连接的头信息中获得字符集
+ * 从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); + } + + /** + * 从流中读取内容
+ * 首先尝试使用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); + } + + /** + * 从流中读取内容
+ * 首先尝试使用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类型,支持的类型有: + * + *
+	 * 1. application/json
+	 * 1. application/xml
+	 * 
+ * + * @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); + } + + /** + * 构建简单的账号秘密验证信息,构建后类似于: + *
+	 *     Basic YWxhZGRpbjpvcGVuc2VzYW1l
+	 * 
+ * + * @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); + } +} diff --git a/src/main/java/aiyh/utils/tool/cn/hutool/http/Method.java b/src/main/java/aiyh/utils/tool/cn/hutool/http/Method.java new file mode 100644 index 0000000..2ef3498 --- /dev/null +++ b/src/main/java/aiyh/utils/tool/cn/hutool/http/Method.java @@ -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 +} diff --git a/src/main/java/aiyh/utils/tool/cn/hutool/http/MultipartOutputStream.java b/src/main/java/aiyh/utils/tool/cn/hutool/http/MultipartOutputStream.java new file mode 100644 index 0000000..a043513 --- /dev/null +++ b/src/main/java/aiyh/utils/tool/cn/hutool/http/MultipartOutputStream.java @@ -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输出流封装
+ * 遵循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表单的数据项
+ *
+	 *     --分隔符(boundary)[换行]
+	 *     Content-Disposition: form-data; name="参数名"[换行]
+	 *     [换行]
+	 *     参数值[换行]
+	 * 
+ *

+ * 或者: + * + *

+	 *     --分隔符(boundary)[换行]
+	 *     Content-Disposition: form-data; name="表单名"; filename="文件名"[换行]
+	 *     Content-Type: MIME类型[换行]
+	 *     [换行]
+	 *     文件的二进制内容[换行]
+	 * 
+ * + * @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开始,写出:
+ *
+	 *     --分隔符(boundary)[换行]
+	 * 
+ */ + private void beginPart() { + // --分隔符(boundary)[换行] + write("--", boundary, StrUtil.CRLF); + } + + /** + * 写出对象 + * + * @param objs 写出的对象(转换为字符串) + */ + private void write(Object... objs) { + IoUtil.write(this, this.charset, false, objs); + } +} diff --git a/src/main/java/aiyh/utils/tool/cn/hutool/http/Status.java b/src/main/java/aiyh/utils/tool/cn/hutool/http/Status.java new file mode 100644 index 0000000..6ea34fd --- /dev/null +++ b/src/main/java/aiyh/utils/tool/cn/hutool/http/Status.java @@ -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; +} diff --git a/src/main/java/aiyh/utils/tool/cn/hutool/http/body/BytesBody.java b/src/main/java/aiyh/utils/tool/cn/hutool/http/body/BytesBody.java new file mode 100644 index 0000000..11643af --- /dev/null +++ b/src/main/java/aiyh/utils/tool/cn/hutool/http/body/BytesBody.java @@ -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); + } +} diff --git a/src/main/java/aiyh/utils/tool/cn/hutool/http/body/FormUrlEncodedBody.java b/src/main/java/aiyh/utils/tool/cn/hutool/http/body/FormUrlEncodedBody.java new file mode 100644 index 0000000..087b93f --- /dev/null +++ b/src/main/java/aiyh/utils/tool/cn/hutool/http/body/FormUrlEncodedBody.java @@ -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 form, Charset charset) { + return new FormUrlEncodedBody(form, charset); + } + + /** + * 构造 + * + * @param form 表单 + * @param charset 编码 + */ + public FormUrlEncodedBody(Map form, Charset charset) { + super(StrUtil.bytes(UrlQuery.of(form, true).build(charset), charset)); + } + +} diff --git a/src/main/java/aiyh/utils/tool/cn/hutool/http/body/MultipartBody.java b/src/main/java/aiyh/utils/tool/cn/hutool/http/body/MultipartBody.java new file mode 100644 index 0000000..a03942d --- /dev/null +++ b/src/main/java/aiyh/utils/tool/cn/hutool/http/body/MultipartBody.java @@ -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数据的请求体封装
+ * 遵循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 form; + /** + * 编码 + */ + private final Charset charset; + /** + * 边界 + */ + private final String boundary = HttpGlobalConfig.getBoundary(); + + /** + * 根据已有表单内容,构建MultipartBody + * + * @param form 表单 + * @param charset 编码 + * @return MultipartBody + */ + public static MultipartBody create(Map 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 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); + } +} diff --git a/src/main/java/aiyh/utils/tool/cn/hutool/http/body/RequestBody.java b/src/main/java/aiyh/utils/tool/cn/hutool/http/body/RequestBody.java new file mode 100644 index 0000000..a3a9709 --- /dev/null +++ b/src/main/java/aiyh/utils/tool/cn/hutool/http/body/RequestBody.java @@ -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); + } + } +} diff --git a/src/main/java/aiyh/utils/tool/cn/hutool/http/body/package-info.java b/src/main/java/aiyh/utils/tool/cn/hutool/http/body/package-info.java new file mode 100644 index 0000000..92f47e0 --- /dev/null +++ b/src/main/java/aiyh/utils/tool/cn/hutool/http/body/package-info.java @@ -0,0 +1,6 @@ +/** + * 请求体封装实现 + * + * @author looly + */ +package aiyh.utils.tool.cn.hutool.http.body; \ No newline at end of file diff --git a/src/main/java/aiyh/utils/tool/cn/hutool/http/cookie/GlobalCookieManager.java b/src/main/java/aiyh/utils/tool/cn/hutool/http/cookie/GlobalCookieManager.java new file mode 100644 index 0000000..f0e0c9a --- /dev/null +++ b/src/main/java/aiyh/utils/tool/cn/hutool/http/cookie/GlobalCookieManager.java @@ -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 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> 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()); + } +} diff --git a/src/main/java/aiyh/utils/tool/cn/hutool/http/cookie/ThreadLocalCookieStore.java b/src/main/java/aiyh/utils/tool/cn/hutool/http/cookie/ThreadLocalCookieStore.java new file mode 100644 index 0000000..79a3af7 --- /dev/null +++ b/src/main/java/aiyh/utils/tool/cn/hutool/http/cookie/ThreadLocalCookieStore.java @@ -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覆盖
+ *

+ * 见:https://stackoverflow.com/questions/16305486/cookiemanager-for-multiple-threads + * + * @author looly + * @since 4.1.18 + */ +public class ThreadLocalCookieStore implements CookieStore { + + private final static ThreadLocal STORES = new ThreadLocal() { + @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 get(URI uri) { + return getCookieStore().get(uri); + } + + @Override + public List getCookies() { + return getCookieStore().getCookies(); + } + + @Override + public List getURIs() { + return getCookieStore().getURIs(); + } + + @Override + public boolean remove(URI uri, HttpCookie cookie) { + return getCookieStore().remove(uri, cookie); + } + + @Override + public boolean removeAll() { + return getCookieStore().removeAll(); + } +} diff --git a/src/main/java/aiyh/utils/tool/cn/hutool/http/cookie/package-info.java b/src/main/java/aiyh/utils/tool/cn/hutool/http/cookie/package-info.java new file mode 100644 index 0000000..38eaed5 --- /dev/null +++ b/src/main/java/aiyh/utils/tool/cn/hutool/http/cookie/package-info.java @@ -0,0 +1,6 @@ +/** + * 自定义Cookie + * + * @author looly + */ +package aiyh.utils.tool.cn.hutool.http.cookie; \ No newline at end of file diff --git a/src/main/java/aiyh/utils/tool/cn/hutool/http/package-info.java b/src/main/java/aiyh/utils/tool/cn/hutool/http/package-info.java new file mode 100644 index 0000000..7140c78 --- /dev/null +++ b/src/main/java/aiyh/utils/tool/cn/hutool/http/package-info.java @@ -0,0 +1,6 @@ +/** + * Hutool-http针对JDK的HttpUrlConnection做一层封装,简化了HTTPS请求、文件上传、Cookie记忆等操作,使Http请求变得无比简单。 + * + * @author looly + */ +package aiyh.utils.tool.cn.hutool.http; \ No newline at end of file diff --git a/src/main/java/aiyh/utils/tool/cn/hutool/http/server/HttpServerBase.java b/src/main/java/aiyh/utils/tool/cn/hutool/http/server/HttpServerBase.java new file mode 100644 index 0000000..b5cd882 --- /dev/null +++ b/src/main/java/aiyh/utils/tool/cn/hutool/http/server/HttpServerBase.java @@ -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(); + } +} diff --git a/src/main/java/aiyh/utils/tool/cn/hutool/http/server/HttpServerRequest.java b/src/main/java/aiyh/utils/tool/cn/hutool/http/server/HttpServerRequest.java new file mode 100644 index 0000000..4f262cf --- /dev/null +++ b/src/main/java/aiyh/utils/tool/cn/hutool/http/server/HttpServerRequest.java @@ -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 cookieCache; + private ListValueMap 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,获取规则如下: + * + *

+	 *     1、从Content-Type头中获取编码,类似于:text/html;charset=utf-8
+	 * 
+ * + * @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 getCookies() { + return getCookieMap().values(); + } + + /** + * 获得Cookie信息Map,键为Cookie名,值为HttpCookie对象 + * + * @return Cookie信息Map + */ + public Map 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等任意内容
+ * 使用{@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 getParams(String name) { + return getParams().get(name); + } + + /** + * 获取参数Map + * + * @return 参数map + */ + public ListValueMap 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 + * + *

+ * 默认检测的Header: + * + *

+	 * 1、X-Forwarded-For
+	 * 2、X-Real-IP
+	 * 3、Proxy-Client-IP
+	 * 4、WL-Proxy-Client-IP
+	 * 
+ * + *

+ * otherHeaderNames参数用于自定义检测的Header
+ * 需要注意的是,使用此方法获取的客户IP地址必须在Http服务器(例如Nginx)中配置头信息,否则容易造成IP伪造。 + *

+ * + * @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 + * + *

+ * headerNames参数用于自定义检测的Header
+ * 需要注意的是,使用此方法获取的客户IP地址必须在Http服务器(例如Nginx)中配置头信息,否则容易造成IP伪造。 + *

+ * + * @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 表单内容
+ * 包括文件和普通表单数据
+ * 在同一次请求中,此方法只能被执行一次! + * + * @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; + } +} diff --git a/src/main/java/aiyh/utils/tool/cn/hutool/http/server/HttpServerResponse.java b/src/main/java/aiyh/utils/tool/cn/hutool/http/server/HttpServerResponse.java new file mode 100644 index 0000000..eb30108 --- /dev/null +++ b/src/main/java/aiyh/utils/tool/cn/hutool/http/server/HttpServerResponse.java @@ -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 value) { + getHeaders().put(header, value); + return this; + } + + /** + * 设置所有响应头,如果已经存在,则覆盖 + * + * @param headers 响应头map + * @return this + */ + public HttpServerResponse setHeaders(Map> headers) { + getHeaders().putAll(headers); + return this; + } + + /** + * 设置Content-Type头,类似于:text/html;charset=utf-8
+ * 如果用户传入的信息无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); + } +} diff --git a/src/main/java/aiyh/utils/tool/cn/hutool/http/server/SimpleServer.java b/src/main/java/aiyh/utils/tool/cn/hutool/http/server/SimpleServer.java new file mode 100644 index 0000000..7c00924 --- /dev/null +++ b/src/main/java/aiyh/utils/tool/cn/hutool/http/server/SimpleServer.java @@ -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 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<>(); + } + + /** + * 增加请求过滤器,此过滤器对所有请求有效
+ * 此方法需在以下方法前之前调用: + * + *
    + *
  • {@link #setRoot(File)}
  • + *
  • {@link #setRoot(String)}
  • + *
  • {@link #createContext(String, HttpHandler)}
  • + *
  • {@link #addHandler(String, HttpHandler)}
  • + *
  • {@link #addAction(String, Action)}
  • + *
+ * + * @param filter {@link Filter} 请求过滤器 + * @return this + * @since 5.5.7 + */ + public SimpleServer addFilter(Filter filter) { + this.filters.add(filter); + return this; + } + + /** + * 增加请求过滤器,此过滤器对所有请求有效
+ * 此方法需在以下方法前之前调用: + * + *
    + *
  • {@link #setRoot(File)}
  • + *
  • {@link #setRoot(String)}
  • + *
  • {@link #createContext(String, HttpHandler)}
  • + *
  • {@link #addHandler(String, HttpHandler)}
  • + *
  • {@link #addAction(String, Action)}
  • + *
+ * + * @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(); + } +} diff --git a/src/main/java/aiyh/utils/tool/cn/hutool/http/server/action/Action.java b/src/main/java/aiyh/utils/tool/cn/hutool/http/server/action/Action.java new file mode 100644 index 0000000..749a4fb --- /dev/null +++ b/src/main/java/aiyh/utils/tool/cn/hutool/http/server/action/Action.java @@ -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; + +/** + * 请求处理接口
+ * 当用户请求某个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; +} diff --git a/src/main/java/aiyh/utils/tool/cn/hutool/http/server/action/RootAction.java b/src/main/java/aiyh/utils/tool/cn/hutool/http/server/action/RootAction.java new file mode 100644 index 0000000..3731d3f --- /dev/null +++ b/src/main/java/aiyh/utils/tool/cn/hutool/http/server/action/RootAction.java @@ -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 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 !"); + } +} diff --git a/src/main/java/aiyh/utils/tool/cn/hutool/http/server/action/package-info.java b/src/main/java/aiyh/utils/tool/cn/hutool/http/server/action/package-info.java new file mode 100644 index 0000000..ebb51f2 --- /dev/null +++ b/src/main/java/aiyh/utils/tool/cn/hutool/http/server/action/package-info.java @@ -0,0 +1,6 @@ +/** + * {@link com.sun.net.httpserver.HttpServer} 封装 + * + * @author looly + */ +package aiyh.utils.tool.cn.hutool.http.server.action; \ No newline at end of file diff --git a/src/main/java/aiyh/utils/tool/cn/hutool/http/server/filter/HttpFilter.java b/src/main/java/aiyh/utils/tool/cn/hutool/http/server/filter/HttpFilter.java new file mode 100644 index 0000000..9c291fc --- /dev/null +++ b/src/main/java/aiyh/utils/tool/cn/hutool/http/server/filter/HttpFilter.java @@ -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; +} diff --git a/src/main/java/aiyh/utils/tool/cn/hutool/http/server/filter/SimpleFilter.java b/src/main/java/aiyh/utils/tool/cn/hutool/http/server/filter/SimpleFilter.java new file mode 100644 index 0000000..0ab9e7f --- /dev/null +++ b/src/main/java/aiyh/utils/tool/cn/hutool/http/server/filter/SimpleFilter.java @@ -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"; + } +} diff --git a/src/main/java/aiyh/utils/tool/cn/hutool/http/server/filter/package-info.java b/src/main/java/aiyh/utils/tool/cn/hutool/http/server/filter/package-info.java new file mode 100644 index 0000000..91fdbb8 --- /dev/null +++ b/src/main/java/aiyh/utils/tool/cn/hutool/http/server/filter/package-info.java @@ -0,0 +1,4 @@ +/** + * {@link com.sun.net.httpserver.Filter} 实现包装 + */ +package aiyh.utils.tool.cn.hutool.http.server.filter; \ No newline at end of file diff --git a/src/main/java/aiyh/utils/tool/cn/hutool/http/server/handler/ActionHandler.java b/src/main/java/aiyh/utils/tool/cn/hutool/http/server/handler/ActionHandler.java new file mode 100644 index 0000000..17c0c6f --- /dev/null +++ b/src/main/java/aiyh/utils/tool/cn/hutool/http/server/handler/ActionHandler.java @@ -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(); + } +} diff --git a/src/main/java/aiyh/utils/tool/cn/hutool/http/server/handler/package-info.java b/src/main/java/aiyh/utils/tool/cn/hutool/http/server/handler/package-info.java new file mode 100644 index 0000000..c4113d6 --- /dev/null +++ b/src/main/java/aiyh/utils/tool/cn/hutool/http/server/handler/package-info.java @@ -0,0 +1,4 @@ +/** + * {@link com.sun.net.httpserver.HttpHandler} 实现包装 + */ +package aiyh.utils.tool.cn.hutool.http.server.handler; \ No newline at end of file diff --git a/src/main/java/aiyh/utils/tool/cn/hutool/http/server/package-info.java b/src/main/java/aiyh/utils/tool/cn/hutool/http/server/package-info.java new file mode 100644 index 0000000..47d979f --- /dev/null +++ b/src/main/java/aiyh/utils/tool/cn/hutool/http/server/package-info.java @@ -0,0 +1,6 @@ +/** + * Http服务器封装 + * + * @author looly + */ +package aiyh.utils.tool.cn.hutool.http.server; \ No newline at end of file diff --git a/src/main/java/aiyh/utils/tool/cn/hutool/http/ssl/AndroidSupportSSLFactory.java b/src/main/java/aiyh/utils/tool/cn/hutool/http/ssl/AndroidSupportSSLFactory.java new file mode 100644 index 0000000..ceb0805 --- /dev/null +++ b/src/main/java/aiyh/utils/tool/cn/hutool/http/ssl/AndroidSupportSSLFactory.java @@ -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连接
+ * 在测试HttpUrlConnection的时候,发现一部分手机无法连接[GithubPage] + * + *

+ * 最后发现原来是某些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); + } + +} diff --git a/src/main/java/aiyh/utils/tool/cn/hutool/http/ssl/CustomProtocolsSSLFactory.java b/src/main/java/aiyh/utils/tool/cn/hutool/http/ssl/CustomProtocolsSSLFactory.java new file mode 100644 index 0000000..22de69b --- /dev/null +++ b/src/main/java/aiyh/utils/tool/cn/hutool/http/ssl/CustomProtocolsSSLFactory.java @@ -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); + } + } + +} diff --git a/src/main/java/aiyh/utils/tool/cn/hutool/http/ssl/DefaultSSLFactory.java b/src/main/java/aiyh/utils/tool/cn/hutool/http/ssl/DefaultSSLFactory.java new file mode 100644 index 0000000..45c5d95 --- /dev/null +++ b/src/main/java/aiyh/utils/tool/cn/hutool/http/ssl/DefaultSSLFactory.java @@ -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() { + } + +} diff --git a/src/main/java/aiyh/utils/tool/cn/hutool/http/ssl/DefaultSSLInfo.java b/src/main/java/aiyh/utils/tool/cn/hutool/http/ssl/DefaultSSLInfo.java new file mode 100644 index 0000000..fa4116a --- /dev/null +++ b/src/main/java/aiyh/utils/tool/cn/hutool/http/ssl/DefaultSSLInfo.java @@ -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(); + } + } +} diff --git a/src/main/java/aiyh/utils/tool/cn/hutool/http/ssl/SSLSocketFactoryBuilder.java b/src/main/java/aiyh/utils/tool/cn/hutool/http/ssl/SSLSocketFactoryBuilder.java new file mode 100755 index 0000000..f4d6050 --- /dev/null +++ b/src/main/java/aiyh/utils/tool/cn/hutool/http/ssl/SSLSocketFactoryBuilder.java @@ -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(); + } +} diff --git a/src/main/java/aiyh/utils/tool/cn/hutool/http/ssl/TrustAnyHostnameVerifier.java b/src/main/java/aiyh/utils/tool/cn/hutool/http/ssl/TrustAnyHostnameVerifier.java new file mode 100644 index 0000000..e60a685 --- /dev/null +++ b/src/main/java/aiyh/utils/tool/cn/hutool/http/ssl/TrustAnyHostnameVerifier.java @@ -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 + } +} diff --git a/src/main/java/aiyh/utils/tool/cn/hutool/http/ssl/package-info.java b/src/main/java/aiyh/utils/tool/cn/hutool/http/ssl/package-info.java new file mode 100644 index 0000000..3de697d --- /dev/null +++ b/src/main/java/aiyh/utils/tool/cn/hutool/http/ssl/package-info.java @@ -0,0 +1,6 @@ +/** + * SSL封装 + * + * @author looly + */ +package aiyh.utils.tool.cn.hutool.http.ssl; \ No newline at end of file diff --git a/src/main/java/aiyh/utils/tool/cn/hutool/http/useragent/Browser.java b/src/main/java/aiyh/utils/tool/cn/hutool/http/useragent/Browser.java new file mode 100755 index 0000000..f75fc07 --- /dev/null +++ b/src/main/java/aiyh/utils/tool/cn/hutool/http/useragent/Browser.java @@ -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 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); + } +} diff --git a/src/main/java/aiyh/utils/tool/cn/hutool/http/useragent/Engine.java b/src/main/java/aiyh/utils/tool/cn/hutool/http/useragent/Engine.java new file mode 100755 index 0000000..c424852 --- /dev/null +++ b/src/main/java/aiyh/utils/tool/cn/hutool/http/useragent/Engine.java @@ -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 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); + } +} diff --git a/src/main/java/aiyh/utils/tool/cn/hutool/http/useragent/OS.java b/src/main/java/aiyh/utils/tool/cn/hutool/http/useragent/OS.java new file mode 100755 index 0000000..8e7c753 --- /dev/null +++ b/src/main/java/aiyh/utils/tool/cn/hutool/http/useragent/OS.java @@ -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 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); + } +} diff --git a/src/main/java/aiyh/utils/tool/cn/hutool/http/useragent/Platform.java b/src/main/java/aiyh/utils/tool/cn/hutool/http/useragent/Platform.java new file mode 100644 index 0000000..4aed047 --- /dev/null +++ b/src/main/java/aiyh/utils/tool/cn/hutool/http/useragent/Platform.java @@ -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 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 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 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); + } + +} diff --git a/src/main/java/aiyh/utils/tool/cn/hutool/http/useragent/UserAgent.java b/src/main/java/aiyh/utils/tool/cn/hutool/http/useragent/UserAgent.java new file mode 100644 index 0000000..6921422 --- /dev/null +++ b/src/main/java/aiyh/utils/tool/cn/hutool/http/useragent/UserAgent.java @@ -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; + } + +} diff --git a/src/main/java/aiyh/utils/tool/cn/hutool/http/useragent/UserAgentInfo.java b/src/main/java/aiyh/utils/tool/cn/hutool/http/useragent/UserAgentInfo.java new file mode 100755 index 0000000..c55e029 --- /dev/null +++ b/src/main/java/aiyh/utils/tool/cn/hutool/http/useragent/UserAgentInfo.java @@ -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; + } +} diff --git a/src/main/java/aiyh/utils/tool/cn/hutool/http/useragent/UserAgentParser.java b/src/main/java/aiyh/utils/tool/cn/hutool/http/useragent/UserAgentParser.java new file mode 100644 index 0000000..8285b9a --- /dev/null +++ b/src/main/java/aiyh/utils/tool/cn/hutool/http/useragent/UserAgentParser.java @@ -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; + } +} diff --git a/src/main/java/aiyh/utils/tool/cn/hutool/http/useragent/UserAgentUtil.java b/src/main/java/aiyh/utils/tool/cn/hutool/http/useragent/UserAgentUtil.java new file mode 100644 index 0000000..0590b4e --- /dev/null +++ b/src/main/java/aiyh/utils/tool/cn/hutool/http/useragent/UserAgentUtil.java @@ -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); + } + +} diff --git a/src/main/java/aiyh/utils/tool/cn/hutool/http/useragent/package-info.java b/src/main/java/aiyh/utils/tool/cn/hutool/http/useragent/package-info.java new file mode 100644 index 0000000..5a697db --- /dev/null +++ b/src/main/java/aiyh/utils/tool/cn/hutool/http/useragent/package-info.java @@ -0,0 +1,6 @@ +/** + * User-Agent解析 + * + * @author looly + */ +package aiyh.utils.tool.cn.hutool.http.useragent; \ No newline at end of file diff --git a/src/main/java/aiyh/utils/tool/cn/hutool/http/webservice/SoapClient.java b/src/main/java/aiyh/utils/tool/cn/hutool/http/webservice/SoapClient.java new file mode 100644 index 0000000..51b91fd --- /dev/null +++ b/src/main/java/aiyh/utils/tool/cn/hutool/http/webservice/SoapClient.java @@ -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客户端 + * + *

+ * 此对象用于构建一个SOAP消息,并通过HTTP接口发出消息内容。 + * SOAP消息本质上是一个XML文本,可以通过调用{@link #getMsgStr(boolean)} 方法获取消息体 + *

+ * 使用方法: + * + *

+ * SoapClient client = SoapClient.create(url)
+ * .setMethod(methodName, namespaceURI)
+ * .setCharset(CharsetUtil.CHARSET_GBK)
+ * .setParam(param1, "XXX");
+ *
+ * String response = client.send(true);
+ *
+ * 
+ * + * @author looly + * @since 4.5.4 + */ +public class SoapClient extends HttpBase { + + /** + * 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客户端,用于客户端复用 + * + *

+ * 重置后需调用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 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 params, boolean useMethodPrefix) { + setMethod(name); + final String prefix = useMethodPrefix ? name.getPrefix() : null; + final SOAPBodyElement methodEle = this.methodEle; + for (Entry entry : MapUtil.wrap(params)) { + setParam(methodEle, entry.getKey(), entry.getValue(), prefix); + } + + return this; + } + + /** + * 设置请求方法
+ * 方法名自动识别前缀,前缀和方法名使用“:”分隔
+ * 当识别到前缀后,自动添加xmlns属性,关联到默认的namespaceURI + * + * @param methodName 方法名 + * @return this + */ + public SoapClient setMethod(String methodName) { + return setMethod(methodName, ObjectUtil.defaultIfNull(this.namespaceURI, XMLConstants.NULL_NS_URI)); + } + + /** + * 设置请求方法
+ * 方法名自动识别前缀,前缀和方法名使用“:”分隔
+ * 当识别到前缀后,自动添加xmlns属性,关联到传入的namespaceURI + * + * @param methodName 方法名(可有前缀也可无) + * @param namespaceURI 命名空间URI + * @return this + */ + public SoapClient setMethod(String methodName, String namespaceURI) { + final List 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 params) { + return setParams(params, true); + } + + /** + * 批量设置参数 + * + * @param params 参数列表 + * @param useMethodPrefix 是否使用方法的命名空间前缀 + * @return this + * @since 4.5.6 + */ + public SoapClient setParams(Map params, boolean useMethodPrefix) { + for (Entry entry : MapUtil.wrap(params)) { + setParam(entry.getKey(), entry.getValue(), useMethodPrefix); + } + return this; + } + + /** + * 获取方法节点
+ * 用于创建子节点等操作 + * + * @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; + } + + /** + * 设置超时,单位:毫秒
+ * 超时包括: + * + *

+	 * 1. 连接超时
+	 * 2. 读取响应超时
+	 * 
+ * + * @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> 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 +} diff --git a/src/main/java/aiyh/utils/tool/cn/hutool/http/webservice/SoapProtocol.java b/src/main/java/aiyh/utils/tool/cn/hutool/http/webservice/SoapProtocol.java new file mode 100644 index 0000000..6c0cbbc --- /dev/null +++ b/src/main/java/aiyh/utils/tool/cn/hutool/http/webservice/SoapProtocol.java @@ -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; + } +} diff --git a/src/main/java/aiyh/utils/tool/cn/hutool/http/webservice/SoapRuntimeException.java b/src/main/java/aiyh/utils/tool/cn/hutool/http/webservice/SoapRuntimeException.java new file mode 100644 index 0000000..eaf7cf9 --- /dev/null +++ b/src/main/java/aiyh/utils/tool/cn/hutool/http/webservice/SoapRuntimeException.java @@ -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); + } +} diff --git a/src/main/java/aiyh/utils/tool/cn/hutool/http/webservice/SoapUtil.java b/src/main/java/aiyh/utils/tool/cn/hutool/http/webservice/SoapUtil.java new file mode 100644 index 0000000..b64b294 --- /dev/null +++ b/src/main/java/aiyh/utils/tool/cn/hutool/http/webservice/SoapUtil.java @@ -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; + } +} diff --git a/src/main/java/aiyh/utils/tool/cn/hutool/http/webservice/package-info.java b/src/main/java/aiyh/utils/tool/cn/hutool/http/webservice/package-info.java new file mode 100644 index 0000000..5fa3834 --- /dev/null +++ b/src/main/java/aiyh/utils/tool/cn/hutool/http/webservice/package-info.java @@ -0,0 +1,6 @@ +/** + * Webservice客户端封装实现 + * + * @author looly + */ +package aiyh.utils.tool.cn.hutool.http.webservice; \ No newline at end of file diff --git a/src/main/java/com/api/youhong/ai/ihgzhouji/taskele/contoller/TaskElementController.java b/src/main/java/com/api/youhong/ai/ihgzhouji/taskele/contoller/TaskElementController.java new file mode 100644 index 0000000..dbb67f5 --- /dev/null +++ b/src/main/java/com/api/youhong/ai/ihgzhouji/taskele/contoller/TaskElementController.java @@ -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; + +/** + *

任务列表元素

+ * + *

create: 2023/5/4 17:13

+ * + * @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 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!"); + } + } +} diff --git a/src/main/java/com/api/youhong/ai/ihgzhouji/taskele/entity/IhgTaskElementConfigItem.java b/src/main/java/com/api/youhong/ai/ihgzhouji/taskele/entity/IhgTaskElementConfigItem.java new file mode 100644 index 0000000..5504335 --- /dev/null +++ b/src/main/java/com/api/youhong/ai/ihgzhouji/taskele/entity/IhgTaskElementConfigItem.java @@ -0,0 +1,43 @@ +package com.api.youhong.ai.ihgzhouji.taskele.entity; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +/** + *

配置表

+ * + *

create: 2023/5/4 17:35

+ * + * @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; +} diff --git a/src/main/java/com/api/youhong/ai/ihgzhouji/taskele/mapper/TaskElementMapper.java b/src/main/java/com/api/youhong/ai/ihgzhouji/taskele/mapper/TaskElementMapper.java new file mode 100644 index 0000000..607155d --- /dev/null +++ b/src/main/java/com/api/youhong/ai/ihgzhouji/taskele/mapper/TaskElementMapper.java @@ -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; + +/** + *

+ * + *

create: 2023/5/4 17:23

+ * + * @author youHong.ai + */ +@SqlMapper +public interface TaskElementMapper { + + + /** + *

查询配置信息

+ * + * @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 selectConfig(); + + /** + *

查询配置信息

+ * + * @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); + + + /** + *

查询图片地址

+ * + * @param id docId + * @return 图片地址 + */ + @Select("select concat('/weaver/weaver.file.FileDownload?fileid=',IMAGEFILEID) from docimagefile where DOCID = #{id}") + @AssociationMethod(1) + String selectImageFileId(String id); + + /** + *

查询权限信息

+ * + * @param viewAuthoritySql 自定义权限sql + * @param user 用户 + * @param map 参数 + * @param where 条件参数 + * @return 权限信息 + */ + @Select(custom = true) + List> selectAuthority(@SqlString String viewAuthoritySql, + @ParamMapper("user") User user, + @ParamMapper("param") Map map); + + /** + *

查询任务信息

+ * + * @param customerValue 自定义sql + * @param item 参数 + * @param user 用户 + * @param where 条件参数 + * @return 任务信息 + */ + @Select(custom = true) + List> selectWorkList(@SqlString String customerValue, + @ParamMapper("param") Map item, + @ParamMapper("user") User user, + @ParamMapper("where") Map where); +} diff --git a/src/main/java/com/api/youhong/ai/ihgzhouji/taskele/mapstruct/TaskElementMapstruct.java b/src/main/java/com/api/youhong/ai/ihgzhouji/taskele/mapstruct/TaskElementMapstruct.java new file mode 100644 index 0000000..4d1045f --- /dev/null +++ b/src/main/java/com/api/youhong/ai/ihgzhouji/taskele/mapstruct/TaskElementMapstruct.java @@ -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; + +/** + *

vo、pojo映射类

+ * + *

create: 2023/5/6 11:11

+ * + * @author youHong.ai + */ +@Mapper +public interface TaskElementMapstruct { + + TaskElementMapstruct INSTANCE = Mappers.getMapper(TaskElementMapstruct.class); + + /** + *

entity转换为vo对象

+ * + * @param item 配置数据 + * @return vo对象 + */ + @Mapping(target = "size", ignore = true) + @Mapping(target = "list", ignore = true) + IhgTaskElementVo entity2Vo(IhgTaskElementConfigItem item); +} diff --git a/src/main/java/com/api/youhong/ai/ihgzhouji/taskele/service/TaskElementGetValueInterface.java b/src/main/java/com/api/youhong/ai/ihgzhouji/taskele/service/TaskElementGetValueInterface.java new file mode 100644 index 0000000..15ecdc9 --- /dev/null +++ b/src/main/java/com/api/youhong/ai/ihgzhouji/taskele/service/TaskElementGetValueInterface.java @@ -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; + +/** + *

自定义获取值接口

+ * + *

create: 2023/5/5 17:42

+ * + * @author youHong.ai + */ +public interface TaskElementGetValueInterface { + + List> getValue(IhgTaskElementConfigItem taskElementConfigItem, + Map param, User user); +} diff --git a/src/main/java/com/api/youhong/ai/ihgzhouji/taskele/service/TaskElementService.java b/src/main/java/com/api/youhong/ai/ihgzhouji/taskele/service/TaskElementService.java new file mode 100644 index 0000000..8df2385 --- /dev/null +++ b/src/main/java/com/api/youhong/ai/ihgzhouji/taskele/service/TaskElementService.java @@ -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; + +/** + *

任务列表元素service

+ * + *

create: 2023/5/4 17:17

+ * + * @author youHong.ai + */ +public class TaskElementService { + private final TaskElementMapper mapper = Util.getMapper(TaskElementMapper.class); + + public List getList(User user) { + List ihgTaskElementConfItemList = mapper.selectConfig(); + if (CollectionUtil.isEmpty(ihgTaskElementConfItemList)) { + return Collections.emptyList(); + } + Map>> taskConfigMap = new HashMap<>(); + // 设置查询基础参数 + Map 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> authorityList = mapper.selectAuthority(viewAuthoritySql, user, map); + if (CollectionUtil.isNotEmpty(authorityList)) { + taskConfigMap.put(ihgTaskElementConfItem, authorityList); + } + } + // 查询数据 + List result = new ArrayList<>(); + for (Map.Entry>> entry : taskConfigMap.entrySet()) { + IhgTaskElementConfigItem taskElementConfigItem = entry.getKey(); + List> authorityList = entry.getValue(); + List> 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> queryWorkList(IhgTaskElementConfigItem taskElementConfigItem, + List> authorityList, + User user, String whereStr, + Map whereMap) { + String dataSource = taskElementConfigItem.getDataSource(); + String customerValue = taskElementConfigItem.getCustomerValue(); + if (StrUtil.isBlank(customerValue)) { + throw new CustomerException("自定义sql或自定义接口不能为空!"); + } + List> result = new ArrayList<>(); + for (Map item : authorityList) { + if ("0".equals(dataSource)) { + // 自定义sql + customerValue += " " + whereStr; + List> list = mapper.selectWorkList(customerValue, item, user, whereMap); + if (CollectionUtil.isNotEmpty(list)) { + result.addAll(list); + } + } else { + // 自定义接口 + Map map = Util.parseCusInterfacePathParam(customerValue); + map.put("whereStr", whereStr); + if (CollectionUtil.isNotEmpty(whereMap)) { + for (Map.Entry 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> 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 Predicate distinctByKey(Function keyExtractor) { + Map seen = new ConcurrentHashMap<>(8); + return t -> seen.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null; + } + + /** + *

查询自定义的数据

+ * + * @param user 用户 + * @param params 参数 + * @return 结果 + */ + public IhgTaskElementVo getList(User user, Map params, String configId) { + Assert.notBlank(configId, "查询配置表Id为空!"); + IhgTaskElementConfigItem ihgTaskElementConfItem = mapper.selectConfig(configId); + if (Objects.isNull(ihgTaskElementConfItem)) { + return null; + } + // 设置查询基础参数 + Map 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 entry : params.entrySet()) { + Map valueMap = (Map) 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> authorityList = mapper.selectAuthority(viewAuthoritySql, user, map); + if (CollectionUtil.isEmpty(authorityList)) { + return null; + } + // 查询数据 + List> 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; + } +} diff --git a/src/main/java/com/api/youhong/ai/ihgzhouji/taskele/vo/IhgTaskElementVo.java b/src/main/java/com/api/youhong/ai/ihgzhouji/taskele/vo/IhgTaskElementVo.java new file mode 100644 index 0000000..5202fc3 --- /dev/null +++ b/src/main/java/com/api/youhong/ai/ihgzhouji/taskele/vo/IhgTaskElementVo.java @@ -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; + +/** + *

配置表

+ * + *

create: 2023/5/4 17:35

+ * + * @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> list; +} diff --git a/src/main/java/com/api/youhong/ai/yashilandai/openbill/controller/OpenTheBillController.java b/src/main/java/com/api/youhong/ai/yashilandai/openbill/controller/OpenTheBillController.java new file mode 100644 index 0000000..fb76a8f --- /dev/null +++ b/src/main/java/com/api/youhong/ai/yashilandai/openbill/controller/OpenTheBillController.java @@ -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; + +/** + *

拆单api接口

+ * + *

create: 2023/4/25 10:09

+ * + * @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(); + } + + } +} diff --git a/src/main/java/com/api/youhong/ai/yashilandai/openbill/controller/OpenThenBillController.java b/src/main/java/com/api/youhong/ai/yashilandai/openbill/controller/OpenThenBillController.java deleted file mode 100644 index 746cc72..0000000 --- a/src/main/java/com/api/youhong/ai/yashilandai/openbill/controller/OpenThenBillController.java +++ /dev/null @@ -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; - -/** - *

拆单api接口

- * - *

create: 2023/4/25 10:09

- * - * @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!"); - } - } -} diff --git a/src/main/java/com/api/youhong/ai/yashilandai/openbill/mapper/OpenTheBillMapper.java b/src/main/java/com/api/youhong/ai/yashilandai/openbill/mapper/OpenTheBillMapper.java new file mode 100644 index 0000000..c06f445 --- /dev/null +++ b/src/main/java/com/api/youhong/ai/yashilandai/openbill/mapper/OpenTheBillMapper.java @@ -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; + +/** + *

+ * + *

create: 2023/4/25 10:15

+ * + * @author youHong.ai + */ +@SqlMapper +public interface OpenTheBillMapper { + + /** + *

查询数据列表

+ * + * @param tableName 表名 + * @return 数据列表 + */ + @Select("select * from $t{tableName}") + List> selectList(@ParamMapper("tableName") String tableName); + + @Select("select * from $t{tableName} where $t{dateField} between #{startDate} and #{endDate} $t{condition}") + List> selectList(@ParamMapper("tableName") String tableName, + @ParamMapper("startDate") String startDate, + @ParamMapper("endDate") String endDate, + @ParamMapper("dateField") String dateField, + @ParamMapper("condition") String condition); + + /** + *

查询条件参数信息

+ * + * @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); + + /** + *

查询条件参数信息明细表

+ * + * @param mainId 主表id + * @return 条件参数明细表 + */ + @Select("select * from uf_ncotjcs_dt1 where mainid = #{mainId}") + @CollectionMethod(1) + List selectConditionDetail(@ParamMapper("mainId") String mainId); + + /** + *

查询workflowId 通过requestId

+ * + * @param requestId requestId + * @return workflowId + */ + @Select("select WORKFLOWID from workflow_requestbase where REQUESTID = #{requestId}") + String selectWorkflowId(@ParamMapper("requestId") String requestId); + +} diff --git a/src/main/java/com/api/youhong/ai/yashilandai/openbill/mapper/OpenThenBillMapper.java b/src/main/java/com/api/youhong/ai/yashilandai/openbill/mapper/OpenThenBillMapper.java deleted file mode 100644 index 600faf6..0000000 --- a/src/main/java/com/api/youhong/ai/yashilandai/openbill/mapper/OpenThenBillMapper.java +++ /dev/null @@ -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; - -/** - *

- * - *

create: 2023/4/25 10:15

- * - * @author youHong.ai - */ -@SqlMapper -public interface OpenThenBillMapper { - - /** - *

查询数据列表

- * - * @return 数据列表 - */ - @Select("select * from v_online") - List> selectList(); -} diff --git a/src/main/java/com/api/youhong/ai/yashilandai/openbill/pojo/ConditionDetail.java b/src/main/java/com/api/youhong/ai/yashilandai/openbill/pojo/ConditionDetail.java new file mode 100644 index 0000000..37d3ce4 --- /dev/null +++ b/src/main/java/com/api/youhong/ai/yashilandai/openbill/pojo/ConditionDetail.java @@ -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; +} \ No newline at end of file diff --git a/src/main/java/com/api/youhong/ai/yashilandai/openbill/pojo/ConditionEntity.java b/src/main/java/com/api/youhong/ai/yashilandai/openbill/pojo/ConditionEntity.java new file mode 100644 index 0000000..b0efb10 --- /dev/null +++ b/src/main/java/com/api/youhong/ai/yashilandai/openbill/pojo/ConditionEntity.java @@ -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; + +/** + *

条件参数配置表

+ * + *

create: 2023/4/25 15:07

+ * + * @author youHong.ai + */ +@Getter +@Setter +@ToString +public class ConditionEntity { + private Integer id; + @SqlDbFieldAnn("mxbjgx") + private String condition; + private List list; +} + diff --git a/src/main/java/com/api/youhong/ai/yashilandai/openbill/service/OpenTheBillService.java b/src/main/java/com/api/youhong/ai/yashilandai/openbill/service/OpenTheBillService.java new file mode 100644 index 0000000..f4720c2 --- /dev/null +++ b/src/main/java/com/api/youhong/ai/yashilandai/openbill/service/OpenTheBillService.java @@ -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; + + +/** + *

拆单service

+ * + *

create: 2023/4/25 10:12

+ * + * @author youHong.ai + */ +public class OpenTheBillService { + + private final OpenTheBillMapper mapper = Util.getMapper(OpenTheBillMapper.class); + + private final Logger log = Util.getLogger(); + + public Map getOpenBillListData(String startDate, String endDate, String orderNo) { + return getData(startDate, endDate, orderNo); + } + + + public void exportExcel(OutputStream outputStream, String startDate, String endDate, String orderNo) { + Map data = getData(startDate, endDate, orderNo); + Map head = (Map) data.get("head"); + List> body = (List>) data.get("body"); + List list = new ArrayList<>(); + List excelHeadCellList = new ArrayList<>(); + List fieldList = new ArrayList<>(); + ExcelRow excelRow = new ExcelRow(); + excelRow.setRowHeight(21F); + excelRow.setDataList(excelHeadCellList); + list.add(excelRow); + List headFieldList = (List) head.get("field"); + List headTitleList = (List) 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 item : body) { + ExcelRow excelBodyRow = new ExcelRow(); + List 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 headStyle = new HashMap<>(); + Map singularLine = new HashMap<>(); + Map 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 headStyle, + Map 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 singularLine, Map 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; + } + + /** + * 计算字符串中中文字符的数量 + * 参见 《汉字unicode编码范围》 + * + * @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 getData(String startDate, String endDate, String orderNo) { + Map config = Util.readProperties2Map("esteeLauderExcelExport", "export"); + Assert.notEmpty(config, "esteeLauderExcelExport.properties文件读取配置为空,请检查配置信息"); + List> 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 result = new HashMap<>(16); + result.put("head", head); + if (CollectionUtil.isEmpty(dataList)) { + return result; + } + calculationCondition(dataList, config); + List>> groupData = groupData(dataList, config); + List> groupDataList = new ArrayList<>(); + AtomicInteger n = new AtomicInteger(1); + for (int i = 0; i < groupData.size(); i++) { + List> 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> list : groupData) { + // calculationOpenBill(list, config); + // + // groupDataList.addAll(list); + // } + result.put("body", groupDataList); + return result; + } + + + private void calculationOpenBill(List> list, Map config) { + String orderTypeKey = Util.null2String(config.get("orderType")); + String finalOrderType = ""; + for (Map 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 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 item : list) { + item.put(Util.null2String(config.get("openBillKey")), ""); + } + } else { + // 都没过bgm,计算条件 + calculationOpenBillN(list, config); + } + } else { + // 不全部都是y或者n + for (Map item : list) { + item.put(Util.null2String(config.get("openBillKey")), "全部订单总金额达到GM审批标准,拆分后无法到达GM审批节点。"); + } + } + + } + + private void calculationOpenBillN(List> list, Map config) { + Map fristMap = list.get(0); + String orderType = Util.null2String(fristMap.get(Util.null2String(config.get("orderType")))); + Map condition = (Map) config.get("condition"); + Map conditionInfo = (Map) 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 conditionDetailList = conditionEntity.getList(); + if (CollectionUtil.isEmpty(conditionDetailList)) { + return; + } + List booleanList = new ArrayList<>(); + Map mapping = (Map) conditionInfo.get("mapping"); + Map totalMap = new HashMap<>(); + for (Map.Entry entry : mapping.entrySet()) { + String key = entry.getKey(); + double totalValue = 0.0; + for (Map 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 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 item : list) { + item.put(Util.null2String(config.get("openBillKey")), "全部订单总金额达到GM审批标准,拆分后无法到达GM审批节点。"); + } + } else { + for (Map 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> dataList, Map config) { + for (Map 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>> groupData(List> dataList, Map config) { + // 根据condition进行分组 + Map>> typeMap = + dataList.stream() + .collect(Collectors.groupingBy( + item -> String.valueOf( + item.get( + Util.null2String(config.get("conditionKey")) + ) + ) + ) + ); + // 对每个type的数据进行日期分组 + List>> result = new ArrayList<>(); + DateFormat dateFormat = new SimpleDateFormat(Util.null2String(config.get("createType"))); + for (String type : typeMap.keySet()) { + List> 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 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> groupList = new ArrayList<>(); + groupList.add(baseData); + for (int i = 1; i < typeDataList.size(); i++) { + Map 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; + } + +} diff --git a/src/main/java/com/api/youhong/ai/yashilandai/openbill/service/OpenThenBillService.java b/src/main/java/com/api/youhong/ai/yashilandai/openbill/service/OpenThenBillService.java deleted file mode 100644 index 0c3ae99..0000000 --- a/src/main/java/com/api/youhong/ai/yashilandai/openbill/service/OpenThenBillService.java +++ /dev/null @@ -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; - -/** - *

- * - *

create: 2023/4/25 10:12

- * - * @author youHong.ai - */ -public class OpenThenBillService { - - - private final OpenThenBillMapper mapper = Util.getMapper(OpenThenBillMapper.class); - - public Object getOpenBillListData() { - return null; - } - - public List> getData() { - List> dataList = mapper.selectList(); - return null; - } - -} diff --git a/src/main/java/com/api/youhong/ai/yashilandai/openbill/util/ExcelBody.java b/src/main/java/com/api/youhong/ai/yashilandai/openbill/util/ExcelBody.java new file mode 100644 index 0000000..ff0d99c --- /dev/null +++ b/src/main/java/com/api/youhong/ai/yashilandai/openbill/util/ExcelBody.java @@ -0,0 +1,18 @@ +package com.api.youhong.ai.yashilandai.openbill.util; + +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +/** + *

表内容

+ * + *

create: 2023/4/19 10:48

+ * + * @author youHong.ai + */ +@Getter +@Setter +@ToString +public class ExcelBody extends ExcelCell { +} diff --git a/src/main/java/com/api/youhong/ai/yashilandai/openbill/util/ExcelCell.java b/src/main/java/com/api/youhong/ai/yashilandai/openbill/util/ExcelCell.java new file mode 100644 index 0000000..33b5bed --- /dev/null +++ b/src/main/java/com/api/youhong/ai/yashilandai/openbill/util/ExcelCell.java @@ -0,0 +1,23 @@ +package com.api.youhong.ai.yashilandai.openbill.util; + +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +/** + *

单元格内容

+ * + *

create: 2023/4/19 10:49

+ * + * @author youHong.ai + */ +@Getter +@Setter +@ToString +public class ExcelCell { + /** 表头名称 */ + private Object value; + + /** 单元格格式 */ + private IExcelCellStyleCreator cellStyle; +} diff --git a/src/main/java/com/api/youhong/ai/yashilandai/openbill/util/ExcelHead.java b/src/main/java/com/api/youhong/ai/yashilandai/openbill/util/ExcelHead.java new file mode 100644 index 0000000..386a0dd --- /dev/null +++ b/src/main/java/com/api/youhong/ai/yashilandai/openbill/util/ExcelHead.java @@ -0,0 +1,20 @@ +package com.api.youhong.ai.yashilandai.openbill.util; + +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +/** + *

excel表头

+ * + *

create: 2023/4/19 10:46

+ * + * @author youHong.ai + */ +@Getter +@Setter +@ToString +public class ExcelHead extends ExcelCell { + + +} diff --git a/src/main/java/com/api/youhong/ai/yashilandai/openbill/util/ExcelPort.java b/src/main/java/com/api/youhong/ai/yashilandai/openbill/util/ExcelPort.java new file mode 100644 index 0000000..b4651fb --- /dev/null +++ b/src/main/java/com/api/youhong/ai/yashilandai/openbill/util/ExcelPort.java @@ -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; + +/** + *

excel导出

+ * + *

create: 2023/4/19 10:39

+ * + * @author youHong.ai + */ +public class ExcelPort { + + public static void exportFile(String sheetName, List dataList, + OutputStream outputStream, + IExcelCellStyleCreator headStyle, + IExcelCellStyleCreator bodyStyle, + Map headStyleMap, + Map singularLine, + Map 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 dataList, + IExcelCellStyleCreator headStyle, + IExcelCellStyleCreator bodyStyle, Map headStyleMap, + Map singularLine, + Map 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 sheetList, + IExcelCellStyleCreator headStyle, + IExcelCellStyleCreator bodyStyle, Map headStyleMap, + Map singularLine, + Map 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 dataList, + IExcelCellStyleCreator headStyle, + IExcelCellStyleCreator bodyStyle, + Map headStyleMap, + Map singularLine, + Map evenNumberLine) { + SXSSFSheet sheet = wb.createSheet(sheetName); + createDate(sheet, wb, dataList, headStyle, bodyStyle, headStyleMap, singularLine, evenNumberLine); + } + + private static void createDate(SXSSFSheet sheet, SXSSFWorkbook wb, List dataList, + IExcelCellStyleCreator headStyle, + IExcelCellStyleCreator bodyStyle, + Map headStyleMap, + Map singularLine, + Map 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 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); + } + + } + } + } + +} diff --git a/src/main/java/com/api/youhong/ai/yashilandai/openbill/util/ExcelRow.java b/src/main/java/com/api/youhong/ai/yashilandai/openbill/util/ExcelRow.java new file mode 100644 index 0000000..5e056d1 --- /dev/null +++ b/src/main/java/com/api/youhong/ai/yashilandai/openbill/util/ExcelRow.java @@ -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; + +/** + *

表行

+ * + *

create: 2023/4/19 10:56

+ * + * @author youHong.ai + */ +@Getter +@Setter +@ToString +public class ExcelRow { + + private Float rowHeight; + private List dataList; +} diff --git a/src/main/java/com/api/youhong/ai/yashilandai/openbill/util/ExcelSheet.java b/src/main/java/com/api/youhong/ai/yashilandai/openbill/util/ExcelSheet.java new file mode 100644 index 0000000..99a524a --- /dev/null +++ b/src/main/java/com/api/youhong/ai/yashilandai/openbill/util/ExcelSheet.java @@ -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; + +/** + *

excel表

+ * + *

create: 2023/4/19 11:14

+ * + * @author youHong.ai + */ +@Getter +@Setter +@ToString +public class ExcelSheet { + private String sheetName; + private List dataList; +} diff --git a/src/main/java/com/api/youhong/ai/yashilandai/openbill/util/IExcelCellStyleCreator.java b/src/main/java/com/api/youhong/ai/yashilandai/openbill/util/IExcelCellStyleCreator.java new file mode 100644 index 0000000..b0038e8 --- /dev/null +++ b/src/main/java/com/api/youhong/ai/yashilandai/openbill/util/IExcelCellStyleCreator.java @@ -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; + +/** + *

创建样式

+ * + *

create: 2023/4/19 13:53

+ * + * @author youHong.ai + */ +@FunctionalInterface +public interface IExcelCellStyleCreator { + + CellStyle createStyle(SXSSFWorkbook workbook, + Integer rowIndex, + Integer colIndex, + SXSSFRow row, + SXSSFCell cell, + SXSSFSheet sheet, Map style2, + Map style1); +} diff --git a/src/main/java/weaver/xiao/commons/config/service/DealWithMapping.java b/src/main/java/weaver/xiao/commons/config/service/DealWithMapping.java index 6f165eb..f025602 100644 --- a/src/main/java/weaver/xiao/commons/config/service/DealWithMapping.java +++ b/src/main/java/weaver/xiao/commons/config/service/DealWithMapping.java @@ -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)); } } diff --git a/src/main/java/weaver/youhong/ai/haripijiu/action/sapdocking/VoucherPayableNewAction.java b/src/main/java/weaver/youhong/ai/haripijiu/action/sapdocking/VoucherPayableNewAction.java index 6bc4e62..b9ef555 100644 --- a/src/main/java/weaver/youhong/ai/haripijiu/action/sapdocking/VoucherPayableNewAction.java +++ b/src/main/java/weaver/youhong/ai/haripijiu/action/sapdocking/VoucherPayableNewAction.java @@ -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) { diff --git a/src/main/java/weaver/youhong/ai/haripijiu/action/sapdocking/service/VoucherPayableService.java b/src/main/java/weaver/youhong/ai/haripijiu/action/sapdocking/service/VoucherPayableService.java index c435866..b8ed1b7 100644 --- a/src/main/java/weaver/youhong/ai/haripijiu/action/sapdocking/service/VoucherPayableService.java +++ b/src/main/java/weaver/youhong/ai/haripijiu/action/sapdocking/service/VoucherPayableService.java @@ -84,7 +84,7 @@ public class VoucherPayableService { StringBuilder voucherDetailBuilder = new StringBuilder(); List> voucherDetail = voucherData.getVoucherDetail(); for (List 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 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(); diff --git a/src/main/java/weaver/youhong/ai/intellectualproperty/action/CaElectronicSignatureAction.java b/src/main/java/weaver/youhong/ai/intellectualproperty/action/CaElectronicSignatureAction.java index fba8965..c9d1abb 100644 --- a/src/main/java/weaver/youhong/ai/intellectualproperty/action/CaElectronicSignatureAction.java +++ b/src/main/java/weaver/youhong/ai/intellectualproperty/action/CaElectronicSignatureAction.java @@ -88,12 +88,12 @@ public class CaElectronicSignatureAction extends SafeCusBaseAction { } Map 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) { diff --git a/src/main/java/weaver/youhong/ai/intellectualproperty/cusgetvalue/FileToBase64CusGetValue.java b/src/main/java/weaver/youhong/ai/intellectualproperty/cusgetvalue/FileToBase64CusGetValue.java index d9cbb55..3fd2b05 100644 --- a/src/main/java/weaver/youhong/ai/intellectualproperty/cusgetvalue/FileToBase64CusGetValue.java +++ b/src/main/java/weaver/youhong/ai/intellectualproperty/cusgetvalue/FileToBase64CusGetValue.java @@ -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) { diff --git a/src/main/resources/WEB-INF/prop/prop2map/esteeLauderExcelExport.properties b/src/main/resources/WEB-INF/prop/prop2map/esteeLauderExcelExport.properties new file mode 100644 index 0000000..05ab7ad --- /dev/null +++ b/src/main/resources/WEB-INF/prop/prop2map/esteeLauderExcelExport.properties @@ -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 + diff --git a/src/main/youhong_ai_old_src/com/api/aiyh_pcn/common_fadada/contraller/CommonFaCallbackController.java b/src/main/youhong_ai_old_src/com/api/aiyh_pcn/common_fadada/contraller/CommonFaCallbackController.java index 7068961..6f232d6 100644 --- a/src/main/youhong_ai_old_src/com/api/aiyh_pcn/common_fadada/contraller/CommonFaCallbackController.java +++ b/src/main/youhong_ai_old_src/com/api/aiyh_pcn/common_fadada/contraller/CommonFaCallbackController.java @@ -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; @@ -20,15 +23,35 @@ import java.util.Map; @Path("/common_fadada/callback") public class CommonFaCallbackController { - - @Path("/dealers/callback") - @POST - @Produces(MediaType.APPLICATION_JSON) - @Consumes(MediaType.APPLICATION_JSON) - public String dealersCallback(@RequestBody Map params) { - Map result = new HashMap<>(); - result.put("code", 200); - result.put("msg", "操作成功!"); - return JSON.toJSONString(result); - } + + private final CommonFaService service = new CommonFaService(); + private final Logger log = Util.getLogger(); + + @Path("/dealers/callback") + @POST + @Produces(MediaType.APPLICATION_JSON) + @Consumes(MediaType.APPLICATION_JSON) + public String dealersCallback(@RequestBody Map params) { + Map result = new HashMap<>(); + result.put("code", 200); + result.put("msg", "操作成功!"); + return JSON.toJSONString(result); + } + + + @Path("/submit/callback") + @POST + @Produces(MediaType.APPLICATION_JSON) + @Consumes(MediaType.APPLICATION_JSON) + public String submitCallback(@RequestBody Map params) { + Map 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); + } } diff --git a/src/main/youhong_ai_old_src/com/api/aiyh_pcn/common_fadada/service/CommonFaService.java b/src/main/youhong_ai_old_src/com/api/aiyh_pcn/common_fadada/service/CommonFaService.java index 87c7da8..1845275 100644 --- a/src/main/youhong_ai_old_src/com/api/aiyh_pcn/common_fadada/service/CommonFaService.java +++ b/src/main/youhong_ai_old_src/com/api/aiyh_pcn/common_fadada/service/CommonFaService.java @@ -3,6 +3,7 @@ package com.api.aiyh_pcn.common_fadada.service; import aiyh.utils.Util; import aiyh.utils.entity.WorkflowNodeConfig; import aiyh.utils.excention.CustomerException; +import com.alibaba.fastjson.JSON; import com.fasterxml.jackson.core.JsonProcessingException; import com.google.common.base.Strings; import org.jetbrains.annotations.Nullable; @@ -28,222 +29,234 @@ import java.util.Map; */ public class CommonFaService { - private final ActionMapper mapper = Util.getMapper(ActionMapper.class); - - public Map createContract(User user, Map params) { - List resultList = null; - try { - resultList = requestFaDaDa(user, params, new CreateContractService()); - } catch (JsonProcessingException e) { - throw new CustomerException("合同创建失败!"); - } - String requestId = String.valueOf(params.get("requestId")); - String workflowId = String.valueOf(params.get("workflowId")); - String contractNoField = String.valueOf(params.get("contractNoField")); - String billTable = Util.getMainTable(workflowId); - for (FaResultEntity result : resultList) { - writeBackContractNo(result, contractNoField, requestId, billTable); - } - return null; - } - - private void writeBackContractNo(FaResultEntity result, String contractNoField, String requestId, String billTable) { - Map entityMap = null; - try { - entityMap = result.getResponeVo().getEntityMap(); - } catch (JsonProcessingException e) { - throw new CustomerException("获取合同号失败,JSON转换错误"); - } - Map data = (Map) entityMap.get("data"); - String contractNo = String.valueOf(data.get("contractNo")); - if (result.getType() == 1) { + private final ActionMapper mapper = Util.getMapper(ActionMapper.class); + + public Map createContract(User user, Map params) { + List resultList = null; + try { + resultList = requestFaDaDa(user, params, new CreateContractService()); + } catch (JsonProcessingException e) { + throw new CustomerException("合同创建失败!"); + } + String requestId = String.valueOf(params.get("requestId")); + String workflowId = String.valueOf(params.get("workflowId")); + String contractNoField = String.valueOf(params.get("contractNoField")); + String billTable = Util.getMainTable(workflowId); + for (FaResultEntity result : resultList) { + writeBackContractNo(result, contractNoField, requestId, billTable); + } + return null; + } + + private void writeBackContractNo(FaResultEntity result, String contractNoField, String requestId, String billTable) { + Map entityMap = null; + try { + entityMap = result.getResponeVo().getEntityMap(); + } catch (JsonProcessingException e) { + throw new CustomerException("获取合同号失败,JSON转换错误"); + } + Map data = (Map) entityMap.get("data"); + String contractNo = String.valueOf(data.get("contractNo")); + if (result.getType() == 1) { // 回写主表 - boolean flag = mapper.updateContractInMainTable(billTable, contractNoField, contractNo, requestId); - if (!flag) { - throw new CustomerException("回写主表失败,更新语句执行false"); - } - return; - } + boolean flag = mapper.updateContractInMainTable(billTable, contractNoField, contractNo, requestId); + if (!flag) { + throw new CustomerException("回写主表失败,更新语句执行false"); + } + return; + } // 回写明细表 - mapper.updateContractInDetailTable(result.getDetailTable(), contractNoField, contractNo, String.valueOf(result.getDetailId())); - } - - - public Map pigeonholeContract(User user, Map params) { - List resultList = null; - try { - resultList = requestFaDaDa(user, params, new PigeonholeContractService()); - } catch (JsonProcessingException e) { - e.printStackTrace(); - } - for (FaResultEntity result : resultList) { - Map entityMap = null; - try { - entityMap = result.getResponeVo().getEntityMap(); - } catch (JsonProcessingException e) { - throw new CustomerException("合同归档失败!"); - } - String code = String.valueOf(entityMap.get("code")); - if (!"200".equals(code)) { - throw new CustomerException("合同归档失败!"); - } - } + mapper.updateContractInDetailTable(result.getDetailTable(), contractNoField, contractNo, String.valueOf(result.getDetailId())); + } + + + public Map pigeonholeContract(User user, Map params) { + List resultList = null; + try { + resultList = requestFaDaDa(user, params, new PigeonholeContractService()); + } catch (JsonProcessingException e) { + e.printStackTrace(); + } + for (FaResultEntity result : resultList) { + Map entityMap = null; + try { + entityMap = result.getResponeVo().getEntityMap(); + } catch (JsonProcessingException e) { + throw new CustomerException("合同归档失败!"); + } + String code = String.valueOf(entityMap.get("code")); + if (!"200".equals(code)) { + throw new CustomerException("合同归档失败!"); + } + } // String requestId = String.valueOf(params.get("requestId")); // Util.submitWorkflow(Integer.valueOf(requestId), 1, "流程自动提交!"); - return null; - } - - public Map downloadContract(User user, Map params) { - List resultList = null; - try { - resultList = requestFaDaDa(user, params, new DownloadContractService()); - } catch (JsonProcessingException e) { - e.printStackTrace(); - } - String requestId = String.valueOf(params.get("requestId")); - String workflowId = String.valueOf(params.get("workflowId")); - String contractField = String.valueOf(params.get("contractField")); - int userId = user.getUID(); - for (FaResultEntity result : resultList) { - Map entityMap = null; - try { - entityMap = result.getResponeVo().getEntityMap(); - } catch (JsonProcessingException e) { - throw new CustomerException("合同签署失败!"); - } - String code = String.valueOf(entityMap.get("code")); - if (!"200".equals(code)) { - throw new CustomerException("合同签署失败!"); - } + return null; + } + + public Map downloadContract(User user, Map params) { + List resultList = null; + try { + resultList = requestFaDaDa(user, params, new DownloadContractService()); + } catch (JsonProcessingException e) { + e.printStackTrace(); + } + String requestId = String.valueOf(params.get("requestId")); + String workflowId = String.valueOf(params.get("workflowId")); + String contractField = String.valueOf(params.get("contractField")); + int userId = user.getUID(); + for (FaResultEntity result : resultList) { + Map entityMap = null; + try { + entityMap = result.getResponeVo().getEntityMap(); + } catch (JsonProcessingException e) { + throw new CustomerException("合同签署失败!"); + } + String code = String.valueOf(entityMap.get("code")); + if (!"200".equals(code)) { + throw new CustomerException("合同签署失败!"); + } // 获取result中的responeVo中的imageFileId并命名为imageFileId - int imageFileId = result.getImageFileId(); + int imageFileId = result.getImageFileId(); // 调用Util中的方法获取目录id - writeBackContractFile(String.valueOf(imageFileId),result,contractField,workflowId,requestId,userId); - } - return null; - } - - private boolean writeBackContractFile(String imgFileId,FaResultEntity result,String contractField ,String workflowId,String requestId,int userId) { - if (result.getType() == 1) { + writeBackContractFile(String.valueOf(imageFileId), result, contractField, workflowId, requestId, userId); + } + return null; + } + + private boolean writeBackContractFile(String imgFileId, FaResultEntity result, String contractField, String workflowId, String requestId, int userId) { + if (result.getType() == 1) { // 回写到主表 - String docCategorys = Util.getDocCategorysByTable(workflowId, contractField,""); - String[] docCategoryArr = docCategorys.split(","); - int docId = 0; - try { - docId = Util.createDocByImageFileId(Integer.parseInt(docCategoryArr[docCategoryArr.length - 1]), Integer.parseInt(imgFileId),userId); - } catch (Exception e) { - throw new CustomerException("生成文档信息失败"); - } - String billTable = Util.getWorkflowMainTable(workflowId); - return mapper.updateContractInMainTable(billTable, contractField, String.valueOf(docId), requestId); - } - // 回写明细表 - String docCategorys = Util.getDocCategorysByTable(workflowId, contractField, result.getDetailTable()); - String[] docCategoryArr = docCategorys.split(","); - int docId = 0; - try { - docId = Util.createDocByImageFileId(Integer.parseInt(docCategoryArr[docCategoryArr.length - 1]), Integer.parseInt(imgFileId),userId); - } catch (Exception e) { - throw new CustomerException("生成文档信息失败"); - } - return mapper.updateContractInDetailTable(result.getDetailTable(), contractField, String.valueOf(docId), String.valueOf(result.getDetailId())); - } - - public Map signOneself(User user, Map params) { - List resultList = null; - try { - resultList = requestFaDaDa(user, params, new OneselfSignContractService()); - } catch (JsonProcessingException e) { - throw new CustomerException("合同签署失败!"); - } - for (FaResultEntity result : resultList) { - Map entityMap = null; - try { - entityMap = result.getResponeVo().getEntityMap(); - } catch (JsonProcessingException e) { - throw new CustomerException("合同签署失败!"); - } - String code = String.valueOf(entityMap.get("code")); - if (!"200".equals(code)) { - throw new CustomerException("合同签署失败!"); - } - } + String docCategorys = Util.getDocCategorysByTable(workflowId, contractField, ""); + String[] docCategoryArr = docCategorys.split(","); + int docId = 0; + try { + docId = Util.createDocByImageFileId(Integer.parseInt(docCategoryArr[docCategoryArr.length - 1]), Integer.parseInt(imgFileId), userId); + } catch (Exception e) { + throw new CustomerException("生成文档信息失败"); + } + String billTable = Util.getWorkflowMainTable(workflowId); + return mapper.updateContractInMainTable(billTable, contractField, String.valueOf(docId), requestId); + } + // 回写明细表 + String docCategorys = Util.getDocCategorysByTable(workflowId, contractField, result.getDetailTable()); + String[] docCategoryArr = docCategorys.split(","); + int docId = 0; + try { + docId = Util.createDocByImageFileId(Integer.parseInt(docCategoryArr[docCategoryArr.length - 1]), Integer.parseInt(imgFileId), userId); + } catch (Exception e) { + throw new CustomerException("生成文档信息失败"); + } + return mapper.updateContractInDetailTable(result.getDetailTable(), contractField, String.valueOf(docId), String.valueOf(result.getDetailId())); + } + + public Map signOneself(User user, Map params) { + List resultList = null; + try { + resultList = requestFaDaDa(user, params, new OneselfSignContractService()); + } catch (JsonProcessingException e) { + throw new CustomerException("合同签署失败!"); + } + for (FaResultEntity result : resultList) { + Map entityMap = null; + try { + entityMap = result.getResponeVo().getEntityMap(); + } catch (JsonProcessingException e) { + throw new CustomerException("合同签署失败!"); + } + String code = String.valueOf(entityMap.get("code")); + if (!"200".equals(code)) { + throw new CustomerException("合同签署失败!"); + } + } // String requestId = String.valueOf(params.get("requestId")); // Util.submitWorkflow(Integer.valueOf(requestId),1,"流程自动提交!"); - return null; - } - - public Map signContract(User user, Map params) { - List resultList = null; - try { - resultList = requestFaDaDa(user, params, new SignContractService()); - } catch (JsonProcessingException e) { - e.printStackTrace(); - } - for (FaResultEntity result : resultList) { - Map entityMap = null; - try { - entityMap = result.getResponeVo().getEntityMap(); - } catch (JsonProcessingException e) { - throw new CustomerException("合同签署失败!"); - } - String code = String.valueOf(entityMap.get("code")); - if (!"200".equals(code)) { - throw new CustomerException("合同签署失败!"); - } - } - return null; - } - - @Nullable - private List requestFaDaDa(User user, Map params, IFaIntegration utilService) throws JsonProcessingException { - String requestId = String.valueOf(params.get("requestId")); - String workflowId = String.valueOf(params.get("workflowId")); - int userId = user.getUID(); - return utilService.execute(workflowId, requestId, userId); - } - - /** - * 重新推送合同数据到d-flow - * @param user 当前用户 - * @param params 请求参数 - */ - public void resignContract(User user, Map params) { - String requestId = Util.null2DefaultStr(params.get("requestId"), ""); - String dealersConfigMark = Util.null2String(params.get("dealersConfigMark")); - String apiKey = Util.null2String(params.get("apiKey")); - if(Strings.isNullOrEmpty(requestId) || Strings.isNullOrEmpty(dealersConfigMark) || Strings.isNullOrEmpty(apiKey)) { - throw new CustomerException("请求参数不能为空!"); - } - RequestService requestService = new RequestService(); - RequestInfo req = requestService.getRequest(Integer.parseInt(requestId)); - req.getRequestManager().setUser(user); - PushContractInfoAction pushContractInfoAction = new PushContractInfoAction(); - pushContractInfoAction.setApikey(apiKey); - pushContractInfoAction.setDealersConfigMark(dealersConfigMark); - try { - String execute = pushContractInfoAction.execute(req); - if(Action.FAILURE_AND_CONTINUE.equals(execute)) { - throw new CustomerException("重新推送合同数据到d-flow失败!"); - } - }catch (Exception e){ - throw new CustomerException("重新推送合同数据到d-flow失败!",e); - } - } - - public Map getContractButton(String workflowId,String markOnly) { - String allVersion = WorkflowVersion.getVersionStringByWfid(workflowId); - WorkflowNodeConfig workflowNodeConfig = Util.selectNodeConfig(allVersion,markOnly); - Util.getLogger().info("查询到的数据:" + workflowNodeConfig); - if(workflowNodeConfig == null){ - return null; - } - String workflowType = workflowNodeConfig.getWorkflowType(); - allVersion = WorkflowVersion.getVersionStringByWfid(workflowType); - Map map = new HashMap<>(8); - map.put("nodes", workflowNodeConfig.getWorkflowNodes().split(",")); - map.put("allVersion", allVersion.split(",")); - Util.getLogger().info("最终数据:" + map); - return map; - } + return null; + } + + public Map signContract(User user, Map params) { + List resultList = null; + try { + resultList = requestFaDaDa(user, params, new SignContractService()); + } catch (JsonProcessingException e) { + e.printStackTrace(); + } + for (FaResultEntity result : resultList) { + Map entityMap = null; + try { + entityMap = result.getResponeVo().getEntityMap(); + } catch (JsonProcessingException e) { + throw new CustomerException("合同签署失败!"); + } + String code = String.valueOf(entityMap.get("code")); + if (!"200".equals(code)) { + throw new CustomerException("合同签署失败!"); + } + } + return null; + } + + @Nullable + private List requestFaDaDa(User user, Map params, IFaIntegration utilService) throws JsonProcessingException { + String requestId = String.valueOf(params.get("requestId")); + String workflowId = String.valueOf(params.get("workflowId")); + int userId = user.getUID(); + return utilService.execute(workflowId, requestId, userId); + } + + /** + * 重新推送合同数据到d-flow + * + * @param user 当前用户 + * @param params 请求参数 + */ + public void resignContract(User user, Map params) { + String requestId = Util.null2DefaultStr(params.get("requestId"), ""); + String dealersConfigMark = Util.null2String(params.get("dealersConfigMark")); + String apiKey = Util.null2String(params.get("apiKey")); + if (Strings.isNullOrEmpty(requestId) || Strings.isNullOrEmpty(dealersConfigMark) || Strings.isNullOrEmpty(apiKey)) { + throw new CustomerException("请求参数不能为空!"); + } + RequestService requestService = new RequestService(); + RequestInfo req = requestService.getRequest(Integer.parseInt(requestId)); + req.getRequestManager().setUser(user); + PushContractInfoAction pushContractInfoAction = new PushContractInfoAction(); + pushContractInfoAction.setApikey(apiKey); + pushContractInfoAction.setDealersConfigMark(dealersConfigMark); + try { + String execute = pushContractInfoAction.execute(req); + if (Action.FAILURE_AND_CONTINUE.equals(execute)) { + throw new CustomerException("重新推送合同数据到d-flow失败!"); + } + } catch (Exception e) { + throw new CustomerException("重新推送合同数据到d-flow失败!", e); + } + } + + public Map getContractButton(String workflowId, String markOnly) { + String allVersion = WorkflowVersion.getVersionStringByWfid(workflowId); + WorkflowNodeConfig workflowNodeConfig = Util.selectNodeConfig(allVersion, markOnly); + Util.getLogger().info("查询到的数据:" + workflowNodeConfig); + if (workflowNodeConfig == null) { + return null; + } + String workflowType = workflowNodeConfig.getWorkflowType(); + allVersion = WorkflowVersion.getVersionStringByWfid(workflowType); + Map map = new HashMap<>(8); + map.put("nodes", workflowNodeConfig.getWorkflowNodes().split(",")); + map.put("allVersion", allVersion.split(",")); + Util.getLogger().info("最终数据:" + map); + return map; + } + + public void submitCallback(Map params) { + String contractNo = Util.null2String(params.get("docNo")); + String requestId = mapper.selectRequestId(contractNo); + RequestService requestService = new RequestService(); + RequestInfo request = requestService.getRequest(Integer.parseInt(requestId)); + boolean b = requestService.nextNodeBySubmit(request, Integer.parseInt(requestId), 1, "auto submit for callback !"); + if (!b) { + Util.getLogger().error("回调流程自动提交失败!" + JSON.toJSONString(params)); + } + } } diff --git a/src/main/youhong_ai_old_src/com/api/supportCenter/controller/MobileController.java b/src/main/youhong_ai_old_src/com/api/supportCenter/controller/MobileController.java new file mode 100644 index 0000000..d56c181 --- /dev/null +++ b/src/main/youhong_ai_old_src/com/api/supportCenter/controller/MobileController.java @@ -0,0 +1,85 @@ +package com.api.supportCenter.controller; + +import com.alibaba.fastjson.JSON; +import com.api.supportCenter.service.Impl.SupportServiceImpl; +import com.api.supportCenter.service.SupportService; +import com.engine.common.service.HrmCommonService; +import com.engine.common.service.impl.HrmCommonServiceImpl; +import weaver.conn.RecordSet; +import weaver.general.BaseBean; +import weaver.hrm.HrmUserVarify; +import weaver.hrm.User; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.core.Context; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +@Path("/MobileController") +public class MobileController { + BaseBean baseBean=new BaseBean(); + @POST + @Path("/getCount") + public String getCount(@Context HttpServletRequest request, @Context HttpServletResponse response){ + HashMap map=new HashMap<>(); + SupportService supportService=new SupportServiceImpl(); + //45\35角色ID权限验证 + String roleid1 = request.getParameter("roleid1"); + String roleid2 = request.getParameter("roleid2"); + HashMap result=new HashMap<>(); + User user = HrmUserVarify.getUser(request, response); + HrmCommonService hrmCommonService = new HrmCommonServiceImpl(); + //获得人员的所有角色 + String roleids = hrmCommonService.getRoleIds(user.getUID()); + baseBean.writeLog("当前人名称"+user.getUsername()+"当前人员所有角色"+roleids); + //符合条件 + RecordSet rs=new RecordSet(); + + if(roleids.indexOf(roleid1)>0||roleids.indexOf(roleid2)>0 || user.getUID()==1){ + List xiafa = supportService.currWeekxiafa(String.valueOf(user.getUID())); + map.put("num1", xiafa.get(0)); + map.put("num2", xiafa.get(1)); + map.put("num3", xiafa.get(2)); + map.put("num4", xiafa.get(3)); + result.put("code",200); + result.put("message","返回成功"); + result.put("datas",map); + }else{//40 45不为的时候提示没有权限 + result.put("code",-1); + result.put("message","没有营运日历中心访问权限"); + } + + return JSON.toJSONString(result); + } + @GET + @Path("/getListBySearch") + public String getListBySearch(@Context HttpServletRequest request, @Context HttpServletResponse response) throws Exception { + SupportService supportService=new SupportServiceImpl(); + User user = HrmUserVarify.getUser(request, response); +// HrmCommonService hrmCommonService = new HrmCommonServiceImpl(); + HashMap result=new HashMap<>(); + String taskTitle = request.getParameter("TaskTitle");//任务标题 + String creator = request.getParameter("Creator");//执行人 + String stopTime = request.getParameter("StopTime");//截止时间 + String taskStatus = request.getParameter("TaskStatus");//任务状态 + String type=request.getParameter("type"); + try { + ArrayList listBySearch = supportService.getListBySearch(String.valueOf(user.getUID()), taskTitle, creator, stopTime, taskStatus,type); + (new BaseBean()).writeLog("查询输出listBySearch"+JSON.toJSONString(listBySearch)); + result.put("data",listBySearch); + result.put("code","success"); + result.put("message","访问成功"); + (new BaseBean()).writeLog("查询输出result"+JSON.toJSONString(result)); + + }catch (Exception e){ + result.put("code","faild"); + result.put("message",e.toString()); + } + return JSON.toJSONString(result); + } +} diff --git a/src/main/youhong_ai_old_src/com/api/supportCenter/controller/SupportAction.java b/src/main/youhong_ai_old_src/com/api/supportCenter/controller/SupportAction.java new file mode 100644 index 0000000..7f0a786 --- /dev/null +++ b/src/main/youhong_ai_old_src/com/api/supportCenter/controller/SupportAction.java @@ -0,0 +1,258 @@ +package com.api.supportCenter.controller; + +import com.api.supportCenter.service.Impl.SupportServiceImpl; +import com.api.supportCenter.service.SupportService; +import com.icbc.api.internal.util.internal.util.fastjson.JSON; +import net.sf.json.JSONArray; +import weaver.general.BaseBean; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.core.Context; + +@Path("/supportcenter") +public class SupportAction { + SupportService supportService=new SupportServiceImpl(); + //获取酒店任务完成前10名(根据月) + @GET + @Path("/getTopTenForMonth") + public String getTopTenForMonth(@Context HttpServletRequest request,@Context HttpServletResponse response){ + String tenBeforeByMonth = supportService.getTenBeforeByMonth(); + return tenBeforeByMonth; + } + //获取酒店任务完成前10名(根据年) + @GET + @Path("/getTopTenForYear") + public String getTopTenForYear(@Context HttpServletRequest request,@Context HttpServletResponse response){ + String tenBeforeByYear = supportService.getTenBeforeByYear(); + return tenBeforeByYear; + } + //获取酒店任务完成后10名(根据月) + @GET + @Path("/getLastTenForMonth") + public String getLastTenForMonth(@Context HttpServletRequest request, @Context HttpServletResponse response){ + String tenAfterByMonth = supportService.getTenAfterByMonth(); + return tenAfterByMonth; + } + //获取酒店任务完成后10名(根据年) + @GET + @Path("/getLastTenForYear") + public String getLastTenForYear(@Context HttpServletRequest request, @Context HttpServletResponse response){ + String tenAfterByYear = supportService.getTenAfterByYear(); + return tenAfterByYear; + } + //任务完成排名 + @GET + @Path("/getTaskElementByMonth") //√ + public Object getTaskElementByMonth(@Context HttpServletRequest request, @Context HttpServletResponse response){ + Object taskElementByMonth = supportService.getTaskElementByMonth(); + return taskElementByMonth; + } + //任务完成排名(全部) + @GET + @Path("/getTaskElementByYear") //√ + public Object getTaskElementByYear(@Context HttpServletRequest request, @Context HttpServletResponse response){ + Object taskElementByYear = supportService.getTaskElementByYear(); + return taskElementByYear; + } + //日常工作任务 + @GET + @Path("/daliyTaskBag")//√ + public String daliyTaskBag(@Context HttpServletRequest request, @Context HttpServletResponse response){ + String s = supportService.daliyTaskBag(); + return s; + } + //开业任务节点(全部) + @GET + @Path("/openTaskNode")//√ + public String openTaskNode(@Context HttpServletRequest request, @Context HttpServletResponse response){ + String s = supportService.openTaskNode(); + return s; + } + //开业筹备进度(当前酒店) + @GET + @Path("/openload") //√ + public String openload(@Context HttpServletRequest request, @Context HttpServletResponse response){ + String jdcode = request.getParameter("jdcode"); + String openload = supportService.openload(jdcode); + return openload; + } + //日程日历 + @GET + @Path("/getWorkplan") //√ + public String getWorkplan(@Context HttpServletRequest request, @Context HttpServletResponse response){ + String id = request.getParameter("id"); + String res = supportService.getworkplan(id); + return res; + } + + //延期任务量(支持中心/总经理) + @GET + @Path("/getdelayTask") //√ + public String getdelayTask(@Context HttpServletRequest request, @Context HttpServletResponse response){ + String id = request.getParameter("id"); + String res = supportService.getdelayTask(id); + return res; + } + //每日任务达成率 + @GET + @Path("/getdaliyTask") //√ + public String getdaliyTask(@Context HttpServletRequest request, @Context HttpServletResponse response){ + String id = request.getParameter("id"); + String res = supportService.getdaliyTask(id); + return res; + } + //本年任务达成率 + @GET + @Path("/getYearTask") //√ + public String getYearTask(@Context HttpServletRequest request, @Context HttpServletResponse response){ + String id = request.getParameter("id"); + String res = supportService.getYearTask(id); + return res; + } + //开业筹备延期任务量(支持中心/总经理) + @GET + @Path("/getPreDelayTask") //√ + public String getPreDelayTask(@Context HttpServletRequest request, @Context HttpServletResponse response){ + String id = request.getParameter("id"); + String res = supportService.getPreDelayTask(id); + return res; + } + //开业筹备今日任务量 + @GET + @Path("/getPreDaliyTask") //√ + public String getPreDaliyTask(@Context HttpServletRequest request, @Context HttpServletResponse response){ + String id = request.getParameter("id"); + String res = supportService.getPreDaliyTask(id); + return res; + } + //开业筹备任务达标率 + @GET + @Path("/getPreTaskRate") //√ + public String getPreTaskRate(@Context HttpServletRequest request, @Context HttpServletResponse response){ + String id = request.getParameter("id"); + String res = supportService.getPreTaskRate(id); + return res; + } + //待办任务 + @GET + @Path("/getNotStartTask") //√ + public String getNotStartTask(@Context HttpServletRequest request, @Context HttpServletResponse response){ + String id = request.getParameter("id"); + + String res = supportService.getNotStartTask(id); + return res; + } + //进行中任务 + @GET + @Path("/getOnGoingTask") //√ + public String getOnGoingTask(@Context HttpServletRequest request, @Context HttpServletResponse response){ + String id = request.getParameter("id"); + String res = supportService.getOnGoingTask(id); + return res; + } + //获取转办延期任务 + @GET + @Path("/getComplaintDelayTask") + public String getComplaintDelayTask(@Context HttpServletRequest request, @Context HttpServletResponse response){ + String id = request.getParameter("id"); + String res = supportService.getComplaintDelayTask(id); + return res; + } + //获取转办待办任务 + @GET + @Path("/getComplaintNotStartTask") + public String getComplaintNotStartTask(@Context HttpServletRequest request, @Context HttpServletResponse response){ + String id = request.getParameter("id"); + String res = supportService.getComplaintNotStartTask(id); + return res; + } + //获取转办进行中任务 + @GET + @Path("/getComplaintOnGoingTask") + public String getComplaintOnGoingTask(@Context HttpServletRequest request, @Context HttpServletResponse response){ + String id = request.getParameter("id"); + String res = supportService.getComplaintOnGoingTask(id); + return res; + } + //修改需求-逾期任务量 + @GET + @Path("/newgetTask") //√ + public String newgetdelayTask(@Context HttpServletRequest request, @Context HttpServletResponse response){ + String stau=request.getParameter("stau"); + String id = request.getParameter("id"); + String res = supportService.newgetdelayTask(stau,id); + return res; + } + @GET + @Path("/zjnewgetTask") //√ + public String zjnewgetTask(@Context HttpServletRequest request, @Context HttpServletResponse response){ + String stau=request.getParameter("stau"); + String id = request.getParameter("id"); + String res = supportService.zjnewgetdelayTask(stau,id); + return res; + } + //新日常工作任务 + @GET + @Path("/newdaliyTaskBag")//√ + public String newdaliyTaskBag(@Context HttpServletRequest request, @Context HttpServletResponse response){ + String jdcode=request.getParameter("jdcode"); + String s = supportService.newdaliyTaskBag(jdcode); + return s; + } + //本周需完成任务 + @GET + @Path("/currWeekTask")//√ + public String currWeekTask(@Context HttpServletRequest request, @Context HttpServletResponse response){ + String jdcode=request.getParameter("id"); + String s = supportService.currWeekTask(jdcode); + return s; + } + @GET + @Path("/newgetWorkplan") //√ + public String newgetWorkplan(@Context HttpServletRequest request, @Context HttpServletResponse response){ + String id = request.getParameter("id"); + String res = supportService.getworkplan(id); + return res; + } + @POST + @Path("/getnewWorkplan") //√ + public String getnewWorkplan(@Context HttpServletRequest request, @Context HttpServletResponse response) { + BaseBean baseBean=new BaseBean(); + String events = request.getParameter("events"); + String currentDate = request.getParameter("currentDate"); + JSONArray list= JSONArray.fromObject(events); + String res = supportService.getnewWorkplan(list,currentDate); +// baseBean.writeLog("=================================>"+list.toString()); + return JSON.toJSONString(res); + } + //区域酒店开业数量 + @GET + @Path("/codeky")//√ + public String codeky(@Context HttpServletRequest request, @Context HttpServletResponse response){ + String region=request.getParameter("region"); + String s = supportService.codeky(region); + return s; + } + //空缺总经理 + @GET + @Path("/emptyMananger")//√ + public String emptyMananger(@Context HttpServletRequest request, @Context HttpServletResponse response){ + String s = supportService.emptyMananger(); + return s; + } + //进行中任务 + @GET + @Path("/zhichiOnGoing") //√ + public String zhichiOnGoing(@Context HttpServletRequest request, @Context HttpServletResponse response){ + String res = supportService.zhichiOnGoing(); + return res; + } + + + + +} diff --git a/src/main/youhong_ai_old_src/com/api/supportCenter/pojo/ChartBean.java b/src/main/youhong_ai_old_src/com/api/supportCenter/pojo/ChartBean.java new file mode 100644 index 0000000..9e14506 --- /dev/null +++ b/src/main/youhong_ai_old_src/com/api/supportCenter/pojo/ChartBean.java @@ -0,0 +1,71 @@ +package com.api.supportCenter.pojo; + +import org.jetbrains.annotations.NotNull; + +public class ChartBean implements Comparable{ + public String num; + public String childnum; + public String jdname; + public String jdcode; + public String comrate; + + @Override + public String toString() { + return "ChartBean{" + + "num='" + num + '\'' + + ", childnum='" + childnum + '\'' + + ", jdname='" + jdname + '\'' + + ", jdcode='" + jdcode + '\'' + + ", comrate='" + comrate + '\'' + + '}'; + } + + public String getNum() { + return num; + } + + public void setNum(String num) { + this.num = num; + } + + public String getChildnum() { + return childnum; + } + + public void setChildnum(String childnum) { + this.childnum = childnum; + } + + public String getJdname() { + return jdname; + } + + public void setJdname(String jdname) { + this.jdname = jdname; + } + + public String getJdcode() { + return jdcode; + } + + public void setJdcode(String jdcode) { + this.jdcode = jdcode; + } + + public String getComrate() { + return comrate; + } + + public void setComrate(String comrate) { + this.comrate = comrate; + } + + public ChartBean() { + } + + @Override + public int compareTo(@NotNull ChartBean o) { + return Double.compare(Double.parseDouble(this.getComrate()),Double.parseDouble(o.getComrate())); + } + +} diff --git a/src/main/youhong_ai_old_src/com/api/supportCenter/pojo/MobileEvents.java b/src/main/youhong_ai_old_src/com/api/supportCenter/pojo/MobileEvents.java new file mode 100644 index 0000000..26cd58b --- /dev/null +++ b/src/main/youhong_ai_old_src/com/api/supportCenter/pojo/MobileEvents.java @@ -0,0 +1,247 @@ +package com.api.supportCenter.pojo; + +public class MobileEvents { + String remindTimeBeforeEnd; + String remindTimesBeforeStart; + String address; + String color; + String remindDateBeforeEnd; + String workPlanTypeName; + String endDate; + String remindDateBeforeStart; + String planName; + String remindBeforeEnd; + String beginDate; + String urgentLevel; + String remindTimeBeforeStart; + String remindTimesBeforeEnd; + String workplanType; + String remindBeforeStart; + String id; + String beginTime; + String endTime; + String remindTypeName; + String remindType; + + public MobileEvents() { + } + + @Override + public String toString() { + return "MobileEvents{" + + "remindTimeBeforeEnd='" + remindTimeBeforeEnd + '\'' + + ", remindTimesBeforeStart='" + remindTimesBeforeStart + '\'' + + ", address='" + address + '\'' + + ", color='" + color + '\'' + + ", remindDateBeforeEnd='" + remindDateBeforeEnd + '\'' + + ", workPlanTypeName='" + workPlanTypeName + '\'' + + ", endDate='" + endDate + '\'' + + ", remindDateBeforeStart='" + remindDateBeforeStart + '\'' + + ", planName='" + planName + '\'' + + ", remindBeforeEnd='" + remindBeforeEnd + '\'' + + ", beginDate='" + beginDate + '\'' + + ", urgentLevel='" + urgentLevel + '\'' + + ", remindTimeBeforeStart='" + remindTimeBeforeStart + '\'' + + ", remindTimesBeforeEnd='" + remindTimesBeforeEnd + '\'' + + ", workplanType='" + workplanType + '\'' + + ", remindBeforeStart='" + remindBeforeStart + '\'' + + ", id='" + id + '\'' + + ", beginTime='" + beginTime + '\'' + + ", endTime='" + endTime + '\'' + + ", remindTypeName='" + remindTypeName + '\'' + + ", remindType='" + remindType + '\'' + + '}'; + } + + public void setRemindTimeBeforeEnd(String remindTimeBeforeEnd) { + this.remindTimeBeforeEnd = remindTimeBeforeEnd; + } + + public void setRemindTimesBeforeStart(String remindTimesBeforeStart) { + this.remindTimesBeforeStart = remindTimesBeforeStart; + } + + public void setAddress(String address) { + this.address = address; + } + + public void setColor(String color) { + this.color = color; + } + + public void setRemindDateBeforeEnd(String remindDateBeforeEnd) { + this.remindDateBeforeEnd = remindDateBeforeEnd; + } + + public void setWorkPlanTypeName(String workPlanTypeName) { + this.workPlanTypeName = workPlanTypeName; + } + + public void setEndDate(String endDate) { + this.endDate = endDate; + } + + public void setRemindDateBeforeStart(String remindDateBeforeStart) { + this.remindDateBeforeStart = remindDateBeforeStart; + } + + public void setPlanName(String planName) { + this.planName = planName; + } + + public void setRemindBeforeEnd(String remindBeforeEnd) { + this.remindBeforeEnd = remindBeforeEnd; + } + + public void setBeginDate(String beginDate) { + this.beginDate = beginDate; + } + + public void setUrgentLevel(String urgentLevel) { + this.urgentLevel = urgentLevel; + } + + public void setRemindTimeBeforeStart(String remindTimeBeforeStart) { + this.remindTimeBeforeStart = remindTimeBeforeStart; + } + + public void setRemindTimesBeforeEnd(String remindTimesBeforeEnd) { + this.remindTimesBeforeEnd = remindTimesBeforeEnd; + } + + public void setWorkplanType(String workplanType) { + this.workplanType = workplanType; + } + + public void setRemindBeforeStart(String remindBeforeStart) { + this.remindBeforeStart = remindBeforeStart; + } + + public void setId(String id) { + this.id = id; + } + + public void setBeginTime(String beginTime) { + this.beginTime = beginTime; + } + + public void setEndTime(String endTime) { + this.endTime = endTime; + } + + public void setRemindTypeName(String remindTypeName) { + this.remindTypeName = remindTypeName; + } + + public void setRemindType(String remindType) { + this.remindType = remindType; + } + + public String getRemindTimeBeforeEnd() { + return remindTimeBeforeEnd; + } + + public String getRemindTimesBeforeStart() { + return remindTimesBeforeStart; + } + + public String getAddress() { + return address; + } + + public String getColor() { + return color; + } + + public String getRemindDateBeforeEnd() { + return remindDateBeforeEnd; + } + + public String getWorkPlanTypeName() { + return workPlanTypeName; + } + + public String getEndDate() { + return endDate; + } + + public String getRemindDateBeforeStart() { + return remindDateBeforeStart; + } + + public String getPlanName() { + return planName; + } + + public String getRemindBeforeEnd() { + return remindBeforeEnd; + } + + public String getBeginDate() { + return beginDate; + } + + public String getUrgentLevel() { + return urgentLevel; + } + + public String getRemindTimeBeforeStart() { + return remindTimeBeforeStart; + } + + public String getRemindTimesBeforeEnd() { + return remindTimesBeforeEnd; + } + + public String getWorkplanType() { + return workplanType; + } + + public String getRemindBeforeStart() { + return remindBeforeStart; + } + + public String getId() { + return id; + } + + public String getBeginTime() { + return beginTime; + } + + public String getEndTime() { + return endTime; + } + + public String getRemindTypeName() { + return remindTypeName; + } + + public String getRemindType() { + return remindType; + } + + public MobileEvents(String remindTimeBeforeEnd, String remindTimesBeforeStart, String address, String color, String remindDateBeforeEnd, String workPlanTypeName, String endDate, String remindDateBeforeStart, String planName, String remindBeforeEnd, String beginDate, String urgentLevel, String remindTimeBeforeStart, String remindTimesBeforeEnd, String workplanType, String remindBeforeStart, String id, String beginTime, String endTime, String remindTypeName, String remindType) { + this.remindTimeBeforeEnd = remindTimeBeforeEnd; + this.remindTimesBeforeStart = remindTimesBeforeStart; + this.address = address; + this.color = color; + this.remindDateBeforeEnd = remindDateBeforeEnd; + this.workPlanTypeName = workPlanTypeName; + this.endDate = endDate; + this.remindDateBeforeStart = remindDateBeforeStart; + this.planName = planName; + this.remindBeforeEnd = remindBeforeEnd; + this.beginDate = beginDate; + this.urgentLevel = urgentLevel; + this.remindTimeBeforeStart = remindTimeBeforeStart; + this.remindTimesBeforeEnd = remindTimesBeforeEnd; + this.workplanType = workplanType; + this.remindBeforeStart = remindBeforeStart; + this.id = id; + this.beginTime = beginTime; + this.endTime = endTime; + this.remindTypeName = remindTypeName; + this.remindType = remindType; + } +} diff --git a/src/main/youhong_ai_old_src/com/api/supportCenter/service/Impl/SupportServiceImpl.java b/src/main/youhong_ai_old_src/com/api/supportCenter/service/Impl/SupportServiceImpl.java new file mode 100644 index 0000000..da4749c --- /dev/null +++ b/src/main/youhong_ai_old_src/com/api/supportCenter/service/Impl/SupportServiceImpl.java @@ -0,0 +1,1671 @@ +package com.api.supportCenter.service.Impl; + +import com.api.supportCenter.pojo.ChartBean; +import com.api.supportCenter.service.SupportService; +import com.icbc.api.internal.util.internal.util.fastjson.JSON; +import com.icbc.api.internal.util.internal.util.fastjson.JSONObject; +import com.weaver.general.BaseBean; +import net.sf.json.JSONArray; +import org.apache.commons.lang3.StringUtils; +import weaver.conn.RecordSet; +import weaver.hrm.resource.ResourceComInfo; + +import java.text.DecimalFormat; +import java.text.SimpleDateFormat; +import java.util.*; + +public class SupportServiceImpl implements SupportService { + BaseBean baseBean = new BaseBean(); + @Override + public String getTenBeforeByMonth() { + + //全部任务 + RecordSet rs1 = new RecordSet(); + RecordSet rs2 = new RecordSet(); + //获取酒店 任务总量、名称、jdcode + boolean execute = rs1.execute("select count(r.jdcode) as num,h.hotelnamezh as jdname,r.jdcode from view_totallist as r left join uf_hotelinfo as h on r.jdcode=h.holidex where DATE_FORMAT(r.modedatacreatedate,'%Y%m')=DATE_FORMAT(CURDATE(),'%Y%m') and jdcode!='' or jdcode!=null group by r.jdcode"); + rs1.next(); + ArrayList list = new ArrayList<>(); + if(execute){//查询成功 + if (rs1.getArray().size() > 0) {//总数、酒店名称、jdcode + for (int i = 0; i < rs1.getArray().size(); i++) { + ChartBean chartBean = new ChartBean(); + String num = rs1.getString("num"); + String jdname = rs1.getString("jdname"); + String jdcode = rs1.getString("jdcode"); + chartBean.setNum(num); + chartBean.setJdcode(jdcode); + if(!"".equals("jdname") && jdname!=null){ + chartBean.setJdname(jdname); + }else{ + chartBean.setJdname("未填写"); + } + boolean execute1 = rs2.execute("select count(jdcode) as num,jdcode from view_totallist where DATE_FORMAT(modedatacreatedate,'%Y%m')=DATE_FORMAT(CURDATE(),'%Y%m') and rwzt='2' and jdcode!='' group by jdcode"); + rs2.next(); + boolean flag=true; + int rsg=rs2.getArray().size(); +// baseBean.writeLog("长度长度长度长度长度长度长度长度长度长度长度长度长度长度长度长度长度长度长度长度长度长度长度长度长度长度"+rs2.getArray().size()); + if (rs2.getArray().size() > 0&&execute1) {//已完成和jdcode + for (int j = 0; j < rs2.getArray().size(); j++) { + +// baseBean.writeLog("循环几次循环几次循环几次循环几次循环几次循环几次循环几次循环几次循环几次循环几次循环几次循环几次循环"+j); + String num1 = rs2.getString("num"); + String jdcode1 = rs2.getString("jdcode"); +// baseBean.writeLog(jdcode1+"--------------------"+ jdcode); + if (jdcode1.equals(jdcode)) {//如果同一酒店标识 + chartBean.setChildnum(num1); +// baseBean.writeLog("chartBean==================>"+chartBean.toString()); + //完成率 + Double comrate = Double.parseDouble(num1) / Double.parseDouble(num) * 100; + DecimalFormat df = new DecimalFormat("#.##"); + chartBean.setComrate(df.format(comrate)); + list.add(chartBean); + rs2.next(); + flag=false; + break; + } else { + rs2.next(); + } + } + } else {//如果r2没有值 + continue; + } + if(flag){ + chartBean.setChildnum("0"); + chartBean.setComrate("0.00"); + list.add(chartBean); + } + rs1.next(); + } + list.sort(Comparator.comparing(ChartBean::getComrate)); + } else { + return JSON.toJSONString(list); + } + }else{//对应查询sql是否成功 + return JSON.toJSONString(list); + } + return JSON.toJSONString(list); + } + +// rs1.execute("select count(r.jdcode) as num,h.hotelnamezh as jdname,r.jdcode from uf_rwwcqd as r left join uf_hotelinfo as h on r.jdcode=h.holidex WHERE YEAR(r.modedatacreatedate)=YEAR(NOW()) group by r.jdcode"); +// rs2.execute("select count(jdcode) as num,jdcode from uf_rwwcqd where rwzt='2' group by jdcode"); + @Override + public String getTenBeforeByYear() { + //全部任务 + RecordSet rs1 = new RecordSet(); + RecordSet rs2 = new RecordSet(); + //获取酒店 任务总量、名称、jdcode + boolean execute = rs1.execute("select count(r.jdcode) as num,h.hotelnamezh as jdname,r.jdcode from view_totallist as r left join uf_hotelinfo as h on r.jdcode=h.holidex where YEAR(r.modedatacreatedate)=YEAR(NOW()) and jdcode!='' or jdcode!=null group by r.jdcode"); + + rs1.next(); + ArrayList list = new ArrayList<>(); + if(execute){//查询成功 + if (rs1.getArray().size() > 0) {//总数、酒店名称、jdcode + for (int i = 0; i < rs1.getArray().size(); i++) { + ChartBean chartBean = new ChartBean(); + String num = rs1.getString("num"); + String jdname = rs1.getString("jdname"); + String jdcode = rs1.getString("jdcode"); + chartBean.setNum(num); + chartBean.setJdcode(jdcode); + if(!"".equals("jdname") && jdname!=null){ + chartBean.setJdname(jdname); + }else{ + chartBean.setJdname("未填写"); + } + boolean execute1 = rs2.execute("select count(jdcode) as num,jdcode from view_totallist where YEAR(modedatacreatedate)=YEAR(NOW()) and rwzt='2' and jdcode!='' group by jdcode"); + rs2.next(); + boolean flag=true; +// baseBean.writeLog("长度长度长度长度长度长度长度长度长度长度长度长度长度长度长度长度长度长度长度长度长度长度长度长度长度长度"+rs2.getArray().size()); + if (rs2.getArray().size() > 0&&execute1) {//已完成和jdcode + for (int j = 0; j < rs2.getArray().size(); j++) { + +// baseBean.writeLog("循环几次循环几次循环几次循环几次循环几次循环几次循环几次循环几次循环几次循环几次循环几次循环几次循环"+j); + String num1 = rs2.getString("num"); + String jdcode1 = rs2.getString("jdcode"); +// baseBean.writeLog(jdcode1+"--------------------"+ jdcode); + if (jdcode1.equals(jdcode)) {//如果同一酒店标识 + chartBean.setChildnum(num1); +// baseBean.writeLog("chartBean==================>"+chartBean.toString()); + //完成率 + Double comrate = Double.parseDouble(num1) / Double.parseDouble(num) * 100; + DecimalFormat df = new DecimalFormat("#.##"); + chartBean.setComrate(df.format(comrate)); + list.add(chartBean); + rs2.next(); + flag=false; + break; + } else { + rs2.next(); + } + } + } else {//如果r2没有值 + continue; + } + if(flag){ + chartBean.setChildnum("0"); + chartBean.setComrate("0.00"); + list.add(chartBean); + } + rs1.next(); + } + list.sort(Comparator.comparing(ChartBean::getComrate)); + } else { + return JSON.toJSONString(list); + } + }else{//对应查询sql是否成功 + return JSON.toJSONString(list); + } + return JSON.toJSONString(list); + } + + @Override + public String getTenAfterByMonth() { +// BaseBean baseBean = new BaseBean(); + //全部任务 + RecordSet rs1 = new RecordSet(); + RecordSet rs2 = new RecordSet(); + //获取酒店 任务总量、名称、jdcode + boolean execute = rs1.execute("select count(r.jdcode) as num,h.hotelnamezh as jdname,r.jdcode from view_totallist as r left join uf_hotelinfo as h on r.jdcode=h.holidex where DATE_FORMAT(r.modedatacreatedate,'%Y%m')=DATE_FORMAT(CURDATE(),'%Y%m') and jdcode!='' or jdcode!=null group by r.jdcode"); + rs1.next(); + ArrayList list = new ArrayList<>(); + if(execute){//查询成功 + if (rs1.getArray().size() > 0) {//总数、酒店名称、jdcode + for (int i = 0; i < rs1.getArray().size(); i++) { + ChartBean chartBean = new ChartBean(); + String num = rs1.getString("num"); + String jdname = rs1.getString("jdname"); + String jdcode = rs1.getString("jdcode"); + chartBean.setNum(num); + chartBean.setJdcode(jdcode); + if(!"".equals("jdname") && jdname!=null){ + chartBean.setJdname(jdname); + }else{ + chartBean.setJdname("未填写"); + } + boolean execute1 = rs2.execute("select count(jdcode) as num,jdcode from view_totallist where DATE_FORMAT(modedatacreatedate,'%Y%m')=DATE_FORMAT(CURDATE(),'%Y%m') and rwzt='2' and jdcode!='' group by jdcode"); + rs2.next(); + boolean flag=true; +// baseBean.writeLog("长度长度长度长度长度长度长度长度长度长度长度长度长度长度长度长度长度长度长度长度长度长度长度长度长度长度"+rs2.getArray().size()); + if (rs2.getArray().size() > 0&&execute1) {//已完成和jdcode + for (int j = 0; j < rs2.getArray().size(); j++) { + +// baseBean.writeLog("循环几次循环几次循环几次循环几次循环几次循环几次循环几次循环几次循环几次循环几次循环几次循环几次循环"+j); + String num1 = rs2.getString("num"); + String jdcode1 = rs2.getString("jdcode"); +// baseBean.writeLog(jdcode1+"--------------------"+ jdcode); + if (jdcode1.equals(jdcode)) {//如果同一酒店标识 + chartBean.setChildnum(num1); +// baseBean.writeLog("chartBean==================>"+chartBean.toString()); + //完成率 + Double comrate = Double.parseDouble(num1) / Double.parseDouble(num) * 100; + DecimalFormat df = new DecimalFormat("#.##"); + chartBean.setComrate(df.format(comrate)); + list.add(chartBean); + rs2.next(); + flag=false; + break; + } else { + rs2.next(); + } + } + } else {//如果r2没有值 + continue; + } + if(flag){ + chartBean.setChildnum("0"); + chartBean.setComrate("0.00"); + list.add(chartBean); + } + rs1.next(); + } + list.sort(Comparator.comparing(ChartBean::getComrate)); + } else { + return JSON.toJSONString(list); + } + }else{//对应查询sql是否成功 + return JSON.toJSONString(list); + } +// baseBean.writeLog("list=====================>"+list); + return JSON.toJSONString(list); + } + + @Override + public String getTenAfterByYear() { + //全部任务 + RecordSet rs1 = new RecordSet(); + RecordSet rs2 = new RecordSet(); + //获取酒店 任务总量、名称、jdcode + boolean execute = rs1.execute("select count(r.jdcode) as num,h.hotelnamezh as jdname,r.jdcode from view_totallist as r left join uf_hotelinfo as h on r.jdcode=h.holidex where YEAR(r.modedatacreatedate)=YEAR(NOW()) and jdcode!='' or jdcode!=null group by r.jdcode"); + + rs1.next(); + ArrayList list = new ArrayList<>(); + if(execute){//查询成功 + if (rs1.getArray().size() > 0) {//总数、酒店名称、jdcode + for (int i = 0; i < rs1.getArray().size(); i++) { + ChartBean chartBean = new ChartBean(); + String num = rs1.getString("num"); + String jdname = rs1.getString("jdname"); + String jdcode = rs1.getString("jdcode"); + chartBean.setNum(num); + chartBean.setJdcode(jdcode); + if(!"".equals("jdname") && jdname!=null){ + chartBean.setJdname(jdname); + }else{ + chartBean.setJdname("未填写"); + } + boolean execute1 = rs2.execute("select count(jdcode) as num,jdcode from view_totallist where YEAR(modedatacreatedate)=YEAR(NOW()) and rwzt='2' and jdcode!='' group by jdcode"); + rs2.next(); + boolean flag=true; +// baseBean.writeLog("长度长度长度长度长度长度长度长度长度长度长度长度长度长度长度长度长度长度长度长度长度长度长度长度长度长度"+rs2.getArray().size()); + if (rs2.getArray().size() > 0&&execute1) {//已完成和jdcode + for (int j = 0; j < rs2.getArray().size(); j++) { + +// baseBean.writeLog("循环几次循环几次循环几次循环几次循环几次循环几次循环几次循环几次循环几次循环几次循环几次循环几次循环"+j); + String num1 = rs2.getString("num"); + String jdcode1 = rs2.getString("jdcode"); +// baseBean.writeLog(jdcode1+"--------------------"+ jdcode); + if (jdcode1.equals(jdcode)) {//如果同一酒店标识 + chartBean.setChildnum(num1); +// baseBean.writeLog("chartBean==================>"+chartBean.toString()); + //完成率 + Double comrate = Double.parseDouble(num1) / Double.parseDouble(num) * 100; + DecimalFormat df = new DecimalFormat("#.##"); + chartBean.setComrate(df.format(comrate)); + list.add(chartBean); + rs2.next(); + flag=false; + break; + } else { + rs2.next(); + } + } + } else {//如果r2没有值 + continue; + } + if(flag){ + chartBean.setChildnum("0"); + chartBean.setComrate("0.00"); + list.add(chartBean); + } + rs1.next(); + } + list.sort(Comparator.comparing(ChartBean::getComrate)); + } else { + return JSON.toJSONString(list); + } + }else{//对应查询sql是否成功 + return JSON.toJSONString(list); + } + return JSON.toJSONString(list); + } + + @Override + public Object getTaskElementByMonth() {//达成元素本月数据 + //排名前10 + RecordSet rs1=new RecordSet(); + //任务完成量 + RecordSet rs2=new RecordSet(); + String sql1="select count(1) as num,h.hotelnamezh as jdname from view_totallist as r left join uf_hotelinfo as h on r.jdcode=h.holidex where rwzt='2' and DATE_FORMAT(r.modedatacreatedate,'%Y%m')=DATE_FORMAT(CURDATE(),'%Y%m') group by r.jdcode order by num desc limit 10"; + String sql2="select r.totalcount,t.succount from(select count(1) as totalcount from view_totallist where rwzt <>4 and DATE_FORMAT(modedatacreatedate,'%Y%m')=DATE_FORMAT(CURDATE(),'%Y%m')) as r,(select count(1) as succount from view_totallist where rwzt='2' and DATE_FORMAT(modedatacreatedate,'%Y%m')=DATE_FORMAT(CURDATE(),'%Y%m')) as t"; + rs1.execute(sql1); + rs2.execute(sql2); + rs1.next(); + rs2.next(); + ArrayList list=new ArrayList(); + ArrayList list2=new ArrayList(); + + if(rs1.getArray().size()>0){//如果rs1查询数组大于0说明有值 + for (int i = 0; i < rs1.getArray().size(); i++) { + Map map=new HashMap<>(); + String num = rs1.getString("num"); + String jdname = rs1.getString("jdname"); + map.put("num",num); + map.put("jdname",jdname); + list.add(map); + rs1.next();//下移 + } + } + if(rs2.getArray().size()>0){ + for (int i = 0; i < rs2.getArray().size(); i++) { + Map map2=new HashMap<>(); + String totalcount = rs2.getString("totalcount"); + String succount = rs2.getString("succount"); + map2.put("totalcount",totalcount); + map2.put("succount",succount); + list2.add(map2); + rs2.next(); + } + } + JSONArray jsonArray1= JSONArray.fromObject(list); + JSONArray jsonArray2= JSONArray.fromObject(list2); + JSONObject object = new JSONObject(); + object.put("mainInfo",jsonArray1); + object.put("topInfo",jsonArray2); + + return JSON.toJSONString(object); + } + + @Override + public Object getTaskElementByYear() {//达成元素本年数据 + //排名前10 + RecordSet rs1=new RecordSet(); + //任务完成量 + RecordSet rs2=new RecordSet(); + String sql1="select count(1) as num,h.hotelnamezh as jdname from view_totallist as r left join uf_hotelinfo as h on r.jdcode=h.holidex where rwzt='2' and YEAR(r.modedatacreatedate)=YEAR(NOW()) group by r.jdcode order by num desc limit 10"; + String sql2="select r.totalcount,t.succount from(select count(1) as totalcount from view_totallist where rwzt <>4 and YEAR(modedatacreatedate)=YEAR(NOW())) as r,(select count(1) as succount from view_totallist where rwzt='2' and YEAR(modedatacreatedate)=YEAR(NOW())) as t"; + rs1.execute(sql1); + boolean execute = rs2.execute(sql2); + rs1.next(); + rs2.next(); + ArrayList list=new ArrayList(); + ArrayList list2=new ArrayList(); + if(rs1.getArray().size()>0){//如果rs1查询数组大于0说明有值 + for (int i = 0; i < rs1.getArray().size(); i++) { + Map map=new HashMap<>(); + String num = rs1.getString("num"); + String jdname = rs1.getString("jdname"); + map.put("num",num); + map.put("jdname",jdname); + list.add(map); + rs1.next();//下移 + } + } + if(rs2.getArray().size()>0){ + for (int i = 0; i < rs2.getArray().size(); i++) { + Map map2=new HashMap<>(); + String totalcount = rs2.getString("totalcount"); + String succount = rs2.getString("succount"); + map2.put("totalcount",totalcount); + map2.put("succount",succount); + list2.add(map2); + rs2.next(); + } + } + JSONArray jsonArray1= JSONArray.fromObject(list); + JSONArray jsonArray2= JSONArray.fromObject(list2); + JSONObject object = new JSONObject(); + object.put("mainInfo",jsonArray1); + object.put("topInfo",jsonArray2); + + return JSON.toJSONString(object); + } + + @Override + public String daliyTaskBag() {//日常工作任务 √ + RecordSet rs1=new RecordSet(); + String sql="SELECT rwbt,lb FROM uf_rwqd where zq='0'"; + rs1.execute(sql); + rs1.next(); + ArrayList list = new ArrayList(); + if(rs1.getArray().size()>0){//如果有值 + for (int i = 0; i < rs1.getArray().size(); i++) { + String rwbt = rs1.getString("rwbt"); + String lb = rs1.getString("lb"); + Map map=new HashMap<>(); + map.put("rwbt",rwbt); + map.put("lb",lb); + list.add(map); + rs1.next(); + } + }else{//如果没值 + return JSON.toJSONString(list); + } + return JSON.toJSONString(list); + } + + + @Override + public String openTaskNode() {//开业任务节点 + RecordSet rs=new RecordSet(); + String sql="SELECT jdmc,kysx,zt FROM uf_txjl"; + rs.execute(sql); + rs.next(); + ArrayList list=new ArrayList(); + if(rs.getArray().size()>0){ + for (int i = 0; i < rs.getArray().size(); i++) { + Map map=new HashMap<>(); + String jdmc = rs.getString("jdmc"); + String kysx = rs.getString("kysx"); + String zt = rs.getString("zt"); + map.put("jdmc",jdmc); + map.put("kysx",kysx); + map.put("zt",zt); + list.add(map); + rs.next(); + } + }else{ + return JSON.toJSONString(list); + } + + return JSON.toJSONString(list); + } + + @Override + public String openload(String jdcode) {//开业 + RecordSet rs=new RecordSet(); + String sql="SELECT kysx,xmjl,jhksrqsjcs,jhjsrqsjcs,zt FROM uf_txjl WHERE jdcode="+"'"+jdcode+"'"; + rs.execute(sql); + rs.next(); + ArrayList list=new ArrayList(); + if(rs.getArray().size()>0){ + for (int i = 0; i < rs.getArray().size(); i++) { + Map map=new HashMap<>(); + String kysx = rs.getString("kysx"); + String xmjl = rs.getString("xmjl"); + String begintime = rs.getString("jhksrqsjcs"); + String endtime = rs.getString("jhjsrqsjcs"); + String zt = rs.getString("zt"); + map.put("kysx",kysx); + map.put("xmjl",xmjl); + map.put("begintime",begintime); + map.put("endtime",endtime); + map.put("zt",zt); + list.add(map); + rs.next(); + } + }else{ + return JSON.toJSONString(list); + } + + return JSON.toJSONString(list); + } + + //获取日程日历 + @Override + public String getworkplan(String id) { + RecordSet rs=new RecordSet(); + String sql="SELECT status,REQUESTID from workplan WHERE id="+"'"+id+"'"; + rs.execute(sql); + rs.next(); + ArrayList list=new ArrayList(); + if(rs.getArray().size()>0){ + for (int i = 0; i < rs.getArray().size(); i++) { + Map map=new HashMap<>(); + String requestid = rs.getString("REQUESTID"); + String status = rs.getString("status"); + map.put("requestid",requestid); + map.put("status",status); + list.add(map); + rs.next(); + } + }else{ + return JSON.toJSONString(list); + } + return JSON.toJSONString(list); + } + + @Override + public String getdelayTask(String id) { + RecordSet rs=new RecordSet(); + RecordSet ye=new RecordSet(); + //延期任务量 + String desql="select count(1) as c from view_EmpTask where rwzt = 3"; + //前一天数据b + //如果是总经理登录 + if(!"".equals(id) && id!=null){ + desql=desql+" and zbr="+"'"+id+"'"; + } + rs.execute(desql); + //当天数据 + String curdata=""; + //昨日数据 + String status="0"; //0/平 1/上升 2/下降 + rs.next(); + if(rs.getArray().size()>0){ + String res1 = rs.getString("c"); + curdata=res1; + rs.next(); + } +// ye.next(); +// if(ye.getArray().size()>0){ +// String res2 = ye.getString("c"); +// yesdata=res2; +// } + Map map=new HashMap(); + if(!curdata.equals("")){ +// if(Double.parseDouble(curdata)Double.parseDouble(yesdata)){ +// status="1"; +// } + map.put("count",curdata); + map.put("status","0"); + return JSON.toJSONString(map); + } + map.put("count", "0"); + map.put("status", "0"); + return JSON.toJSONString(map); + } + //每日任务达成率 + @Override + public String getdaliyTask(String id) { + RecordSet rs=new RecordSet(); + RecordSet ye=new RecordSet(); + String sql=""; + String yesql=""; + + if(!"".equals(id) && id!=null){ + sql="select a.total,b.suc from(select count(1) as total from uf_rwwcqd where rwzt='3' and zjln="+"'"+id+"'"+") as a,(select count(1) as suc from uf_rwwcqd where DATE_FORMAT(modedatacreatedate,'%Y%m%d')=CURDATE() and rwzt='2' and zjln="+"'"+id+"'"+") as b"; + yesql="select a.total,b.suc from(select count(1) as total from uf_rwwcqd where rwzt='3' and zjln="+"'"+id+"'"+") as a,(select count(1) as suc from uf_rwwcqd where DATE_FORMAT(modedatacreatedate,'%Y%m%d')=date_sub(curdate(),interval 1 day) and rwzt='2' and zjln="+"'"+id+"'"+") as b"; + }else{ + sql="select a.total,b.suc from(select count(1) as total from uf_rwwcqd where rwzt='3') as a,(select count(1) as suc from uf_rwwcqd where DATE_FORMAT(modedatacreatedate,'%Y%m%d')=CURDATE() and rwzt='2') as b"; + yesql="select a.total,b.suc from(select count(1) as total from uf_rwwcqd where rwzt='3') as a,(select count(1) as suc from uf_rwwcqd where DATE_FORMAT(modedatacreatedate,'%Y%m%d')=date_sub(curdate(),interval 1 day) and rwzt='2') as b"; + } + rs.execute(sql); + ye.execute(yesql); + //当天数据 + String curdata=""; + //昨日数据 + String yesdata=""; + String status="0"; //0/平 1/上升 2/下降 + rs.next(); + if(rs.getArray().size()>0){ + String total = rs.getString("total"); + String suc = rs.getString("suc"); + if(total.equals("0")){ + curdata="0.00"; + }else{ + Double comrate = Double.parseDouble(suc)/Double.parseDouble(total) * 100; + DecimalFormat df = new DecimalFormat("#.##"); + String format = df.format(comrate); + curdata=format; + } + } + ye.next(); + if(ye.getArray().size()>0){ + String total = ye.getString("total"); + String suc = ye.getString("suc"); + if(total.equals("0")){ + yesdata="0.00"; + }else{ + Double comrate = Double.parseDouble(suc) / Double.parseDouble(total) * 100; + DecimalFormat df = new DecimalFormat("#.##"); + String format = df.format(comrate); + yesdata=format; + } + + } + Map map=new HashMap(); + if(!curdata.equals("") && !yesdata.equals("")){ + if(Double.parseDouble(curdata)Double.parseDouble(yesdata)){ + status="1"; + } + map.put("count",curdata); + map.put("status",status); + return JSON.toJSONString(map); + } + map.put("count", "-.--"); + map.put("status", "0"); + + return JSON.toJSONString(map); + } + //本年任务达成率 + + @Override + public String getYearTask(String id) { + RecordSet rs = new RecordSet(); + RecordSet ye = new RecordSet(); + String sql = ""; + String yesql = ""; + + if (!"".equals(id) && id != null) { + sql = "select a.total,b.suc from(select count(1) as total from uf_rwwcqd where DATE_FORMAT(modedatacreatedate,'%Y')=year(now()) and zjln=" + "'" + id + "'" + ") as a,(select count(1) as suc from uf_rwwcqd where DATE_FORMAT(modedatacreatedate,'%Y')=year(now()) and rwzt='2' and zjln=" + "'" + id + "'" + ") as b"; + yesql = "select a.total,b.suc from(select count(1) as total from uf_rwwcqd where DATE_FORMAT(modedatacreatedate,'%Y')=year(now())-1) and zjln=" + "'" + id + "'" + ") as a,(select count(1) as suc from uf_rwwcqd where DATE_FORMAT(modedatacreatedate,'%Y')=year(now())-1 and rwzt='2' and zjln=" + "'" + id + "'" + ") as b"; + } else { + sql = "select a.total,b.suc from(select count(1) as total from uf_rwwcqd where DATE_FORMAT(modedatacreatedate,'%Y')=year(now()) ) as a,(select count(1) as suc from uf_rwwcqd where DATE_FORMAT(modedatacreatedate,'%Y')=year(now()) and rwzt='2') as b"; + yesql = "select a.total,b.suc from(select count(1) as total from uf_rwwcqd where DATE_FORMAT(modedatacreatedate,'%Y')=year(now())-1 ) as a,(select count(1) as suc from uf_rwwcqd where DATE_FORMAT(modedatacreatedate,'%Y')=year(now())-1 and rwzt='2') as b"; + } + rs.execute(sql); + ye.execute(yesql); + //当天数据 + String curdata = ""; + //昨日数据 + String yesdata = ""; + String status = "0"; //0/平 1/上升 2/下降 + rs.next(); + if (rs.getArray().size() > 0) { + String total = rs.getString("total"); + String suc = rs.getString("suc"); + if (total.equals("0")) { + curdata = "0.00"; + } else { + Double comrate = Double.parseDouble(suc) / Double.parseDouble(total) * 100; + DecimalFormat df = new DecimalFormat("#.##"); + String format = df.format(comrate); + curdata = format; + } + } + ye.next(); + if (ye.getArray().size() > 0) { + String total = ye.getString("total"); + String suc = ye.getString("suc"); + if (total.equals("0")) { + yesdata = "0.00"; + } else { + Double comrate = Double.parseDouble(suc) / Double.parseDouble(total) * 100; + DecimalFormat df = new DecimalFormat("#.##"); + String format = df.format(comrate); + yesdata = format; + } + + } + Map map = new HashMap(); + if (!curdata.equals("") && !yesdata.equals("")) { + if (Double.parseDouble(curdata) < Double.parseDouble(yesdata)) { + status = "2"; + } else if (Double.parseDouble(curdata) > Double.parseDouble(yesdata)) { + status = "1"; + } + map.put("count", curdata); + map.put("status", status); + return JSON.toJSONString(map); + } + map.put("count", "-.--"); + map.put("status", "0"); + + return JSON.toJSONString(map); + } + //开业筹备--------------------------------------- + //延期任务量 + @Override + public String getPreDelayTask(String id) { + RecordSet rs=new RecordSet(); + RecordSet ye=new RecordSet(); + //延期任务量 + String desql="select count(1) as c from uf_txjl WHERE jhjsrqsjcs0){ + String res1 = rs.getString("c"); + curdata=res1; + rs.next(); + } + ye.next(); + if(ye.getArray().size()>0){ + String res2 = ye.getString("c"); + yesdata=res2; + } + Map map=new HashMap(); + if(!curdata.equals("") && !yesdata.equals("")){ + if(Double.parseDouble(curdata)Double.parseDouble(yesdata)){ + status="1"; + } + map.put("count",curdata); + map.put("status",status); + return JSON.toJSONString(map); + } + map.put("count", "-"); + map.put("status", "0"); + return JSON.toJSONString(map); + } + //今日任务 + @Override + public String getPreDaliyTask(String id) { + RecordSet rs=new RecordSet(); + RecordSet ye=new RecordSet(); + //今日任务 + String desql="select count(1) as c from uf_txjl WHERE DATE_FORMAT(jhksrqsjcs,'%Y%m%d')=curdate()"; + //前一天数据 + String yesql="select count(1) as c from uf_txjl WHERE DATE_FORMAT(jhksrqsjcs,'%Y%m%d')=date_sub(curdate(),interval 1 day)"; + //如果是总经理登录 + if(!"".equals(id) && id!=null){ + desql=desql+" and pm="+"'"+id+"'"; + yesql=yesql+" and pm="+"'"+id+"'"; + } + + rs.execute(desql); + ye.execute(yesql); + //当天数据 + String curdata=""; + //昨日数据 + String yesdata=""; + String status="0"; //0/平 1/上升 2/下降 + rs.next(); + if(rs.getArray().size()>0){ + String res1 = rs.getString("c"); + curdata=res1; + rs.next(); + } + ye.next(); + if(ye.getArray().size()>0){ + String res2 = ye.getString("c"); + yesdata=res2; + } + Map map=new HashMap(); + if(!curdata.equals("") && !yesdata.equals("")){ + if(Double.parseDouble(curdata)Double.parseDouble(yesdata)){ + status="1"; + } + map.put("count",curdata); + map.put("status",status); + return JSON.toJSONString(map); + } + map.put("count", "-"); + map.put("status", "0"); + return JSON.toJSONString(map); + } + + @Override + public String getPreTaskRate(String id) { + RecordSet rs = new RecordSet(); + RecordSet ye = new RecordSet(); + String sql = ""; + String yesql = ""; + + if (!"".equals(id) && id != null) { + sql = "select a.total,b.suc from (select count(1) as total from uf_txjl where pm="+"'"+id+"'"+") as a,(select count(1) as suc from uf_txjl where zt='2' and pm="+"'"+id+"'"+") as b"; + yesql = "select a.total,b.suc from (select count(1) as total from uf_txjl where DATE_FORMAT(jhjsrqsjcs,'%Y%m%d') 0) { + String total = rs.getString("total"); + String suc = rs.getString("suc"); + if (total.equals("0")) { + curdata = "0.00"; + } else { + Double comrate = Double.parseDouble(suc) / Double.parseDouble(total) * 100; + DecimalFormat df = new DecimalFormat("#.##"); + String format = df.format(comrate); + curdata = format; + } + } + ye.next(); + if (ye.getArray().size() > 0) { + String total = ye.getString("total"); + String suc = ye.getString("suc"); + if (total.equals("0")) { + yesdata = "0.00"; + } else { + Double comrate = Double.parseDouble(suc) / Double.parseDouble(total) * 100; + DecimalFormat df = new DecimalFormat("#.##"); + String format = df.format(comrate); + yesdata = format; + } + + } + Map map = new HashMap(); + if (!curdata.equals("") && !yesdata.equals("")) { + if (Double.parseDouble(curdata) < Double.parseDouble(yesdata)) { + status = "2"; + } else if (Double.parseDouble(curdata) > Double.parseDouble(yesdata)) { + status = "1"; + } + map.put("count", curdata); + map.put("status", status); + return JSON.toJSONString(map); + } + map.put("count", "-.--"); + map.put("status", "0"); + + return JSON.toJSONString(map); + } + + //获取经理待办任务 + @Override + public String getNotStartTask(String id) { + RecordSet rs=new RecordSet(); + //待办任务(状态为未完成) + String desql=""; + String res1 = "0"; + if(!"".equals(id) && id!=null){ + desql="select count(1) as c from view_EmpTask where rwzt in (0,1) and zbr = '"+id+"'"; + }else{ + desql="select count(1) as c from view_EmpTask where rwzt in (0,1)"; + } + boolean execute = rs.execute(desql); + if(execute&&rs.next()){ + res1= rs.getString("c"); + } + return JSON.toJSONString(res1); + } + //获取经理进行中任务 + @Override + public String getOnGoingTask(String id) { + RecordSet rs=new RecordSet(); + //待办任务(状态为未完成) + String desql=""; + if(!"".equals(id) && id!=null){ + desql="SELECT count(zjln) as c FROM uf_rwwcqd where rwzt='1' and zjln='"+id+"'"; + }else{ + desql="SELECT count(zjln) as c FROM uf_rwwcqd where rwzt='1' "; + } + rs.execute(desql); + rs.next(); + String res1="0"; + if(rs.getArray().size()>0){ + res1 = rs.getString("c"); + } + return JSON.toJSONString(res1); + } +//----------------------------------------------------------新需求 + @Override + public String getComplaintDelayTask(String id) { + RecordSet rs=new RecordSet(); + //待办任务(状态为未完成) + String desql=""; + if(!"".equals(id) && id!=null){ + desql="SELECT count(zbr) as c FROM uf_rwwcqd where rwzt='3' and zbr='"+id+"'"; + }else{ + desql="SELECT count(zbr) as c FROM uf_rwwcqd where rwzt='3' "; + } + rs.execute(desql); + rs.next(); + String res1="0"; + if(rs.getArray().size()>0){ + res1 = rs.getString("c"); + } + return JSON.toJSONString(res1); + } + + @Override + public String getComplaintNotStartTask(String id) { + RecordSet rs=new RecordSet(); + //待办任务(状态为未完成) + String desql=""; + if(!"".equals(id) && id!=null){ + desql="SELECT count(zbr) as c FROM uf_rwwcqd where rwzt='0' and zbr='"+id+"'"; + }else{ + desql="SELECT count(zbr) as c FROM uf_rwwcqd where rwzt='0'"; + } + rs.execute(desql); + rs.next(); + String res1="0"; + if(rs.getArray().size()>0){ + res1 = rs.getString("c"); + } + return JSON.toJSONString(res1); + } + + @Override + public String getComplaintOnGoingTask(String id) { + RecordSet rs=new RecordSet(); + //待办任务(状态为未完成) + String desql=""; + if(!"".equals(id) && id!=null){ + desql="SELECT count(zbr) as c FROM uf_rwwcqd where rwzt='1' and zbr='"+id+"'"; + }else{ + desql="SELECT count(zbr) as c FROM uf_rwwcqd where rwcodekyzt='1' "; + } + rs.execute(desql); + rs.next(); + String res1="0"; + if(rs.getArray().size()>0){ + res1 = rs.getString("c"); + } + return JSON.toJSONString(res1); + } + + @Override + public String newgetdelayTask(String stau,String id) { + RecordSet rs=new RecordSet(); + RecordSet ye=new RecordSet(); + //延期任务量 + if("".equals(stau) && stau==null){ + stau="3"; + } + String desql="select count(1) as c from view_totaltask_supportcenter where rwzt='"+stau+"'"; + //前一天数据 +// String yesql="select count(1) as c from uf_rwwcqd where csrq0){ + String res1 = rs.getString("c"); + curdata=res1; + rs.next(); + } + Map map=new HashMap(); + if(!curdata.equals("")){ + map.put("count",curdata); + map.put("status","0"); + return JSON.toJSONString(map); + } + map.put("count", "0"); + map.put("status", "0"); + return JSON.toJSONString(map); + } + + @Override + public String newdaliyTaskBag(String jdcode) {//TODO +// BaseBean baseBean=new BaseBean(); + RecordSet rs1=new RecordSet(); + RecordSet rs2=new RecordSet(); + RecordSet rs3=new RecordSet(); + ArrayList list=new ArrayList(); + //查到任务包类型 + String sql="select rwblx from uf_hotelinfo where holidex='"+jdcode+"'"; + String sql2="SELECT rwlb from uf_rwbpzb where rwbid='"; + String sql3="SELECT rwbt,lb,rwnr,zq from uf_rwqd where zq='0' and id in"; + boolean execute = rs1.execute(sql); + if(execute && rs1.next()){ + String rwblx = rs1.getString("rwblx"); + sql2=sql2+Integer.parseInt(rwblx)+"'"; + boolean execute1 = rs2.execute(sql2); + //获取列表数组 + if(execute1 && rs2.next()){ + String rwlb = rs2.getString("rwlb").replace("\"", ""); +// baseBean.writeLog("列表数组为"+rwlb); + sql3=sql3+"("+rwlb+")"; +// baseBean.writeLog("sql3===========>"+sql3); + boolean execute2 = rs3.execute(sql3); + if(execute2){ + while (rs3.next()){ + Map map=new HashMap(); + String rwbt = rs3.getString("rwbt"); + String lb = rs3.getString("lb"); + String rwnr = rs3.getString("rwnr"); + String zq = rs3.getString("zq"); + map.put("rwbt",rwbt); + map.put("lb",lb); + map.put("rwnr",rwnr); + map.put("zq",zq); + list.add(map); + } + return JSON.toJSONString(list); + } + } + } + return JSON.toJSONString(list); + } + + @Override + public String newgetWorkplan(List mobileEvents,String id) { + RecordSet rs=new RecordSet(); + String sql="SELECT status,REQUESTID from workplan WHERE id="+"'"+id+"'"; + rs.execute(sql); + rs.next(); + ArrayList list=new ArrayList(); + if(rs.getArray().size()>0){ + for (int i = 0; i < rs.getArray().size(); i++) { + Map map=new HashMap<>(); + String requestid = rs.getString("REQUESTID"); + String status = rs.getString("status"); + map.put("requestid",requestid); + map.put("status",status); + list.add(map); + rs.next(); + } + }else{ + return JSON.toJSONString(list); + } + return JSON.toJSONString(list); + } + //本周需完成任务 + @Override + public String currWeekTask(String jdcode) { + Calendar cal = Calendar.getInstance(); + Date date=new Date(); + cal.setTime(date); + // 判断要计算的日期是否是周日,如果是则减一天计算周六的,否则会出问题,计算到下一周去了 + int dayWeek = cal.get(Calendar.DAY_OF_WEEK);// 获得当前日期是一个星期的第几天 + if (1 == dayWeek) { + cal.add(Calendar.DAY_OF_MONTH, -1); + } + // 设置一个星期的第一天,按中国的习惯一个星期的第一天是星期一 + cal.setFirstDayOfWeek(Calendar.MONDAY); + // 获得当前日期是一个星期的第几天 + int day = cal.get(Calendar.DAY_OF_WEEK); + // 根据日历的规则,给当前日期减去星期几与一个星期第一天的差值 + cal.add(Calendar.DATE, cal.getFirstDayOfWeek() - day); + SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd"); + String imptimeBegin = sdf.format(cal.getTime()); + // System.out.println("所在周星期一的日期:" + imptimeBegin); + cal.add(Calendar.DATE, 6); + String imptimeEnd = sdf.format(cal.getTime()); + + int num=0; + RecordSet rs=new RecordSet(); + String sql="SELECT csrq from view_totallist WHERE rwzt in(0,1) and find_in_set('"+jdcode+"',zjln)"; + boolean execute = rs.execute(sql); + if(execute){ + while(rs.next()){ + String csrq = rs.getString("csrq").replace("-",""); + if(Integer.valueOf(csrq)>=Integer.valueOf(imptimeBegin) && Integer.valueOf(csrq)<=Integer.valueOf(imptimeEnd)){ + num++; + } + } + } + return String.valueOf(num); + } + + @Override + public String getnewWorkplan(JSONArray list,String currentDate) { +// BaseBean baseBean=new BaseBean(); + ArrayList> list1=new ArrayList(); + ArrayList> list2=new ArrayList(); + ArrayList> uncomrelist=new ArrayList();//未完成的添加 + ArrayList> comrelist=new ArrayList();//已完成的添加 + ArrayList> norelist=new ArrayList();//无requestid + Map>> stringArrayListMap=new HashMap<>(); + String idsql=" where id in("; + for (int i = 0; i map = new HashMap<>(); + net.sf.json.JSONObject object = list.getJSONObject(i); +// baseBean.writeLog("封装传来的对象"+object); +// String color = object.getString("color"); + String workPlanTypeName = object.getString("workPlanTypeName"); + String planName = object.getString("planName"); + String beginDate = object.getString("beginDate"); + String endDate = object.getString("endDate"); + String beginTime = object.getString("beginTime"); + String endTime = object.getString("endTime"); + String id = object.getString("id"); + String workplanType = object.getString("workplanType"); +// String address = object.getString("address"); +// String urgentLevel = object.getString("urgentLevel"); +// String remindTypeName = object.getString("remindTypeName"); + +// map.put("color",color); + map.put("workPlanTypeName",workPlanTypeName); + map.put("planName",planName); + map.put("beginDate",beginDate); + map.put("endDate",endDate); + map.put("beginTime",beginTime); + map.put("endTime",endTime); + map.put("id",id); + map.put("workplanType",workplanType); +// map.put("address",address); +// map.put("urgentLevel",urgentLevel); +// map.put("remindTypeName",remindTypeName); + list1.add(map); + idsql+="'"+id+"',"; + } + //确定有数据 + if(list1.size()>0){ + //处理下sql +// baseBean.writeLog("sql:"+idsql); + String substring = idsql.substring(0, idsql.length() - 1); + + substring+=")"; + RecordSet recordSet = new RecordSet(); + boolean execute = recordSet.execute("select id,status,REQUESTID from workplan"+substring); +// baseBean.writeLog("sql为==========>>>select id,status,REQUESTID from workplan"+substring); + if(execute){ + while (recordSet.next()){ +// baseBean.writeLog("封装截取的id"+recordSet.getString("id")); + Map map = new HashMap<>(); + String id = recordSet.getString("id"); + String status = recordSet.getString("status"); + String requestid = recordSet.getString("REQUESTID"); + map.put("id", id); + map.put("status", status); + map.put("requestid", requestid); +// baseBean.writeLog("封装截取的id"+map); + list2.add(map); + } + } + //根据id查出来的数据查询 + for (Map map : list2) { +// baseBean.writeLog("封装封装开始,3个数组"); + for (Map stringMap : list1) { + if(stringMap.get("workplanType").equals("15")){//临时任务 + stringMap.put("workplanType","0"); + }// + if(stringMap.get("id").equals(map.get("id"))){//如果id相同,匹配对象了,说明是点击,或今天的数据 + if(!"".equals(map.get("requestid"))){//如果有requestid + if(map.get("status").equals("0")){ + // console.log("未完成的添加",daily) + +// baseBean.writeLog("未完成"+stringMap.toString()); + uncomrelist.add(stringMap); + }else{ + // console.log("已完成的添加",aily) +// baseBean.writeLog("已完成" + stringMap.toString()); + comrelist.add(stringMap); + } + }else{//如果没有requestid +// baseBean.writeLog("无requestid" + stringMap.toString()); + norelist.add(stringMap); + } + } + } + } +// baseBean.writeLog("未完成的结果集"+uncomrelist.toString()); + stringArrayListMap.put("uncomrelist",uncomrelist); +// baseBean.writeLog("已完成的结果集"+comrelist.toString()); + stringArrayListMap.put("comrelist",comrelist); +// baseBean.writeLog("无requestid的结果集"+norelist.toString()); + stringArrayListMap.put("norelist",norelist); + + }else{//今天无数据 + return JSON.toJSONString(stringArrayListMap); + } + + + return JSON.toJSONString(stringArrayListMap); + } + + @Override + public String codeky(String region) { + RecordSet rs=new RecordSet(); + //延期任务量 + String desql="select count(1) as num from uf_hotelinfo where region like '"+region+"' and kyzt = '0'"; + //前一天数据 + String yesql="select count(1) as c from uf_rwwcqd where csrq map=new HashMap(); + if(execute&&rs.next()){ + String num = rs.getString("num"); + map.put("num",num); + } + return JSON.toJSONString(map); + } + + @Override + public String emptyMananger() { + RecordSet rs=new RecordSet(); + RecordSet rs1=new RecordSet(); +// boolean execute = rs.execute("select count(1) as num from uf_hotelinfo where region like 'HIEX%' and kyzt = 0"); + boolean execute = rs.execute("select count(1) as num from view_kqManager"); + boolean execute1 = rs1.execute("select count(1) as c from view_kqManager where zjn is null or zjn=''"); + Map map=new HashMap(); + rs1.next(); + if(execute&&rs.next()) { + String num = rs.getString("num"); + String qu = rs1.getString("c"); + map.put("num", num); + map.put("qu", qu); + } + return JSON.toJSONString(map); + } + + @Override + + public String zhichiOnGoing() { + RecordSet rs=new RecordSet(); + //待办任务(状态为未完成) +// String desql="select count(1) as c from view_totallist where rwzt = '1'"; + String desql="select count(1) as c from view_totaltask_supportcenter where rwzt = '2'"; + rs.execute(desql); + rs.next(); + String res1="0"; + if(rs.getArray().size()>0){ + res1 = rs.getString("c"); + } + return JSON.toJSONString(res1); + } + + @Override + public String zjnewgetdelayTask(String stau, String id) { + RecordSet rs=new RecordSet(); + RecordSet ye=new RecordSet(); + //延期任务量 + if("".equals(stau) && stau==null){ + stau="3"; + } + String desql="select count(1) as c from view_totallist where rwzt='"+stau+"'"; + //如果是总经理登录 + if(!"".equals(id) && id!=null){ + desql=desql+" and find_in_set('"+id+"',zjln)"; + } + rs.execute(desql); + //当天数据 + String curdata=""; + //昨日数据 + String status="0"; //0/平 1/上升 2/下降 + rs.next(); + if(rs.getArray().size()>0){ + String res1 = rs.getString("c"); + curdata=res1; + rs.next(); + } + Map map=new HashMap(); + if(!curdata.equals("")){ + map.put("count",curdata); + map.put("status","0"); + return JSON.toJSONString(map); + } + map.put("count", "0"); + map.put("status", "0"); + return JSON.toJSONString(map); + } + //本周总经理下发 + @Override + public List currWeekxiafa(String id) { + ArrayList list=new ArrayList<>(); + RecordSet rs=new RecordSet(); + String num1=getdelay(id); + baseBean.writeLog("逾期时任务"+num1); + String num2=getnum2(id); + baseBean.writeLog("本周需完成任务"+num1); + String num3=getnum3(id); + baseBean.writeLog("本周总经理下发任务"+num1); + String num4=getnum4(id); + baseBean.writeLog("本月完成任务"+num1); + list.add(num1); + list.add(num2); + list.add(num3); + list.add(num4); + return list; + } + + @Override + public ArrayList getListBySearch(String uid, String taskTitle, String creator, String stopTime, String taskStatus,String type) throws Exception { + ArrayList list=new ArrayList<>(); + if(type.equals("1")){ + List hashMaps = getdelayBySearch(uid, taskTitle, creator, stopTime, taskStatus); + list.add(hashMaps); + } + if(type.equals("2")){ + List hashMaps2 = getList2BySearch(uid, taskTitle, creator, stopTime, taskStatus); + list.add(hashMaps2); + } + if(type.equals("3")){ + List hashMaps3 = getList3BySearch(uid, taskTitle, creator, stopTime, taskStatus); + list.add(hashMaps3); + } + if(type.equals("4")){ + List hashMaps4 = getList4BySearch(uid, taskTitle, creator, stopTime, taskStatus); + list.add(hashMaps4); + } + + return list; + } + + private List getList4BySearch(String uid, String taskTitle, String creator, String stopTime, String taskStatus) throws Exception { + RecordSet rs=new RecordSet(); + ArrayList list=new ArrayList<>(); + + String sql="SELECT requestId,rwbt,zbr,csrq,zjln,zxr,rwzt from view_totallist WHERE rwzt = 2 and find_in_set('"+uid+"',zjln) and rq30 map=new HashMap<>(); + map.put("id",rs.getString("requestId")); + map.put("title",rs.getString("rwbt")); + map.put("zbr",resourceComInfo.getLastname(rs.getString("zxr"))); + map.put("csrq",rs.getString("csrq")); + String rwzt=""; + if(rs.getString("rwzt").equals("0")){ + rwzt="未开始"; + } + if(rs.getString("rwzt").equals("1")){ + rwzt="进行中"; + } + if(rs.getString("rwzt").equals("2")){ + rwzt="已提交"; + } + if(rs.getString("rwzt").equals("3")){ + rwzt="逾期"; + } + map.put("rwzt",rwzt); + list.add(map); + } + } + return list; + } + + private List getList3BySearch(String uid, String taskTitle, String creator, String stopTime, String taskStatus) throws Exception { + ArrayList list=new ArrayList<>(); + Calendar cal = Calendar.getInstance(); + Date date=new Date(); + cal.setTime(date); + // 判断要计算的日期是否是周日,如果是则减一天计算周六的,否则会出问题,计算到下一周去了 + int dayWeek = cal.get(Calendar.DAY_OF_WEEK);// 获得当前日期是一个星期的第几天 + if (1 == dayWeek) { + cal.add(Calendar.DAY_OF_MONTH, -1); + } + // 设置一个星期的第一天,按中国的习惯一个星期的第一天是星期一 + cal.setFirstDayOfWeek(Calendar.MONDAY); + // 获得当前日期是一个星期的第几天 + int day = cal.get(Calendar.DAY_OF_WEEK); + // 根据日历的规则,给当前日期减去星期几与一个星期第一天的差值 + cal.add(Calendar.DATE, cal.getFirstDayOfWeek() - day); + SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd"); + String imptimeBegin = sdf.format(cal.getTime()); + // System.out.println("所在周星期一的日期:" + imptimeBegin); + cal.add(Calendar.DATE, 6); + String imptimeEnd = sdf.format(cal.getTime()); + + RecordSet rs=new RecordSet(); +// String sql="SELECT ksrq,lcsjid,rwbt,jsr,jzrq,zjln,rwzt from uf_xfrw WHERE rwzt in(0,1) and find_in_set('"+uid+"',xfr)"; + String sql="SELECT ksrq,lcsjid,rwbt,jsr,jzrq,zjln,rwzt from uf_xfrw WHERE find_in_set('"+uid+"',xfr)"; + if(StringUtils.isNotBlank(taskTitle)){ + sql+=" and rwbt like '%"+taskTitle+"%'"; + } + if(StringUtils.isNotBlank(creator)){ + sql+=" and jsr = '"+creator+"'"; + } + if(StringUtils.isNotBlank(stopTime)){ + sql+=" and jzrq <= '"+stopTime+"'"; + } + if(StringUtils.isNotBlank(taskStatus)){ + sql+=" and rwzt = '"+taskStatus+"'"; + } + sql+=" order by jzrq desc"; + baseBean.writeLog("移动端本周总经理下发sql"+sql); + ResourceComInfo resourceComInfo = new ResourceComInfo(); + boolean execute = rs.execute(sql); + if(execute){ + baseBean.writeLog("移动端本周总经理下发sql完成"); + while(rs.next()){ + baseBean.writeLog("移动端本周总经理循环"); + String ksrq = rs.getString("ksrq").replace("-",""); + if(Integer.valueOf(ksrq)>=Integer.valueOf(imptimeBegin) && Integer.valueOf(ksrq)<=Integer.valueOf(imptimeEnd)){ + HashMap map=new HashMap<>(); + map.put("id",rs.getString("lcsjid")); + baseBean.writeLog("移动端本周总经理循环添加map"+JSON.toJSONString(map)); + map.put("title",rs.getString("rwbt")); + baseBean.writeLog("移动端本周总经理循环添加map"+JSON.toJSONString(map)); + if(!rs.getString("jsr").equals("")){ + map.put("zbr",resourceComInfo.getLastname(rs.getString("jsr"))); + }else if(!rs.getString("zjln").equals("")){ + map.put("zbr",resourceComInfo.getLastname(rs.getString("zjln"))); + }else{ + map.put("zbr","暂无"); + } + baseBean.writeLog("移动端本周总经理循环添加map"+JSON.toJSONString(map)); + map.put("csrq",rs.getString("jzrq")); + baseBean.writeLog("移动端本周总经理循环添加map"+JSON.toJSONString(map)); + String rwzt=""; + baseBean.writeLog("移动端本周总经理循环添加map=============================>"+rs.getString("rwzt")); + if(rs.getString("rwzt").equals("0")){ + rwzt="未开始"; + } + if(rs.getString("rwzt").equals("1")){ + rwzt="进行中"; + } + if(rs.getString("rwzt").equals("2")){ + rwzt="已提交"; + } + if(rs.getString("rwzt").equals("3")){ + rwzt="逾期"; + } + + map.put("rwzt",rwzt); + baseBean.writeLog("移动端本周总经理循环添加map"+JSON.toJSONString(map)); + list.add(map); + } + } + } + baseBean.writeLog("移动端本周总经理最终list"+JSON.toJSONString(list)); + return list; + } + public String getnum3(String id){ + Calendar cal = Calendar.getInstance(); + Date date=new Date(); + cal.setTime(date); + // 判断要计算的日期是否是周日,如果是则减一天计算周六的,否则会出问题,计算到下一周去了 + int dayWeek = cal.get(Calendar.DAY_OF_WEEK);// 获得当前日期是一个星期的第几天 + if (1 == dayWeek) { + cal.add(Calendar.DAY_OF_MONTH, -1); + } + // 设置一个星期的第一天,按中国的习惯一个星期的第一天是星期一 + cal.setFirstDayOfWeek(Calendar.MONDAY); + // 获得当前日期是一个星期的第几天 + int day = cal.get(Calendar.DAY_OF_WEEK); + // 根据日历的规则,给当前日期减去星期几与一个星期第一天的差值 + cal.add(Calendar.DATE, cal.getFirstDayOfWeek() - day); + SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd"); + String imptimeBegin = sdf.format(cal.getTime()); + // System.out.println("所在周星期一的日期:" + imptimeBegin); + cal.add(Calendar.DATE, 6); + String imptimeEnd = sdf.format(cal.getTime()); + + int num=0; + RecordSet rs=new RecordSet(); + String sql="SELECT ksrq from uf_xfrw WHERE find_in_set('"+id+"',xfr)"; + baseBean.writeLog("本周总经理下发sql"+sql); + boolean execute = rs.execute(sql); + if(execute){ + baseBean.writeLog("本周总经理下发sql完成"); + while(rs.next()){ + String ksrq = rs.getString("ksrq").replace("-",""); + if(Integer.valueOf(ksrq)>=Integer.valueOf(imptimeBegin) && Integer.valueOf(ksrq)<=Integer.valueOf(imptimeEnd)){ + num++; + } + } + } + return String.valueOf(num); + } + + private List getList2BySearch(String uid, String taskTitle, String creator, String stopTime, String taskStatus) throws Exception { + ArrayList list=new ArrayList<>(); + Calendar cal = Calendar.getInstance(); + Date date=new Date(); + cal.setTime(date); + // 判断要计算的日期是否是周日,如果是则减一天计算周六的,否则会出问题,计算到下一周去了 + int dayWeek = cal.get(Calendar.DAY_OF_WEEK);// 获得当前日期是一个星期的第几天 + if (1 == dayWeek) { + cal.add(Calendar.DAY_OF_MONTH, -1); + } + // 设置一个星期的第一天,按中国的习惯一个星期的第一天是星期一 + cal.setFirstDayOfWeek(Calendar.MONDAY); + // 获得当前日期是一个星期的第几天 + int day = cal.get(Calendar.DAY_OF_WEEK); + // 根据日历的规则,给当前日期减去星期几与一个星期第一天的差值 + cal.add(Calendar.DATE, cal.getFirstDayOfWeek() - day); + SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd"); + String imptimeBegin = sdf.format(cal.getTime()); + // System.out.println("所在周星期一的日期:" + imptimeBegin); + cal.add(Calendar.DATE, 6); + String imptimeEnd = sdf.format(cal.getTime()); +// 开始查询sql + String sql="SELECT requestId,rwbt,zbr,csrq,zjln,zxr,rwzt from view_totallist WHERE rwzt in(0,1) and find_in_set('"+uid+"',zjln)"; + if(StringUtils.isNotBlank(taskTitle)){ + sql+=" and rwbt like '%"+taskTitle+"%'"; + } + if(StringUtils.isNotBlank(creator)){ + sql+=" and zxr = '"+creator+"'"; + } + if(StringUtils.isNotBlank(stopTime)){ + sql+=" and csrq <= '"+stopTime+"'"; + } + if(StringUtils.isNotBlank(taskStatus)){ + sql+=" and rwzt = '"+taskStatus+"'"; + } + RecordSet rs=new RecordSet(); + ResourceComInfo resourceComInfo = new ResourceComInfo(); + baseBean.writeLog("本周需完成任务列表sql"+sql); + boolean execute = rs.execute(sql); + if(execute){ + baseBean.writeLog("本周需完成任务sql执行成功"); + while(rs.next()){ + String csrq = rs.getString("csrq").replace("-",""); + baseBean.writeLog("周需完成任务截取时间"+csrq); + if(Integer.valueOf(csrq)>=Integer.valueOf(imptimeBegin) && Integer.valueOf(csrq)<=Integer.valueOf(imptimeEnd)){ + HashMap map=new HashMap<>(); + map.put("id",rs.getString("requestId")); + baseBean.writeLog("本周需完成任务map添加数据"+JSON.toJSONString(map)); + map.put("title",rs.getString("rwbt")); + map.put("zbr",resourceComInfo.getLastname(rs.getString("zxr"))); + baseBean.writeLog("本周需完成任务map添加数据"+JSON.toJSONString(map)); + map.put("csrq",rs.getString("csrq")); + baseBean.writeLog("本周需完成任务map添加数据"+JSON.toJSONString(map)); + String rwzt=""; + if(rs.getString("rwzt").equals("0")){ + rwzt="未开始"; + } + if(rs.getString("rwzt").equals("1")){ + rwzt="进行中"; + } + if(rs.getString("rwzt").equals("2")){ + rwzt="已提交"; + } + if(rs.getString("rwzt").equals("3")){ + rwzt="逾期"; + } + baseBean.writeLog("本周需完成任务map添加数据"+JSON.toJSONString(map)); + map.put("rwzt",rwzt); + baseBean.writeLog("本周需完成任务map添加数据"+JSON.toJSONString(map)); + list.add(map); + } + } + } + return list; + } + + private List getdelayBySearch(String uid, String taskTitle, String creator, String stopTime, String taskStatus) throws Exception { + ArrayList list=new ArrayList<>(); + String sql="select requestId,rwbt,zbr,zxr,zjln,csrq,rwzt from view_totallist where rwzt = '3' and zjln="+ uid; + if(StringUtils.isNotBlank(taskTitle)){ + sql+=" and rwbt like '%"+taskTitle+"%'"; + } + if(StringUtils.isNotBlank(creator)){ + sql+=" and zxr = '"+creator+"'"; + } + if(StringUtils.isNotBlank(stopTime)){ + sql+=" and csrq <= '"+stopTime+"'"; + } + if(StringUtils.isNotBlank(taskStatus)){ + sql+=" and rwzt = '"+taskStatus+"'"; + } + RecordSet rs=new RecordSet(); + baseBean.writeLog("逾期任务sql条件查询"+sql); + ResourceComInfo resourceComInfo = new ResourceComInfo(); + boolean b = rs.executeQuery(sql); + if(b){ + while (rs.next()){ + HashMap map=new HashMap<>(); + map.put("id",rs.getString("requestId")); + map.put("title",rs.getString("rwbt")); + map.put("zbr",resourceComInfo.getLastname(rs.getString("zxr"))); + map.put("csrq",rs.getString("csrq")); + String rwzt=""; + if(rs.getString("rwzt").equals("0")){ + rwzt="未开始"; + } + if(rs.getString("rwzt").equals("1")){ + rwzt="进行中"; + } + if(rs.getString("rwzt").equals("2")){ + rwzt="已提交"; + } + if(rs.getString("rwzt").equals("3")){ + rwzt="逾期"; + } + map.put("rwzt",rwzt); + list.add(map); + } + } + return list; + } + + //逾期任务 + public String getdelay(String id){ + RecordSet rs=new RecordSet(); + String sql="select count(1) as c from view_totallist where rwzt = '3' and zjln="+ id; + baseBean.writeLog("逾期任务sql"+sql); + boolean b = rs.executeQuery(sql); + if(b&&rs.next()){ + baseBean.writeLog("逾期任务sql执行成功"); + String c = rs.getString("c"); + return c; + }else{ + return null; + } + } + + //本周需完成任务 + public String getnum2(String id){ + Calendar cal = Calendar.getInstance(); + Date date=new Date(); + cal.setTime(date); + // 判断要计算的日期是否是周日,如果是则减一天计算周六的,否则会出问题,计算到下一周去了 + int dayWeek = cal.get(Calendar.DAY_OF_WEEK);// 获得当前日期是一个星期的第几天 + if (1 == dayWeek) { + cal.add(Calendar.DAY_OF_MONTH, -1); + } + // 设置一个星期的第一天,按中国的习惯一个星期的第一天是星期一 + cal.setFirstDayOfWeek(Calendar.MONDAY); + // 获得当前日期是一个星期的第几天 + int day = cal.get(Calendar.DAY_OF_WEEK); + // 根据日历的规则,给当前日期减去星期几与一个星期第一天的差值 + cal.add(Calendar.DATE, cal.getFirstDayOfWeek() - day); + SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd"); + String imptimeBegin = sdf.format(cal.getTime()); + // System.out.println("所在周星期一的日期:" + imptimeBegin); + cal.add(Calendar.DATE, 6); + String imptimeEnd = sdf.format(cal.getTime()); + + int num=0; + RecordSet rs=new RecordSet(); + String sql="SELECT csrq from view_totallist WHERE rwzt in(0,1) and find_in_set('"+id+"',zjln)"; + baseBean.writeLog("本周需完成任务sql"+sql); + boolean execute = rs.execute(sql); + if(execute){ + baseBean.writeLog("本周需完成任务sql执行成功"); + while(rs.next()){ + String csrq = rs.getString("csrq").replace("-",""); + if(Integer.valueOf(csrq)>=Integer.valueOf(imptimeBegin) && Integer.valueOf(csrq)<=Integer.valueOf(imptimeEnd)){ + num++; + } + } + } + return String.valueOf(num); + } + //本周总经理下发 + + //总经理本月已完成任务 + public String getnum4(String id){ + RecordSet rs=new RecordSet(); + String sql="SELECT count(1) as num from view_totallist WHERE rwzt = 2 and find_in_set('"+id+"',zjln) and rq30 mobileEvents,String id); + + String currWeekTask(String jdcode); + + String getnewWorkplan(JSONArray list,String currentDate); + + String codeky(String region); + + String emptyMananger(); + + String zhichiOnGoing(); + + String zjnewgetdelayTask(String stau, String id); + + List currWeekxiafa(String id); + + ArrayList getListBySearch(String uid,String taskTitle, String creator, String stopTime, String taskStatus,String type) throws Exception; +} diff --git a/src/main/youhong_ai_old_src/weaver/aiyh_pcn/common_fadada/mapper/ActionMapper.java b/src/main/youhong_ai_old_src/weaver/aiyh_pcn/common_fadada/mapper/ActionMapper.java index a2d6957..85d115b 100644 --- a/src/main/youhong_ai_old_src/weaver/aiyh_pcn/common_fadada/mapper/ActionMapper.java +++ b/src/main/youhong_ai_old_src/weaver/aiyh_pcn/common_fadada/mapper/ActionMapper.java @@ -1,6 +1,9 @@ package weaver.aiyh_pcn.common_fadada.mapper; -import aiyh.utils.annotation.recordset.*; +import aiyh.utils.annotation.recordset.ParamMapper; +import aiyh.utils.annotation.recordset.Select; +import aiyh.utils.annotation.recordset.SqlMapper; +import aiyh.utils.annotation.recordset.Update; import weaver.conn.RecordSet; import java.util.Map; @@ -14,76 +17,88 @@ import java.util.Map; @SqlMapper public interface ActionMapper { - - - /** - * 更新合同号 - * - * @param tableName 表名 - * @param contractField 合同字段 - * @param contractNo 合同号 - * @param requestId 流程类型 - * @return 是否更新成功 - */ - @Update("update $t{tableName} set $t{contractField} = #{contractNo} " + - " where requestid = #{requestId}") - boolean updateContractInMainTable(@ParamMapper("tableName") String tableName, - @ParamMapper("contractField") String contractField, - @ParamMapper("contractNo") String contractNo, - @ParamMapper("requestId") String requestId); - - - /** - * 更新合同号 - * - * @param tableName 表名 - * @param contractField 合同字段 - * @param contractNo 合同号 - * @param detailId 明细表ID - * @return 是否更新成功 - */ - @Update("update $t{tableName} set $t{contractField} = #{contractNo} " + - " where id = #{detailId}") - boolean updateContractInDetailTable(@ParamMapper("tableName") String tableName, - @ParamMapper("contractField") String contractField, - @ParamMapper("contractNo") String contractNo, - @ParamMapper("detailId") String detailId); - - /** - * 查询主表rs - * @param billTable 表名 - * @param requestId requestid - * @return 结果rs - */ - @Select("select * from $t{tableName} where requestid = #{requestId}") - RecordSet selectMainRs(@ParamMapper("tableName") String billTable, @ParamMapper("requestId") String requestId); - - /** - * 跟新合同状态到主表 - * @param workflowTableName 流程表名 - * @param signStatusField 签署状态字段 - * @param requestId 请求id - * @param signSuccessValue 签署状态值 - */ - @Update("update $t{workflowTableName} set $t{signStatusField} = #{signSuccessValue} where requestid = #{requestId}") - void updateSignStatusMain(@ParamMapper("workflowTableName") String workflowTableName, - @ParamMapper("signStatusField") String signStatusField, - @ParamMapper("requestId") String requestId, - @ParamMapper("signSuccessValue") String signSuccessValue); - - /** - * 跟新合同状态到明细表 - * @param workflowTableName 流程表名 - * @param signStatusField 签署状态字段 - * @param detailId 请求id - * @param signSuccessValue 签署状态值 - */ - @Update("update $t{workflowTableName} set $t{signStatusField} = #{signSuccessValue} where id = #{detailId}") - void updateSignStatusDetail(@ParamMapper("workflowTableName") String workflowTableName, - @ParamMapper("signStatusField") String signStatusField, - @ParamMapper("signSuccessValue") String signSuccessValue, - @ParamMapper("detailId") Integer detailId); - - @Update("update $t{_billTable} set $t{_updateField} where requestid = #{_requestId}") - boolean updateFields(Map param); + + + /** + * 更新合同号 + * + * @param tableName 表名 + * @param contractField 合同字段 + * @param contractNo 合同号 + * @param requestId 流程类型 + * @return 是否更新成功 + */ + @Update("update $t{tableName} set $t{contractField} = #{contractNo} " + + " where requestid = #{requestId}") + boolean updateContractInMainTable(@ParamMapper("tableName") String tableName, + @ParamMapper("contractField") String contractField, + @ParamMapper("contractNo") String contractNo, + @ParamMapper("requestId") String requestId); + + + /** + * 更新合同号 + * + * @param tableName 表名 + * @param contractField 合同字段 + * @param contractNo 合同号 + * @param detailId 明细表ID + * @return 是否更新成功 + */ + @Update("update $t{tableName} set $t{contractField} = #{contractNo} " + + " where id = #{detailId}") + boolean updateContractInDetailTable(@ParamMapper("tableName") String tableName, + @ParamMapper("contractField") String contractField, + @ParamMapper("contractNo") String contractNo, + @ParamMapper("detailId") String detailId); + + /** + * 查询主表rs + * + * @param billTable 表名 + * @param requestId requestid + * @return 结果rs + */ + @Select("select * from $t{tableName} where requestid = #{requestId}") + RecordSet selectMainRs(@ParamMapper("tableName") String billTable, @ParamMapper("requestId") String requestId); + + /** + * 跟新合同状态到主表 + * + * @param workflowTableName 流程表名 + * @param signStatusField 签署状态字段 + * @param requestId 请求id + * @param signSuccessValue 签署状态值 + */ + @Update("update $t{workflowTableName} set $t{signStatusField} = #{signSuccessValue} where requestid = #{requestId}") + void updateSignStatusMain(@ParamMapper("workflowTableName") String workflowTableName, + @ParamMapper("signStatusField") String signStatusField, + @ParamMapper("requestId") String requestId, + @ParamMapper("signSuccessValue") String signSuccessValue); + + /** + * 跟新合同状态到明细表 + * + * @param workflowTableName 流程表名 + * @param signStatusField 签署状态字段 + * @param detailId 请求id + * @param signSuccessValue 签署状态值 + */ + @Update("update $t{workflowTableName} set $t{signStatusField} = #{signSuccessValue} where id = #{detailId}") + void updateSignStatusDetail(@ParamMapper("workflowTableName") String workflowTableName, + @ParamMapper("signStatusField") String signStatusField, + @ParamMapper("signSuccessValue") String signSuccessValue, + @ParamMapper("detailId") Integer detailId); + + @Update("update $t{_billTable} set $t{_updateField} where requestid = #{_requestId}") + boolean updateFields(Map param); + + /** + *

查询流程请求id

+ * + * @param docNo 合同编号 + * @return 流程id + */ + @Select("select request_id from uf_contract_log where contract_id = #{docNo}") + String selectRequestId(@ParamMapper("docNo") String docNo); } diff --git a/src/test/java/xuanran/wang/shyl/dataasync/AsyncTest.java b/src/test/java/xuanran/wang/shyl/dataasync/AsyncTest.java index 057a32b..2db4f9b 100644 --- a/src/test/java/xuanran/wang/shyl/dataasync/AsyncTest.java +++ b/src/test/java/xuanran/wang/shyl/dataasync/AsyncTest.java @@ -3,10 +3,15 @@ package xuanran.wang.shyl.dataasync; import aiyh.utils.Util; import aiyh.utils.excention.CustomerException; import aiyh.utils.httpUtil.util.HttpUtils; +import aiyh.utils.tool.cn.hutool.http.HttpRequest; import basetest.BaseTest; +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.crypto.asymmetric.KeyType; +import cn.hutool.crypto.asymmetric.RSA; +import cn.hutool.json.JSONUtil; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; -import com.api.xuanran.wang.shyl.entity.MQMessage; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext; @@ -22,25 +27,13 @@ import org.junit.Test; import weaver.xuanran.wang.common.annocation.SqlFieldMapping; import weaver.xuanran.wang.common.annocation.SqlUpdateWhereField; import weaver.xuanran.wang.shyl.dataasync.entity.StudentClass; - -import java.io.UnsupportedEncodingException; -import java.lang.reflect.Field; -import java.nio.charset.StandardCharsets; -import java.util.*; - -import cn.hutool.core.util.CharsetUtil; -import cn.hutool.core.util.StrUtil; -import cn.hutool.crypto.asymmetric.KeyType; -import cn.hutool.crypto.asymmetric.RSA; -import cn.hutool.http.HttpRequest; -import cn.hutool.json.JSONUtil; import weaver.xuanran.wang.shyl_mq.RocketMQFactory; import weaver.xuanran.wang.shyl_mq.consumer.OrgConsumer; import weaver.xuanran.wang.shyl_mq.util.RocketConsumerUtil; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; +import java.io.UnsupportedEncodingException; +import java.lang.reflect.Field; +import java.util.*; /** *

上海团校测试类

@@ -49,335 +42,335 @@ import java.util.Objects; * @date 2023/2/9 13:23 */ public class AsyncTest extends BaseTest { - - @Test - public void testExecuteBatchByEntity() { - HttpUtils httpUtils = new HttpUtils(); - String json = "{\n" + - "\t\"msg\": \"success\",\n" + - "\t\"code\": 0,\n" + - "\t\"data\": [{\n" + - "\t\t\"id\": \"2f44f6c8626c4e328af10fd3af4122a3\",\n" + - "\t\t\"classNo\": null,\n" + - "\t\t\"className\": \"BB班202301\",\n" + - "\t\t\"belongYear\": 2023\n" + - "\t}, {\n" + - "\t\t\"id\": \"134fdb4ef8b4432cb66c03eb34dc94f7\",\n" + - "\t\t\"classNo\": null,\n" + - "\t\t\"className\": \"1\",\n" + - "\t\t\"belongYear\": 2023\n" + - "\t}]\n" + - "}"; - HashMap responseMap = JSONObject.parseObject(json, HashMap.class); - log.info("responseMap : " + responseMap); - Object data = responseMap.get("data"); - List list = JSONObject.parseArray(data.toString(), StudentClass.class); - List> params = new ArrayList<>(); - List> whereParams = new ArrayList<>(); - for (Object o : list) { - if (Objects.isNull(o)) { - continue; - } - Class clazz = o.getClass(); - Field[] fields = clazz.getDeclaredFields(); - LinkedHashMap linkedHashMap = new LinkedHashMap<>(); - ArrayList whereParam = new ArrayList<>(); - for (Field field : fields) { - field.setAccessible(true); - String fieldName = field.getName(); - Object fieldValue; - try { - fieldValue = field.get(o); - } catch (IllegalAccessException e) { - throw new CustomerException(Util.logStr("field get error! the error is :[{}]," + - "current field is: [{}], current obj is: [{}]", e.getMessage(), fieldName, JSONObject.toJSONString(o))); - } - if (Objects.isNull(fieldValue)) { - continue; - } - // 数据库字段映射 如果注解中没有值那么写入数据库就是实体类字段名 - SqlFieldMapping sqlFieldMapping = field.getAnnotation(SqlFieldMapping.class); - if (null == sqlFieldMapping) { - continue; - } - String sqlFieldMappingValue = sqlFieldMapping.value(); - if (StringUtils.isNotBlank(sqlFieldMappingValue)) { - fieldName = sqlFieldMappingValue; - } - linkedHashMap.put(fieldName, fieldValue); - - // 更新条件字段注解 - SqlUpdateWhereField sqlUpdateWhereField = field.getAnnotation(SqlUpdateWhereField.class); - if (null == sqlUpdateWhereField || !sqlUpdateWhereField.value()) { - continue; - } - whereParam.add(fieldValue.toString()); - } - params.add(linkedHashMap); - whereParams.add(whereParam); - } - log.info("params : " + JSONObject.toJSONString(params)); - log.info("wheres : " + whereParams); - } - - @Test - public void testSql() { - String json = "[{\"requestName\":\"放松放松\",\"mainData\":[{\"fieldName\":\"yysy\",\"fieldValue\":\"测试20230309\"}],\"workflowId\":\"45\"},{\"requestName\":\"放松放松\",\"mainData\":[{\"fieldName\":\"yysy\",\"fieldValue\":\"测试20230309\"}],\"workflowId\":\"451\"}]"; - JSONArray temp = new JSONArray(); - JSONArray arr = JSONObject.parseObject(json, JSONArray.class); - for (int i = 0; i < arr.size() - 1; i++) { - for (int j = 0; j < 25; j++) { - Object o = arr.get(i); - JSONObject object = JSONObject.parseObject(o.toString()); - temp.add(object); - } - } - JSONArray arrr = new JSONArray(); - for (Object o : temp) { - JSONObject object = JSONObject.parseObject(o.toString()); - List mainData = (List)object.get("mainData"); - JSONObject jsonObject = new JSONObject(); - jsonObject.put("fieldName","customId"); - jsonObject.put("fieldValue",UUID.randomUUID().toString()); - mainData.add(jsonObject); - object.put("mainData", mainData); - arrr.add(object); - } - arr.addAll(arrr); - log.info("json arr size " + arr.size()); - log.info("arr : \n" + JSONObject.toJSONString(arr)); + + @Test + public void testExecuteBatchByEntity() { + HttpUtils httpUtils = new HttpUtils(); + String json = "{\n" + + "\t\"msg\": \"success\",\n" + + "\t\"code\": 0,\n" + + "\t\"data\": [{\n" + + "\t\t\"id\": \"2f44f6c8626c4e328af10fd3af4122a3\",\n" + + "\t\t\"classNo\": null,\n" + + "\t\t\"className\": \"BB班202301\",\n" + + "\t\t\"belongYear\": 2023\n" + + "\t}, {\n" + + "\t\t\"id\": \"134fdb4ef8b4432cb66c03eb34dc94f7\",\n" + + "\t\t\"classNo\": null,\n" + + "\t\t\"className\": \"1\",\n" + + "\t\t\"belongYear\": 2023\n" + + "\t}]\n" + + "}"; + HashMap responseMap = JSONObject.parseObject(json, HashMap.class); + log.info("responseMap : " + responseMap); + Object data = responseMap.get("data"); + List list = JSONObject.parseArray(data.toString(), StudentClass.class); + List> params = new ArrayList<>(); + List> whereParams = new ArrayList<>(); + for (Object o : list) { + if (Objects.isNull(o)) { + continue; + } + Class clazz = o.getClass(); + Field[] fields = clazz.getDeclaredFields(); + LinkedHashMap linkedHashMap = new LinkedHashMap<>(); + ArrayList whereParam = new ArrayList<>(); + for (Field field : fields) { + field.setAccessible(true); + String fieldName = field.getName(); + Object fieldValue; + try { + fieldValue = field.get(o); + } catch (IllegalAccessException e) { + throw new CustomerException(Util.logStr("field get error! the error is :[{}]," + + "current field is: [{}], current obj is: [{}]", e.getMessage(), fieldName, JSONObject.toJSONString(o))); + } + if (Objects.isNull(fieldValue)) { + continue; + } + // 数据库字段映射 如果注解中没有值那么写入数据库就是实体类字段名 + SqlFieldMapping sqlFieldMapping = field.getAnnotation(SqlFieldMapping.class); + if (null == sqlFieldMapping) { + continue; + } + String sqlFieldMappingValue = sqlFieldMapping.value(); + if (StringUtils.isNotBlank(sqlFieldMappingValue)) { + fieldName = sqlFieldMappingValue; + } + linkedHashMap.put(fieldName, fieldValue); + + // 更新条件字段注解 + SqlUpdateWhereField sqlUpdateWhereField = field.getAnnotation(SqlUpdateWhereField.class); + if (null == sqlUpdateWhereField || !sqlUpdateWhereField.value()) { + continue; + } + whereParam.add(fieldValue.toString()); + } + params.add(linkedHashMap); + whereParams.add(whereParam); + } + log.info("params : " + JSONObject.toJSONString(params)); + log.info("wheres : " + whereParams); + } + + @Test + public void testSql() { + String json = "[{\"requestName\":\"放松放松\",\"mainData\":[{\"fieldName\":\"yysy\",\"fieldValue\":\"测试20230309\"}],\"workflowId\":\"45\"},{\"requestName\":\"放松放松\",\"mainData\":[{\"fieldName\":\"yysy\",\"fieldValue\":\"测试20230309\"}],\"workflowId\":\"451\"}]"; + JSONArray temp = new JSONArray(); + JSONArray arr = JSONObject.parseObject(json, JSONArray.class); + for (int i = 0; i < arr.size() - 1; i++) { + for (int j = 0; j < 25; j++) { + Object o = arr.get(i); + JSONObject object = JSONObject.parseObject(o.toString()); + temp.add(object); + } + } + JSONArray arrr = new JSONArray(); + for (Object o : temp) { + JSONObject object = JSONObject.parseObject(o.toString()); + List mainData = (List) object.get("mainData"); + JSONObject jsonObject = new JSONObject(); + jsonObject.put("fieldName", "customId"); + jsonObject.put("fieldValue", UUID.randomUUID().toString()); + mainData.add(jsonObject); + object.put("mainData", mainData); + arrr.add(object); + } + arr.addAll(arrr); + log.info("json arr size " + arr.size()); + log.info("arr : \n" + JSONObject.toJSONString(arr)); // try { // List s = batchCreateWorkflowService.batchCreateWorkflow2(arr); // log.info("创建流程接口返回 " + s); // } catch (Exception e) { // log.error("创建流程失败 ! " + e.getMessage()); // } - } - - @Test - public void testC() { - try { - // 声明一个消费者consumer,需要传入一个组 weaver-consumer - DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("weaver-consumer"); - // 设置集群的NameServer地址,多个地址之间以分号分隔 183.192.65.118:9876 - consumer.setNamesrvAddr("114.115.168.220:9876"); - // 设置consumer的消费策略 - consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET); - // 集群模式消费,广播消费不会重试 - consumer.setMessageModel(MessageModel.CLUSTERING); - // 设置最大重试次数,默认是16次 - consumer.setMaxReconsumeTimes(1); - // 设置consumer所订阅的Topic和Tag,*代表全部的Tag AUTH_CONSOLE_USERINFO_TOPIC - consumer.subscribe("AUTH_CONSOLE_ORG_TOPIC", "*"); - consumer.setVipChannelEnabled(false); - // Listener,主要进行消息的逻辑处理,监听topic,如果有消息就会立即去消费 - consumer.registerMessageListener(new MessageListenerConcurrently() { - @Override - public ConsumeConcurrentlyStatus consumeMessage(List list, ConsumeConcurrentlyContext consumeConcurrentlyContext) { - System.out.println("msgs : " + JSONObject.toJSONString(list)); - System.out.println("context : " + JSONObject.toJSONString(consumeConcurrentlyContext)); - return ConsumeConcurrentlyStatus.RECONSUME_LATER; - } - }); - consumer.start(); - } catch (Exception e) { - log.error(" e => " + e.getMessage()); - } - } - - @Test - public void testD() throws UnsupportedEncodingException, MQBrokerException, RemotingException, InterruptedException, MQClientException { - String msg = "{\n" + - "\t\"actionType\": \"CREATE_ACTION\",\n" + - "\t\"topic\": \"OA_MEETING_TOPIC\",\n" + - "\t\"id\": \"29039\",\n" + - "\t\"content\": {\n" + - "\t\t\"sourceId\": \"29050\",\n" + - "\t\t\"mrtype\": \"2\",\n" + - "\t\t\"EndTime\": \"17:10\",\n" + - "\t\t\"meetingStatus\": \"1\",\n" + - "\t\t\"BeginTime\": \"17:36\",\n" + - "\t\t\"meetingTitle\": \"MQ测试20230223\",\n" + - "\t\t\"roomCode\": \"青年会堂会议室\",\n" + - "\t\t\"signCode\": \"\",\n" + - "\t\t\"EndDate\": \"2023-02-24\",\n" + - "\t\t\"BeginDate\": \"2023-02-23\",\n" + - "\t\t\"meetingHost\": \"96\",\n" + - "\t\t\"userList\": \"96\",\n" + - "\t\t\"meetingContent\": \"测试MQ20230223\"\n" + - "\t},\n" + - "\t\"sendTime\": \"2023-02-23 17:49:11\"\n" + - "}"; - RocketConsumerUtil.producerSendMsg("OAMeeting", msg,""); - // 先从本地缓存中获取生产者对象 - } - - @Test - public void testE() { - DefaultMQPushConsumer consumer = null; - log.info(Util.logStr("---- consumer : {} initialized start ----", "OrgConsumer")); - try { - try { - // 根据配置文件初始化一个consumer对象 - consumer = RocketMQFactory.getMQPushConsumer("OrgConsumer", new OrgConsumer().service()); - } catch (Exception e) { - throw new CustomerException(Util.logStr("the consumer init exception : {}", e.getMessage())); - } - try { - // 调用start()方法启动consumer - consumer.start(); - } catch (Exception e) { - throw new CustomerException(Util.logStr("the consumer start exception : {}", e.getMessage())); - } - log.info(Util.logStr("---- consumer : {} initialized end ----", "OrgConsumer")); - } catch (Exception e) { - log.info(Util.logStr("---- consumer : {} initialized error ----", "OrgConsumer")); - log.error(Util.getErrString(e)); - } - - } - - private static final Map SYSTEM_CACHE = new HashMap<>(); - /** - * ecology系统发放的授权许可证(appid) - */ - private static final String APPID = "JYZ"; - - @Test - public void testB() { - String json = "[{\n" + - "\t\"requestName\": \"你个沙雕32323\",\n" + - "\t\"workflowId\": \"45\",\n" + - "\t\"mainData\": [{\n" + - "\t\t\"fieldName\": \"yysy\",\n" + - "\t\t\"fieldValue\": \"测试20230309\"\n" + - "\t}]\n" + - "}, {\n" + - "\t\"requestName\": \"沙雕2号\",\n" + - "\t\"workflowId\": \"45\",\n" + - "\t\"mainData\": [{\n" + - "\t\t\"fieldName\": \"yysy\",\n" + - "\t\t\"fieldValue\": \"沙雕沙雕沙雕沙雕沙雕\"\n" + - "\t}]\n" + - "}, {\n" + - "\t\"requestName\": \"沙雕2号\",\n" + - "\t\"workflowId\": \"451\",\n" + - "\t\"mainData\": [{\n" + - "\t\t\"fieldName\": \"yysy\",\n" + - "\t\t\"fieldValue\": \"沙雕沙雕沙雕沙雕沙雕\"\n" + - "\t}]\n" + - "}]"; - testRestful("http://183.192.65.115:8080", "/api/wxr/shyl/workflow/batchCreate233", json); - } - - /** - * 第一步: - *

- * 调用ecology注册接口,根据appid进行注册,将返回服务端公钥和Secret信息 - */ - public static Map testRegist(String address) { - //获取当前系统RSA加密的公钥 - RSA rsa = new RSA(); - String publicKey = rsa.getPublicKeyBase64(); - String privateKey = rsa.getPrivateKeyBase64(); - // 客户端RSA私钥 - SYSTEM_CACHE.put("LOCAL_PRIVATE_KEY", privateKey); - // 客户端RSA公钥 - SYSTEM_CACHE.put("LOCAL_PUBLIC_KEY", publicKey); - //调用ECOLOGY系统接口进行注册 - String data = HttpRequest.post(address + "/api/ec/dev/auth/regist") - .header("appid", APPID) - .header("cpk", publicKey) - .timeout(2000) - .execute().body(); - // 打印ECOLOGY响应信息 - System.out.println("testRegist():" + data); - Map datas = JSONUtil.parseObj(data); - //ECOLOGY返回的系统公钥 - SYSTEM_CACHE.put("SERVER_PUBLIC_KEY", StrUtil.nullToEmpty((String) datas.get("spk"))); - //ECOLOGY返回的系统密钥 - SYSTEM_CACHE.put("SERVER_SECRET", StrUtil.nullToEmpty((String) datas.get("secrit"))); - return datas; - } - - /** - * 第二步: - *

- * 通过第一步中注册系统返回信息进行获取token信息 - */ - public static Map testGetoken(String address) { - // 从系统缓存或者数据库中获取ECOLOGY系统公钥和Secret信息 - String secret = SYSTEM_CACHE.get("SERVER_SECRET"); - String spk = SYSTEM_CACHE.get("SERVER_PUBLIC_KEY"); - // 如果为空,说明还未进行注册,调用注册接口进行注册认证与数据更新 - if (Objects.isNull(secret) || Objects.isNull(spk)) { - testRegist(address); - // 重新获取最新ECOLOGY系统公钥和Secret信息 - secret = SYSTEM_CACHE.get("SERVER_SECRET"); - spk = SYSTEM_CACHE.get("SERVER_PUBLIC_KEY"); - } - // 公钥加密,所以RSA对象私钥为null - RSA rsa = new RSA(null, spk); - //对秘钥进行加密传输,防止篡改数据 - String encryptSecret = rsa.encryptBase64(secret, CharsetUtil.CHARSET_UTF_8, KeyType.PublicKey); - //调用ECOLOGY系统接口进行注册 - String data = HttpRequest.post(address + "/api/ec/dev/auth/applytoken") - .header("appid", APPID) - .header("secret", encryptSecret) - .header("time", "3600") - .execute().body(); - System.out.println("testGetoken():" + data); - Map datas = JSONUtil.parseObj(data); - //ECOLOGY返回的token - // TODO 为Token缓存设置过期时间 - SYSTEM_CACHE.put("SERVER_TOKEN", StrUtil.nullToEmpty((String) datas.get("token"))); - return datas; - } - - /** - * 第三步: - *

- * 调用ecology系统的rest接口,请求头部带上token和用户标识认证信息 - * - * @param address ecology系统地址 - * @param api rest api 接口地址(该测试代码仅支持GET请求) - * @param jsonParams 请求参数json串 - *

- * 注意:ECOLOGY系统所有POST接口调用请求头请设置 "Content-Type","application/x-www-form-urlencoded; charset=utf-8" - */ - public String testRestful(String address, String api, String jsonParams) { - //ECOLOGY返回的token - String token = SYSTEM_CACHE.get("SERVER_TOKEN"); - if (StrUtil.isEmpty(token)) { - token = (String) testGetoken(address).get("token"); - } - String spk = SYSTEM_CACHE.get("SERVER_PUBLIC_KEY"); - //封装请求头参数 - RSA rsa = new RSA(null, spk); - //对用户信息进行加密传输,暂仅支持传输OA用户ID - String encryptUserid = rsa.encryptBase64("1", CharsetUtil.CHARSET_UTF_8, KeyType.PublicKey); - List list = JSONObject.parseObject(jsonParams, List.class); - HttpUtils httpUtils = new HttpUtils(); - HashMap headers = new HashMap<>(); - headers.put("appid", APPID); - headers.put("token", token); - headers.put("userid", encryptUserid); - headers.put("Content-Type", "application/x-www-form-urlencoded; charset=utf-8"); - log.info("请求头 = > " + headers); + } + + @Test + public void testC() { + try { + // 声明一个消费者consumer,需要传入一个组 weaver-consumer + DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("weaver-consumer"); + // 设置集群的NameServer地址,多个地址之间以分号分隔 183.192.65.118:9876 + consumer.setNamesrvAddr("114.115.168.220:9876"); + // 设置consumer的消费策略 + consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET); + // 集群模式消费,广播消费不会重试 + consumer.setMessageModel(MessageModel.CLUSTERING); + // 设置最大重试次数,默认是16次 + consumer.setMaxReconsumeTimes(1); + // 设置consumer所订阅的Topic和Tag,*代表全部的Tag AUTH_CONSOLE_USERINFO_TOPIC + consumer.subscribe("AUTH_CONSOLE_ORG_TOPIC", "*"); + consumer.setVipChannelEnabled(false); + // Listener,主要进行消息的逻辑处理,监听topic,如果有消息就会立即去消费 + consumer.registerMessageListener(new MessageListenerConcurrently() { + @Override + public ConsumeConcurrentlyStatus consumeMessage(List list, ConsumeConcurrentlyContext consumeConcurrentlyContext) { + System.out.println("msgs : " + JSONObject.toJSONString(list)); + System.out.println("context : " + JSONObject.toJSONString(consumeConcurrentlyContext)); + return ConsumeConcurrentlyStatus.RECONSUME_LATER; + } + }); + consumer.start(); + } catch (Exception e) { + log.error(" e => " + e.getMessage()); + } + } + + @Test + public void testD() throws UnsupportedEncodingException, MQBrokerException, RemotingException, InterruptedException, MQClientException { + String msg = "{\n" + + "\t\"actionType\": \"CREATE_ACTION\",\n" + + "\t\"topic\": \"OA_MEETING_TOPIC\",\n" + + "\t\"id\": \"29039\",\n" + + "\t\"content\": {\n" + + "\t\t\"sourceId\": \"29050\",\n" + + "\t\t\"mrtype\": \"2\",\n" + + "\t\t\"EndTime\": \"17:10\",\n" + + "\t\t\"meetingStatus\": \"1\",\n" + + "\t\t\"BeginTime\": \"17:36\",\n" + + "\t\t\"meetingTitle\": \"MQ测试20230223\",\n" + + "\t\t\"roomCode\": \"青年会堂会议室\",\n" + + "\t\t\"signCode\": \"\",\n" + + "\t\t\"EndDate\": \"2023-02-24\",\n" + + "\t\t\"BeginDate\": \"2023-02-23\",\n" + + "\t\t\"meetingHost\": \"96\",\n" + + "\t\t\"userList\": \"96\",\n" + + "\t\t\"meetingContent\": \"测试MQ20230223\"\n" + + "\t},\n" + + "\t\"sendTime\": \"2023-02-23 17:49:11\"\n" + + "}"; + RocketConsumerUtil.producerSendMsg("OAMeeting", msg, ""); + // 先从本地缓存中获取生产者对象 + } + + @Test + public void testE() { + DefaultMQPushConsumer consumer = null; + log.info(Util.logStr("---- consumer : {} initialized start ----", "OrgConsumer")); + try { + try { + // 根据配置文件初始化一个consumer对象 + consumer = RocketMQFactory.getMQPushConsumer("OrgConsumer", new OrgConsumer().service()); + } catch (Exception e) { + throw new CustomerException(Util.logStr("the consumer init exception : {}", e.getMessage())); + } + try { + // 调用start()方法启动consumer + consumer.start(); + } catch (Exception e) { + throw new CustomerException(Util.logStr("the consumer start exception : {}", e.getMessage())); + } + log.info(Util.logStr("---- consumer : {} initialized end ----", "OrgConsumer")); + } catch (Exception e) { + log.info(Util.logStr("---- consumer : {} initialized error ----", "OrgConsumer")); + log.error(Util.getErrString(e)); + } + + } + + private static final Map SYSTEM_CACHE = new HashMap<>(); + /** + * ecology系统发放的授权许可证(appid) + */ + private static final String APPID = "JYZ"; + + @Test + public void testB() { + String json = "[{\n" + + "\t\"requestName\": \"你个沙雕32323\",\n" + + "\t\"workflowId\": \"45\",\n" + + "\t\"mainData\": [{\n" + + "\t\t\"fieldName\": \"yysy\",\n" + + "\t\t\"fieldValue\": \"测试20230309\"\n" + + "\t}]\n" + + "}, {\n" + + "\t\"requestName\": \"沙雕2号\",\n" + + "\t\"workflowId\": \"45\",\n" + + "\t\"mainData\": [{\n" + + "\t\t\"fieldName\": \"yysy\",\n" + + "\t\t\"fieldValue\": \"沙雕沙雕沙雕沙雕沙雕\"\n" + + "\t}]\n" + + "}, {\n" + + "\t\"requestName\": \"沙雕2号\",\n" + + "\t\"workflowId\": \"451\",\n" + + "\t\"mainData\": [{\n" + + "\t\t\"fieldName\": \"yysy\",\n" + + "\t\t\"fieldValue\": \"沙雕沙雕沙雕沙雕沙雕\"\n" + + "\t}]\n" + + "}]"; + testRestful("http://183.192.65.115:8080", "/api/wxr/shyl/workflow/batchCreate233", json); + } + + /** + * 第一步: + *

+ * 调用ecology注册接口,根据appid进行注册,将返回服务端公钥和Secret信息 + */ + public static Map testRegist(String address) { + // 获取当前系统RSA加密的公钥 + RSA rsa = new RSA(); + String publicKey = rsa.getPublicKeyBase64(); + String privateKey = rsa.getPrivateKeyBase64(); + // 客户端RSA私钥 + SYSTEM_CACHE.put("LOCAL_PRIVATE_KEY", privateKey); + // 客户端RSA公钥 + SYSTEM_CACHE.put("LOCAL_PUBLIC_KEY", publicKey); + // 调用ECOLOGY系统接口进行注册 + String data = HttpRequest.post(address + "/api/ec/dev/auth/regist") + .header("appid", APPID) + .header("cpk", publicKey) + .timeout(2000) + .execute().body(); + // 打印ECOLOGY响应信息 + System.out.println("testRegist():" + data); + Map datas = JSONUtil.parseObj(data); + // ECOLOGY返回的系统公钥 + SYSTEM_CACHE.put("SERVER_PUBLIC_KEY", StrUtil.nullToEmpty((String) datas.get("spk"))); + // ECOLOGY返回的系统密钥 + SYSTEM_CACHE.put("SERVER_SECRET", StrUtil.nullToEmpty((String) datas.get("secrit"))); + return datas; + } + + /** + * 第二步: + *

+ * 通过第一步中注册系统返回信息进行获取token信息 + */ + public static Map testGetoken(String address) { + // 从系统缓存或者数据库中获取ECOLOGY系统公钥和Secret信息 + String secret = SYSTEM_CACHE.get("SERVER_SECRET"); + String spk = SYSTEM_CACHE.get("SERVER_PUBLIC_KEY"); + // 如果为空,说明还未进行注册,调用注册接口进行注册认证与数据更新 + if (Objects.isNull(secret) || Objects.isNull(spk)) { + testRegist(address); + // 重新获取最新ECOLOGY系统公钥和Secret信息 + secret = SYSTEM_CACHE.get("SERVER_SECRET"); + spk = SYSTEM_CACHE.get("SERVER_PUBLIC_KEY"); + } + // 公钥加密,所以RSA对象私钥为null + RSA rsa = new RSA(null, spk); + // 对秘钥进行加密传输,防止篡改数据 + String encryptSecret = rsa.encryptBase64(secret, CharsetUtil.CHARSET_UTF_8, KeyType.PublicKey); + // 调用ECOLOGY系统接口进行注册 + String data = HttpRequest.post(address + "/api/ec/dev/auth/applytoken") + .header("appid", APPID) + .header("secret", encryptSecret) + .header("time", "3600") + .execute().body(); + System.out.println("testGetoken():" + data); + Map datas = JSONUtil.parseObj(data); + // ECOLOGY返回的token + // TODO 为Token缓存设置过期时间 + SYSTEM_CACHE.put("SERVER_TOKEN", StrUtil.nullToEmpty((String) datas.get("token"))); + return datas; + } + + /** + * 第三步: + *

+ * 调用ecology系统的rest接口,请求头部带上token和用户标识认证信息 + * + * @param address ecology系统地址 + * @param api rest api 接口地址(该测试代码仅支持GET请求) + * @param jsonParams 请求参数json串 + *

+ * 注意:ECOLOGY系统所有POST接口调用请求头请设置 "Content-Type","application/x-www-form-urlencoded; charset=utf-8" + */ + public String testRestful(String address, String api, String jsonParams) { + // ECOLOGY返回的token + String token = SYSTEM_CACHE.get("SERVER_TOKEN"); + if (StrUtil.isEmpty(token)) { + token = (String) testGetoken(address).get("token"); + } + String spk = SYSTEM_CACHE.get("SERVER_PUBLIC_KEY"); + // 封装请求头参数 + RSA rsa = new RSA(null, spk); + // 对用户信息进行加密传输,暂仅支持传输OA用户ID + String encryptUserid = rsa.encryptBase64("1", CharsetUtil.CHARSET_UTF_8, KeyType.PublicKey); + List list = JSONObject.parseObject(jsonParams, List.class); + HttpUtils httpUtils = new HttpUtils(); + HashMap headers = new HashMap<>(); + headers.put("appid", APPID); + headers.put("token", token); + headers.put("userid", encryptUserid); + headers.put("Content-Type", "application/x-www-form-urlencoded; charset=utf-8"); + log.info("请求头 = > " + headers); // try { // ResponeVo responeVo = httpUtils.apiPostObject(address + api, list, headers); // log.info("reslut => " + JSONObject.toJSON(responeVo)); // } catch (Exception e) { // log.error("e => " + e.getMessage()); // } - return ""; - } - - @Test - public void testF() { - Map test = Util.getProperties2Map("test"); - log.info("test => " + JSONObject.toJSONString(test)); - } - - + return ""; + } + + @Test + public void testF() { + Map test = Util.getProperties2Map("test"); + log.info("test => " + JSONObject.toJSONString(test)); + } + + } diff --git a/src/test/java/youhong/ai/utiltest/GenericTest.java b/src/test/java/youhong/ai/utiltest/GenericTest.java index 1369871..b88d6ed 100644 --- a/src/test/java/youhong/ai/utiltest/GenericTest.java +++ b/src/test/java/youhong/ai/utiltest/GenericTest.java @@ -4,6 +4,7 @@ import aiyh.utils.GenerateFileUtil; import aiyh.utils.Util; import aiyh.utils.tool.cn.hutool.core.collection.CollectionUtil; import basetest.BaseTest; +import com.alibaba.fastjson.JSON; import org.apache.poi.hssf.util.HSSFColor; import org.apache.poi.ss.usermodel.*; import org.apache.poi.xssf.streaming.SXSSFCell; @@ -11,6 +12,8 @@ import org.apache.poi.xssf.streaming.SXSSFRow; import org.apache.poi.xssf.streaming.SXSSFSheet; import org.apache.poi.xssf.streaming.SXSSFWorkbook; import org.junit.Test; +import weaver.soa.workflow.request.RequestInfo; +import weaver.soa.workflow.request.RequestService; import weaver.youhong.ai.haripijiu.action.sapdocking.VoucherPayableNewAction; import java.lang.reflect.Field; @@ -172,12 +175,8 @@ public class GenericTest extends BaseTest { if (length >= columnWidth && length < 256 * 256) { sheet.setColumnWidth(colIndex, length); } - if (colIndex == 1 || colIndex == 2 || colIndex == 3) { - style.setFillForegroundColor(HSSFColor.HSSFColorPredefined.RED.getIndex()); - } else { - style.setFillBackgroundColor(HSSFColor.HSSFColorPredefined.BLACK.getIndex()); - } - style.setFillPattern(FillPatternType.SOLID_FOREGROUND); + style.setFillBackgroundColor(HSSFColor.HSSFColorPredefined.BLACK.getIndex()); + style.setFillPattern(FillPatternType.BRICKS); return style; } @@ -215,6 +214,10 @@ public class GenericTest extends BaseTest { if (length >= columnWidth && length < 256 * 256) { sheet.setColumnWidth(colIndex, length); } + if (rowIndex % 2 == 0) { + style.setFillForegroundColor(HSSFColor.HSSFColorPredefined.GREY_25_PERCENT.getIndex()); + style.setFillPattern(FillPatternType.SOLID_FOREGROUND); + } return style; } @@ -241,4 +244,11 @@ public class GenericTest extends BaseTest { } return count; } + + @Test + public void tesetAction() { + RequestService requestService = new RequestService(); + RequestInfo request = requestService.getRequest(433436); + System.out.println(JSON.toJSONString(request)); + } } \ No newline at end of file diff --git a/src/test/java/youhong/ai/yashilandai/GroupByTime.java b/src/test/java/youhong/ai/yashilandai/GroupByTime.java index 7c02caf..0ae96da 100644 --- a/src/test/java/youhong/ai/yashilandai/GroupByTime.java +++ b/src/test/java/youhong/ai/yashilandai/GroupByTime.java @@ -104,14 +104,6 @@ public class GroupByTime { public static Map>> groupData(List> dataList) { // 根据type进行分组 - // Map>> typeMap = new HashMap<>(); - // for (Map data : dataList) { - // String type = data.get("type").toString(); - // if (!typeMap.containsKey(type)) { - // typeMap.put(type, new ArrayList<>()); - // } - // typeMap.get(type).add(data); - // } Map>> typeMap = dataList.stream() .collect(Collectors.groupingBy(item -> String.valueOf(item.get("type")))); // 对每个type的数据进行日期分组 diff --git a/src/test/java/youhong/ai/yashilandai/MyTest.java b/src/test/java/youhong/ai/yashilandai/MyTest.java index fe496d9..c67357e 100644 --- a/src/test/java/youhong/ai/yashilandai/MyTest.java +++ b/src/test/java/youhong/ai/yashilandai/MyTest.java @@ -1,13 +1,11 @@ package youhong.ai.yashilandai; +import aiyh.utils.Util; import basetest.BaseTest; -import com.alibaba.fastjson.JSON; +import org.junit.Test; +import youhong.ai.mymapper.util.ScriptUtil; -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.ArrayList; import java.util.HashMap; -import java.util.List; import java.util.Map; /** @@ -18,7 +16,18 @@ import java.util.Map; * @author youHong.ai */ public class MyTest extends BaseTest { - + + + @Test + public void test() { + Map map = new HashMap<>(); + map.put("test", "100"); + map.put("obj", "20"); + System.out.println(ScriptUtil.invokeScript("test", map)); + System.out.println(ScriptUtil.invokeScript("obj", map)); + System.out.println(ScriptUtil.invokeScript("test * obj", map)); + System.out.println(Double.parseDouble(Util.null2String(Util.null2String(null), "0.0"))); + } } diff --git a/src/test/java/youhong/ai/yihong/YiHongTest.java b/src/test/java/youhong/ai/yihong/YiHongTest.java index 4539fa9..30e826a 100644 --- a/src/test/java/youhong/ai/yihong/YiHongTest.java +++ b/src/test/java/youhong/ai/yihong/YiHongTest.java @@ -7,6 +7,10 @@ import org.junit.Test; import weaver.youhong.ai.yihong.formmode.stagediagram.mapper.ModeExpandSaveActionMapper; import weaver.youhong.ai.yihong.formmode.stagediagram.pojo.StageUpdateFieldConfig; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + /** *

屹宏测试

* @@ -23,4 +27,12 @@ public class YiHongTest extends BaseTest { StageUpdateFieldConfig testWrite = mapper.selectConfigUpdate("test_write"); System.out.println(JSON.toJSONString(testWrite)); } + + + @Test + public void test2() { + List strings = Arrays.asList("1", "2", "3", "4"); + System.out.println(strings.stream().filter(item -> !item.equals("2")).collect(Collectors.toList())); + + } } diff --git a/view-sql-server.sql b/view-sql-server.sql index 8bf2a25..53de47a 100644 --- a/view-sql-server.sql +++ b/view-sql-server.sql @@ -1,51 +1,49 @@ -- 流程类型视图,可用于数据集成或流览按钮 -if exists (select * from sysobjects where name = 'workflow_type_info_view') +if +exists (select * from sysobjects where name = 'workflow_type_info_view') drop view workflow_type_info_view -go + go create view workflow_type_info_view as select wb.id, wb.workflowname, wt.typename, wb.workflowdesc, - (IIF(wb.version is null, 1, wb.version)) version + (case when wb.version is null then 1 else wb.version end) version from workflow_base wb - RIGHT JOIN workflow_type wt on wb.workflowtype = wt.id -go + RIGHT JOIN workflow_type wt on wb.workflowtype = wt.id go -- 流程表单视图,用于流览按钮或数据集成,配置流程类型表可以用字段联动获取流程表表名 if exists (select * from sysobjects where name = 'workflow_table_view') - drop view workflow_table_view -go +drop view workflow_table_view + go create view workflow_table_view as select base.id, base.workflowname, base.formid, bill.tablename, - (IIF(base.version is null, 1, base.version)) version + (case when base.version is null then 1 else base.version end) version from workflow_bill bill - join workflow_base base on base.formid = bill.id -go + join workflow_base base on base.formid = bill.id go -- 流程明细表信息,可用流程主表查询对应的明细表信息,用于流览框 if exists (select * from sysobjects where name = 'workflow_detail_table_view') - drop view workflow_detail_table_view -go +drop view workflow_detail_table_view + go create view workflow_detail_table_view as -select (bill.id + '-' + base.id) id, - bill.id bill_id, - base.id workflow_id, +select (bill.id + '-' + base.id) id, + bill.id bill_id, + base.id workflow_id, base.workflowname, - base.formid main_formid, + base.formid main_formid, bill.tablename from workflow_billdetailtable bill - join workflow_base base on base.formid = bill.billid -go + join workflow_base base on base.formid = bill.billid go -- 流程和建模字段视图,更具流程和建模的billid可以查询流程和建模中的字段信息 if exists (select * from sysobjects where name = 'workflow_field_table_view') - drop view workflow_field_table_view -go +drop view workflow_field_table_view + go create view workflow_field_table_view as select wb.id, wb.fieldname, @@ -57,41 +55,39 @@ select wb.id, then (select distinct tablename from workflow_bill where id = wb.billid) else wb.detailtable end - ) tablename, + ) tablename, billid, ( case when wb.detailtable = '' then 'main table' when wb.detailtable is null then 'main table' else wb.detailtable end - ) showtablename, + ) showtablename, (case when wb.fieldhtmltype = '1' then '单行文本框' when wb.FIELDHTMLTYPE = '2' then '多行文本框' when wb.FIELDHTMLTYPE = '3' then '流览框' when wb.FIELDHTMLTYPE = '4' then 'check框' when wb.FIELDHTMLTYPE = '5' then '选择框' - else '附件上传' end) fieldhtmltype + else '附件上传' end) fieldhtmltype from workflow_billfield wb - left join htmllabelindex ht on wb.fieldlabel = ht.id -go + left join htmllabelindex ht on wb.fieldlabel = ht.id go -- 建模表信息视图 if exists (select * from sysobjects where name = 'mode_bill_info_view') - drop view mode_bill_info_view -go +drop view mode_bill_info_view + go create view mode_bill_info_view as select bill.id, bill.tablename, hti.indexdesc from workflow_bill bill left join htmllabelindex hti on hti.id = bill.namelabel where bill.id < 0 - and bill.tablename like 'uf%' -go + and bill.tablename like 'uf%' go -- 流程节点信息视图 if exists (select * from sysobjects where name = 'workflow_node_info_view') - drop view workflow_node_info_view -go +drop view workflow_node_info_view + go create view workflow_node_info_view as select distinct nb.id, nb.nodename, @@ -100,4 +96,4 @@ select distinct nb.id, from workflow_nodebase nb left join workflow_flownode fn on nb.id = fn.nodeid left join workflow_base wb on wb.id = fn.workflowid -go + go diff --git a/常用信息.md b/常用信息.md index fa60097..0dad4cb 100644 --- a/常用信息.md +++ b/常用信息.md @@ -67,6 +67,16 @@ git pull 远程服务名称 分支名称 # git pull origin dev ``` +## 移动端模拟地址 + +| 描述 | 移动端模拟地址 | +|--------|----------------------------------------------------------------------------------| +| 流程代办页面 | /spa/workflow/static4mobile/index.html#/center/doing | +| 工作台 | /spa/coms/static4mobile/index.html#/menu-preview?id=appDefaultPage&checkAccess=1 | +| 新建流程 | /spa/workflow/static4mobile/index.html#/add | +| 日程 | /spa/workplan/static4mobile/index.html#/calendar/myCalendar | +| 我的请求 | /spa/workflow/static4mobile/index.html#/center/mine | + ## 常用代码 ### 前端 @@ -206,7 +216,7 @@ myComp.setState({test1: test2}); ```js $(() => { - if (window.location.hash.indexOf("${appId}_organization-chart") !== -1) { + if (window.location.hash.indexOf("${appId}_") !== -1) { loadCssArr(['index.css']) /* ******************** 下面两个文件为开发新页面时候,如果用户登陆超时,用于集成系统登陆弹窗的依赖和css样式文件 ******************* */ // loadJs('/spa/portal/public/index.js')