From 01e310a6e708b20fb59826bcf65dfd74f80aae4f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E9=99=88=E7=B2=BE=E5=8D=8E?= <842761733@qq.com>
Date: Fri, 29 Nov 2019 14:41:52 +0800
Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=EF=BC=9A=E9=A2=84=E8=A7=88UR?=
=?UTF-8?q?L=E7=89=B9=E6=AE=8A=E5=AD=97=E4=BD=93=E4=BD=BF=E7=94=A8Hutool?=
=?UTF-8?q?=E8=A7=A3=E5=86=B3=E6=96=B9=E6=A1=88?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../main/java/cn/keking/hutool/HexUtil.java | 388 ++++++++++++++++++
.../main/java/cn/keking/hutool/StrUtil.java | 283 +++++++++++++
.../java/cn/keking/hutool/URLEncoder.java | 232 +++++++++++
.../main/java/cn/keking/hutool/URLUtil.java | 70 ++++
.../java/cn/keking/utils/DownloadUtils.java | 91 +---
5 files changed, 975 insertions(+), 89 deletions(-)
create mode 100644 jodconverter-web/src/main/java/cn/keking/hutool/HexUtil.java
create mode 100644 jodconverter-web/src/main/java/cn/keking/hutool/StrUtil.java
create mode 100644 jodconverter-web/src/main/java/cn/keking/hutool/URLEncoder.java
create mode 100644 jodconverter-web/src/main/java/cn/keking/hutool/URLUtil.java
diff --git a/jodconverter-web/src/main/java/cn/keking/hutool/HexUtil.java b/jodconverter-web/src/main/java/cn/keking/hutool/HexUtil.java
new file mode 100644
index 00000000..34d75f8d
--- /dev/null
+++ b/jodconverter-web/src/main/java/cn/keking/hutool/HexUtil.java
@@ -0,0 +1,388 @@
+package cn.keking.hutool;
+
+import java.awt.*;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * 十六进制(简写为hex或下标16)在数学中是一种逢16进1的进位制,一般用数字0到9和字母A到F表示(其中:A~F即10~15)。
+ * 例如十进制数57,在二进制写作111001,在16进制写作39。
+ * 像java,c这样的语言为了区分十六进制和十进制数值,会在十六进制数的前面加上 0x,比如0x20是十进制的32,而不是十进制的20
+ *
+ * 参考:https://my.oschina.net/xinxingegeya/blog/287476
+ *
+ * @author Looly
+ */
+public class HexUtil {
+
+ /**
+ * 用于建立十六进制字符的输出的小写字符数组
+ */
+ private static final char[] DIGITS_LOWER = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
+ /**
+ * 用于建立十六进制字符的输出的大写字符数组
+ */
+ private static final char[] DIGITS_UPPER = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
+
+ /**
+ * 判断给定字符串是否为16进制数
+ * 如果是,需要使用对应数字类型对象的decode方法解码
+ * 例如:{@code Integer.decode}方法解码int类型的16进制数字
+ *
+ * @param value 值
+ * @return 是否为16进制
+ */
+ public static boolean isHexNumber(String value) {
+ final int index = (value.startsWith("-") ? 1 : 0);
+ if (value.startsWith("0x", index) || value.startsWith("0X", index) || value.startsWith("#", index)) {
+ try {
+ Long.decode(value);
+ } catch (NumberFormatException e) {
+ return false;
+ }
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ // ---------------------------------------------------------------------------------------------------- encode
+
+ /**
+ * 将字节数组转换为十六进制字符数组
+ *
+ * @param data byte[]
+ * @return 十六进制char[]
+ */
+ public static char[] encodeHex(byte[] data) {
+ return encodeHex(data, true);
+ }
+
+ /**
+ * 将字节数组转换为十六进制字符数组
+ *
+ * @param str 字符串
+ * @param charset 编码
+ * @return 十六进制char[]
+ */
+ public static char[] encodeHex(String str, Charset charset) {
+ return encodeHex(StrUtil.bytes(str, charset), true);
+ }
+
+ /**
+ * 将字节数组转换为十六进制字符数组
+ *
+ * @param data byte[]
+ * @param toLowerCase true 传换成小写格式 , false 传换成大写格式
+ * @return 十六进制char[]
+ */
+ public static char[] encodeHex(byte[] data, boolean toLowerCase) {
+ return encodeHex(data, toLowerCase ? DIGITS_LOWER : DIGITS_UPPER);
+ }
+
+ /**
+ * 将字节数组转换为十六进制字符串
+ *
+ * @param data byte[]
+ * @return 十六进制String
+ */
+ public static String encodeHexStr(byte[] data) {
+ return encodeHexStr(data, true);
+ }
+
+ /**
+ * 将字节数组转换为十六进制字符串,结果为小写
+ *
+ * @param data 被编码的字符串
+ * @param charset 编码
+ * @return 十六进制String
+ */
+ public static String encodeHexStr(String data, Charset charset) {
+ return encodeHexStr(StrUtil.bytes(data, charset), true);
+ }
+
+ /**
+ * 将字节数组转换为十六进制字符串,结果为小写,默认编码是UTF-8
+ *
+ * @param data 被编码的字符串
+ * @return 十六进制String
+ */
+ public static String encodeHexStr(String data) {
+ return encodeHexStr(data, StandardCharsets.UTF_8);
+ }
+
+ /**
+ * 将字节数组转换为十六进制字符串
+ *
+ * @param data byte[]
+ * @param toLowerCase true 传换成小写格式 , false 传换成大写格式
+ * @return 十六进制String
+ */
+ public static String encodeHexStr(byte[] data, boolean toLowerCase) {
+ return encodeHexStr(data, toLowerCase ? DIGITS_LOWER : DIGITS_UPPER);
+ }
+
+ // ---------------------------------------------------------------------------------------------------- decode
+
+ /**
+ * 将十六进制字符数组转换为字符串,默认编码UTF-8
+ *
+ * @param hexStr 十六进制String
+ * @return 字符串
+ */
+ public static String decodeHexStr(String hexStr) {
+ return decodeHexStr(hexStr, StandardCharsets.UTF_8);
+ }
+
+ /**
+ * 将十六进制字符数组转换为字符串
+ *
+ * @param hexStr 十六进制String
+ * @param charset 编码
+ * @return 字符串
+ */
+ public static String decodeHexStr(String hexStr, Charset charset) {
+ if (StrUtil.isEmpty(hexStr)) {
+ return hexStr;
+ }
+ return decodeHexStr(hexStr.toCharArray(), charset);
+ }
+
+ /**
+ * 将十六进制字符数组转换为字符串
+ *
+ * @param hexData 十六进制char[]
+ * @param charset 编码
+ * @return 字符串
+ */
+ public static String decodeHexStr(char[] hexData, Charset charset) {
+ return StrUtil.str(decodeHex(hexData), charset);
+ }
+
+ /**
+ * 将十六进制字符数组转换为字节数组
+ *
+ * @param hexData 十六进制char[]
+ * @return byte[]
+ * @throws RuntimeException 如果源十六进制字符数组是一个奇怪的长度,将抛出运行时异常
+ */
+ public static byte[] decodeHex(char[] hexData) {
+
+ int len = hexData.length;
+
+ if ((len & 0x01) != 0) {
+ throw new RuntimeException("Odd number of characters.");
+ }
+
+ byte[] out = new byte[len >> 1];
+
+ // two characters form the hex value.
+ for (int i = 0, j = 0; j < len; i++) {
+ int f = toDigit(hexData[j], j) << 4;
+ j++;
+ f = f | toDigit(hexData[j], j);
+ j++;
+ out[i] = (byte) (f & 0xFF);
+ }
+
+ return out;
+ }
+
+ /**
+ * 将十六进制字符串解码为byte[]
+ *
+ * @param hexStr 十六进制String
+ * @return byte[]
+ */
+ public static byte[] decodeHex(String hexStr) {
+ if (StrUtil.isEmpty(hexStr)) {
+ return null;
+ }
+ return decodeHex(hexStr.toCharArray());
+ }
+
+ // ---------------------------------------------------------------------------------------- Color
+
+ /**
+ * 将{@link Color}编码为Hex形式
+ *
+ * @param color {@link Color}
+ * @return Hex字符串
+ * @since 3.0.8
+ */
+ public static String encodeColor(Color color) {
+ return encodeColor(color, "#");
+ }
+
+ /**
+ * 将{@link Color}编码为Hex形式
+ *
+ * @param color {@link Color}
+ * @param prefix 前缀字符串,可以是#、0x等
+ * @return Hex字符串
+ * @since 3.0.8
+ */
+ public static String encodeColor(Color color, String prefix) {
+ final StringBuilder builder = new StringBuilder(prefix);
+ String colorHex;
+ colorHex = Integer.toHexString(color.getRed());
+ if (1 == colorHex.length()) {
+ builder.append('0');
+ }
+ builder.append(colorHex);
+ colorHex = Integer.toHexString(color.getGreen());
+ if (1 == colorHex.length()) {
+ builder.append('0');
+ }
+ builder.append(colorHex);
+ colorHex = Integer.toHexString(color.getBlue());
+ if (1 == colorHex.length()) {
+ builder.append('0');
+ }
+ builder.append(colorHex);
+ return builder.toString();
+ }
+
+ /**
+ * 将Hex颜色值转为
+ *
+ * @param hexColor 16进制颜色值,可以以#开头,也可以用0x开头
+ * @return {@link Color}
+ * @since 3.0.8
+ */
+ public static Color decodeColor(String hexColor) {
+ return Color.decode(hexColor);
+ }
+
+ /**
+ * 将指定int值转换为Unicode字符串形式,常用于特殊字符(例如汉字)转Unicode形式
+ * 转换的字符串如果u后不足4位,则前面用0填充,例如:
+ *
+ *
+ * '我' =》\u4f60 + *+ * + * @param value int值,也可以是char + * @return Unicode表现形式 + */ + public static String toUnicodeHex(int value) { + final StringBuilder builder = new StringBuilder(6); + + builder.append("\\u"); + String hex = toHex(value); + int len = hex.length(); + if (len < 4) { + builder.append("0000", 0, 4 - len);// 不足4位补0 + } + builder.append(hex); + + return builder.toString(); + } + + /** + * 将指定char值转换为Unicode字符串形式,常用于特殊字符(例如汉字)转Unicode形式
+ * '我' =》\u4f60 + *+ * + * @param ch char值 + * @return Unicode表现形式 + * @since 4.0.1 + */ + public static String toUnicodeHex(char ch) { + return "\\u" +// + DIGITS_LOWER[(ch >> 12) & 15] +// + DIGITS_LOWER[(ch >> 8) & 15] +// + DIGITS_LOWER[(ch >> 4) & 15] +// + DIGITS_LOWER[(ch) & 15]; + } + + /** + * 转为16进制字符串 + * + * @param value int值 + * @return 16进制字符串 + * @since 4.4.1 + */ + public static String toHex(int value) { + return Integer.toHexString(value); + } + + /** + * 转为16进制字符串 + * + * @param value int值 + * @return 16进制字符串 + * @since 4.4.1 + */ + public static String toHex(long value) { + return Long.toHexString(value); + } + + /** + * 将byte值转为16进制并添加到{@link StringBuilder}中 + * + * @param builder {@link StringBuilder} + * @param b byte + * @param toLowerCase 是否使用小写 + * @since 4.4.1 + */ + public static void appendHex(StringBuilder builder, byte b, boolean toLowerCase) { + final char[] toDigits = toLowerCase ? DIGITS_LOWER : DIGITS_UPPER; + + int high = (b & 0xf0) >>> 4;//高位 + int low = b & 0x0f;//低位 + builder.append(toDigits[high]); + builder.append(toDigits[low]); + } + + // ---------------------------------------------------------------------------------------- Private method start + + /** + * 将字节数组转换为十六进制字符串 + * + * @param data byte[] + * @param toDigits 用于控制输出的char[] + * @return 十六进制String + */ + private static String encodeHexStr(byte[] data, char[] toDigits) { + return new String(encodeHex(data, toDigits)); + } + + /** + * 将字节数组转换为十六进制字符数组 + * + * @param data byte[] + * @param toDigits 用于控制输出的char[] + * @return 十六进制char[] + */ + private static char[] encodeHex(byte[] data, char[] toDigits) { + final int len = data.length; + final char[] out = new char[len << 1];//len*2 + // two characters from the hex value. + for (int i = 0, j = 0; i < len; i++) { + out[j++] = toDigits[(0xF0 & data[i]) >>> 4];// 高位 + out[j++] = toDigits[0x0F & data[i]];// 低位 + } + return out; + } + + /** + * 将十六进制字符转换成一个整数 + * + * @param ch 十六进制char + * @param index 十六进制字符在字符数组中的位置 + * @return 一个整数 + * @throws RuntimeException 当ch不是一个合法的十六进制字符时,抛出运行时异常 + */ + private static int toDigit(char ch, int index) { + int digit = Character.digit(ch, 16); + if (digit == -1) { + throw new RuntimeException("Illegal hexadecimal character " + ch + " at index " + index); + } + return digit; + } + // ---------------------------------------------------------------------------------------- Private method end +} \ No newline at end of file diff --git a/jodconverter-web/src/main/java/cn/keking/hutool/StrUtil.java b/jodconverter-web/src/main/java/cn/keking/hutool/StrUtil.java new file mode 100644 index 00000000..91d62c5b --- /dev/null +++ b/jodconverter-web/src/main/java/cn/keking/hutool/StrUtil.java @@ -0,0 +1,283 @@ +package cn.keking.hutool; + +import java.nio.charset.Charset; + +/** + * 字符串工具类 + * + * @author xiaoleilu + * + */ +public class StrUtil { + + public static final String EMPTY = ""; + + /** + * 是否空白符
null,则返回指定默认字符串,否则返回字符串本身。
+ *
+ *
+ * nullToDefault(null, "default") = "default"
+ * nullToDefault("", "default") = ""
+ * nullToDefault(" ", "default") = " "
+ * nullToDefault("bat", "default") = "bat"
+ *
+ *
+ * @param str 要转换的字符串
+ * @param defaultStr 默认字符串
+ *
+ * @return 字符串本身或指定的默认字符串
+ */
+ public static String nullToDefault(CharSequence str, String defaultStr) {
+ return (str == null) ? defaultStr : str.toString();
+ }
+
+ /**
+ * 当给定字符串为null时,转换为Empty
+ *
+ * @param str 被转换的字符串
+ * @return 转换后的字符串
+ */
+ public static String nullToEmpty(CharSequence str) {
+ return nullToDefault(str, EMPTY);
+ }
+}
diff --git a/jodconverter-web/src/main/java/cn/keking/hutool/URLEncoder.java b/jodconverter-web/src/main/java/cn/keking/hutool/URLEncoder.java
new file mode 100644
index 00000000..ccb2b92a
--- /dev/null
+++ b/jodconverter-web/src/main/java/cn/keking/hutool/URLEncoder.java
@@ -0,0 +1,232 @@
+package cn.keking.hutool;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.Serializable;
+import java.nio.charset.Charset;
+import java.util.BitSet;
+
+/**
+ * URL编码,数据内容的类型是 application/x-www-form-urlencoded。
+ *
+ * + * 1.字符"a"-"z","A"-"Z","0"-"9",".","-","*",和"_" 都不会被编码; + * 2.将空格转换为%20 ; + * 3.将非文本内容转换成"%xy"的形式,xy是两位16进制的数值; + * 4.在每个 name=value 对之间放置 & 符号。 + *+ * + * @author looly, + * + */ +public class URLEncoder implements Serializable{ + private static final long serialVersionUID = 1L; + + // --------------------------------------------------------------------------------------------- Static method start + /** + * 默认{@link URLEncoder}
+ * pchar = unreserved(不处理) / pct-encoded / sub-delims(子分隔符) / ":" / "@"
+ * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
+ * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "="
+ *
+ */
+ public static final URLEncoder DEFAULT = createDefault();
+
+ /**
+ * 用于查询语句的{@link URLEncoder}+ * 0x20 ' ' =》 '+' + * 0x2A, 0x2D, 0x2E, 0x30 to 0x39, 0x41 to 0x5A, 0x5F, 0x61 to 0x7A as-is + * '*', '-', '.', '0' to '9', 'A' to 'Z', '_', 'a' to 'z' Also '=' and '&' 不编码 + * 其它编码为 %nn 形式 + *+ * + * 详细见:https://www.w3.org/TR/html5/forms.html#application/x-www-form-urlencoded-encoding-algorithm + */ + public static final URLEncoder QUERY = createQuery(); + + /** + * 创建默认{@link URLEncoder}
+ * pchar = unreserved(不处理) / pct-encoded / sub-delims(子分隔符) / ":" / "@"
+ * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
+ * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "="
+ *
+ *
+ * @return {@link URLEncoder}
+ */
+ public static URLEncoder createDefault() {
+ final URLEncoder encoder = new URLEncoder();
+ encoder.addSafeCharacter('-');
+ encoder.addSafeCharacter('.');
+ encoder.addSafeCharacter('_');
+ encoder.addSafeCharacter('~');
+ // Add the sub-delims
+ encoder.addSafeCharacter('!');
+ encoder.addSafeCharacter('$');
+ encoder.addSafeCharacter('&');
+ encoder.addSafeCharacter('\'');
+ encoder.addSafeCharacter('(');
+ encoder.addSafeCharacter(')');
+ encoder.addSafeCharacter('*');
+ encoder.addSafeCharacter('+');
+ encoder.addSafeCharacter(',');
+ encoder.addSafeCharacter(';');
+ encoder.addSafeCharacter('=');
+ // Add the remaining literals
+ encoder.addSafeCharacter(':');
+ encoder.addSafeCharacter('@');
+ // Add '/' so it isn't encoded when we encode a path
+ encoder.addSafeCharacter('/');
+
+ return encoder;
+ }
+
+ /**
+ * 创建用于查询语句的{@link URLEncoder}+ * 0x20 ' ' =》 '+' + * 0x2A, 0x2D, 0x2E, 0x30 to 0x39, 0x41 to 0x5A, 0x5F, 0x61 to 0x7A as-is + * '*', '-', '.', '0' to '9', 'A' to 'Z', '_', 'a' to 'z' Also '=' and '&' 不编码 + * 其它编码为 %nn 形式 + *+ * + * 详细见:https://www.w3.org/TR/html5/forms.html#application/x-www-form-urlencoded-encoding-algorithm + * + * @return {@link URLEncoder} + */ + public static URLEncoder createQuery() { + final URLEncoder encoder = new URLEncoder(); + // Special encoding for space + encoder.setEncodeSpaceAsPlus(true); + // Alpha and digit are safe by default + // Add the other permitted characters + encoder.addSafeCharacter('*'); + encoder.addSafeCharacter('-'); + encoder.addSafeCharacter('.'); + encoder.addSafeCharacter('_'); + encoder.addSafeCharacter('='); + encoder.addSafeCharacter('&'); + + return encoder; + } + // --------------------------------------------------------------------------------------------- Static method end + + /** 存放安全编码 */ + private final BitSet safeCharacters; + /** 是否编码空格为+ */ + private boolean encodeSpaceAsPlus = false; + + /** + * 构造
+ * 1. 多个/替换为一个 + *+ * + * @param url URL字符串 + * @return 标准化后的URL字符串 + */ + public static String normalize(String url) { + return normalize(url, false); + } + + /** + * 标准化URL字符串,包括: + * + *
+ * 1. 多个/替换为一个 + *+ * + * @param url URL字符串 + * @param isEncodeBody 是否对URL中body部分的中文和特殊字符做转义(不包括http:和/) + * @return 标准化后的URL字符串 + * @since 4.4.1 + */ + public static String normalize(String url, boolean isEncodeBody) { + if (StrUtil.isBlank(url)) { + return url; + } + final int sepIndex = url.indexOf("://"); + String pre; + String body; + if (sepIndex > 0) { + pre = StrUtil.subPre(url, sepIndex + 3); + body = StrUtil.subSuf(url, sepIndex + 3); + } else { + pre = "http://"; + body = url; + } + + final int paramsSepIndex = StrUtil.indexOf(body, '?'); + String params = null; + if (paramsSepIndex > 0) { + params = StrUtil.subSuf(body, paramsSepIndex); + body = StrUtil.subPre(body, paramsSepIndex); + } + + // 去除开头的\或者/ + body = body.replaceAll("^[\\\\/]+", StrUtil.EMPTY); + // 替换多个\或/为单个/ + body = body.replace("\\", "/").replaceAll("//+", "/"); + if (isEncodeBody) { + body = URLEncoder.DEFAULT.encode(body, StandardCharsets.UTF_8); + } + return pre + body + StrUtil.nullToEmpty(params); + } +} \ No newline at end of file diff --git a/jodconverter-web/src/main/java/cn/keking/utils/DownloadUtils.java b/jodconverter-web/src/main/java/cn/keking/utils/DownloadUtils.java index 26e721e6..21ba2e2d 100644 --- a/jodconverter-web/src/main/java/cn/keking/utils/DownloadUtils.java +++ b/jodconverter-web/src/main/java/cn/keking/utils/DownloadUtils.java @@ -1,6 +1,7 @@ package cn.keking.utils; import cn.keking.config.ConfigConstants; +import cn.keking.hutool.URLUtil; import cn.keking.model.FileAttribute; import cn.keking.model.ReturnResponse; import org.slf4j.Logger; @@ -29,11 +30,6 @@ public class DownloadUtils { private static final String URL_PARAM_FTP_CONTROL_ENCODING = "ftp.control.encoding"; /** - * 一开始测试的时候发现有些文件没有下载下来,而有些可以;当时也是郁闷了好一阵,但是最终还是不得解 - * 再次测试的时候,通过前台对比url发现,原来参数中有+号特殊字符存在,但是到后之后却变成了空格,突然恍然大悟 - * 应该是转义出了问题,url转义中会把+号当成空格来计算,所以才会出现这种情况,遂想要通过整体替换空格为加号,因为url - * 中的参数部分是不会出现空格的,但是文件名中就不好确定了,所以只对url参数部分做替换 - * 注: 针对URLEncoder.encode(s,charset)会将空格转成+的情况需要做下面的替换工作 * @param fileAttribute * @return */ @@ -43,12 +39,7 @@ public class DownloadUtils { ReturnResponse