123
This commit is contained in:
@@ -53,6 +53,12 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.ruoyi</groupId>
|
<groupId>com.ruoyi</groupId>
|
||||||
<artifactId>ruoyi-system</artifactId>
|
<artifactId>ruoyi-system</artifactId>
|
||||||
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<artifactId>forest-core</artifactId>
|
||||||
|
<groupId>com.dtflys.forest</groupId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
|
|||||||
@@ -294,7 +294,7 @@ public class SysPushServiceImpl extends ServiceImpl<SysPushMapper, SysPush> impl
|
|||||||
if(imResp != null && imResp.isSuccess()){
|
if(imResp != null && imResp.isSuccess()){
|
||||||
sysPushLog.setStatus(SystemPushLogStatusEnum.SEND_SUCCESS.getCode());
|
sysPushLog.setStatus(SystemPushLogStatusEnum.SEND_SUCCESS.getCode());
|
||||||
}else{
|
}else{
|
||||||
sysPushLog.setStatus(SystemPushLogStatusEnum.SEND_SUCCESS.getCode());
|
sysPushLog.setStatus(SystemPushLogStatusEnum.SEND_FAIL.getCode());
|
||||||
}
|
}
|
||||||
sysPushLog.setResult(JSON.toJSONString(imResp));
|
sysPushLog.setResult(JSON.toJSONString(imResp));
|
||||||
}catch (Exception e){
|
}catch (Exception e){
|
||||||
|
|||||||
@@ -23,17 +23,17 @@
|
|||||||
<artifactId>ruoyi-common</artifactId>
|
<artifactId>ruoyi-common</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<!--<dependency>
|
||||||
<groupId>org.dromara.sms4j</groupId>
|
<groupId>org.dromara.sms4j</groupId>
|
||||||
<artifactId>sms4j-spring-boot-starter</artifactId>
|
<artifactId>sms4j-spring-boot-starter</artifactId>
|
||||||
<exclusions>
|
<exclusions>
|
||||||
<!-- 排除京东短信内存在的fastjson等待作者后续修复 -->
|
<!– 排除京东短信内存在的fastjson等待作者后续修复 –>
|
||||||
<exclusion>
|
<exclusion>
|
||||||
<groupId>com.alibaba</groupId>
|
<groupId>com.alibaba</groupId>
|
||||||
<artifactId>fastjson</artifactId>
|
<artifactId>fastjson</artifactId>
|
||||||
</exclusion>
|
</exclusion>
|
||||||
</exclusions>
|
</exclusions>
|
||||||
</dependency>
|
</dependency>-->
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
|
|||||||
@@ -17,15 +17,14 @@ public class YunExecutor {
|
|||||||
public static Executor YUN_EXECUTOR;
|
public static Executor YUN_EXECUTOR;
|
||||||
|
|
||||||
static {
|
static {
|
||||||
ThreadPoolExecutor roomExecutor = new ThreadPoolExecutor(CPU_NUM,
|
YUN_EXECUTOR = new ThreadPoolExecutor(CPU_NUM,
|
||||||
CPU_NUM << 2,
|
CPU_NUM << 2,
|
||||||
5,
|
5,
|
||||||
TimeUnit.SECONDS,
|
TimeUnit.SECONDS,
|
||||||
new ArrayBlockingQueue<>(5),
|
new ArrayBlockingQueue<>(5),
|
||||||
init("yunxinThreadPoll-%d"),
|
init("yunxinThreadPoll-%d"),
|
||||||
new ThreadPoolExecutor.CallerRunsPolicy());
|
new ThreadPoolExecutor.CallerRunsPolicy());
|
||||||
YUN_EXECUTOR = TtlExecutors.getTtlExecutor(roomExecutor);
|
// YUN_EXECUTOR = TtlExecutors.getTtlExecutor(roomExecutor);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ThreadFactory init(String nameFormat){
|
private static ThreadFactory init(String nameFormat){
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
package com.ruoyi.yunxin.client;
|
package com.ruoyi.yunxin.client;
|
||||||
|
|
||||||
import com.dtflys.forest.annotation.BaseRequest;
|
import com.dtflys.forest.annotation.*;
|
||||||
import com.dtflys.forest.annotation.Body;
|
import com.ruoyi.yunxin.convertor.CustomFormConvertor;
|
||||||
import com.dtflys.forest.annotation.Post;
|
|
||||||
import com.ruoyi.yunxin.interceptor.GlodonTokenInterceptor;
|
import com.ruoyi.yunxin.interceptor.GlodonTokenInterceptor;
|
||||||
import com.ruoyi.yunxin.req.SendAttachMsgReq;
|
import com.ruoyi.yunxin.req.SendAttachMsgReq;
|
||||||
import com.ruoyi.yunxin.req.SendBatchAttachMsgReq;
|
import com.ruoyi.yunxin.req.SendBatchAttachMsgReq;
|
||||||
@@ -21,6 +20,7 @@ public interface ImMessageClient {
|
|||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
@Post(url = "/nimserver/msg/sendMsg.action")
|
@Post(url = "/nimserver/msg/sendMsg.action")
|
||||||
|
@BodyType(type = "form", encoder = CustomFormConvertor.class)
|
||||||
YxDataR<SendMsgResp> sendMsg(@Body SendMsgReq req);
|
YxDataR<SendMsgResp> sendMsg(@Body SendMsgReq req);
|
||||||
|
|
||||||
@Post(url = "/nimserver/msg/sendBatchMsg.action")
|
@Post(url = "/nimserver/msg/sendBatchMsg.action")
|
||||||
|
|||||||
@@ -0,0 +1,254 @@
|
|||||||
|
package com.ruoyi.yunxin.convertor;
|
||||||
|
|
||||||
|
import com.dtflys.forest.config.ForestConfiguration;
|
||||||
|
import com.dtflys.forest.converter.ConvertOptions;
|
||||||
|
import com.dtflys.forest.converter.ForestConverter;
|
||||||
|
import com.dtflys.forest.converter.ForestEncoder;
|
||||||
|
import com.dtflys.forest.converter.json.ForestJsonConverter;
|
||||||
|
import com.dtflys.forest.http.ForestBody;
|
||||||
|
import com.dtflys.forest.http.ForestRequest;
|
||||||
|
import com.dtflys.forest.http.ForestRequestBody;
|
||||||
|
import com.dtflys.forest.http.Lazy;
|
||||||
|
import com.dtflys.forest.http.body.SupportFormUrlEncoded;
|
||||||
|
import com.dtflys.forest.mapping.MappingParameter;
|
||||||
|
import com.dtflys.forest.mapping.MappingTemplate;
|
||||||
|
import com.dtflys.forest.utils.ForestDataType;
|
||||||
|
import com.dtflys.forest.utils.ReflectUtils;
|
||||||
|
import com.dtflys.forest.utils.RequestNameValue;
|
||||||
|
import com.dtflys.forest.utils.StringUtils;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.lang.reflect.Array;
|
||||||
|
import java.lang.reflect.Type;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class CustomFormConvertor implements ForestConverter<String>, ForestEncoder {
|
||||||
|
|
||||||
|
private final ForestConfiguration configuration;
|
||||||
|
|
||||||
|
public CustomFormConvertor(ForestConfiguration configuration) {
|
||||||
|
this.configuration = configuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> T convertToJavaObject(String source, Type targetType) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> T convertToJavaObject(byte[] source, Class<T> targetType, Charset charset) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> T convertToJavaObject(byte[] source, Type targetType, Charset charset) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ForestDataType getDataType() {
|
||||||
|
return ForestDataType.FORM;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String encodeToString(Object obj) {
|
||||||
|
final ForestJsonConverter jsonConverter = configuration.getJsonConverter();
|
||||||
|
final Map<String, Object> map = jsonConverter.convertObjectToMap(obj);
|
||||||
|
final List<RequestNameValue> nameValueList = new LinkedList<>();
|
||||||
|
for (Map.Entry<String, Object> entry : map.entrySet()) {
|
||||||
|
final RequestNameValue nameValue = new RequestNameValue(entry.getKey(), MappingParameter.TARGET_BODY);
|
||||||
|
nameValue.setValue(entry.getValue());
|
||||||
|
nameValueList.add(nameValue);
|
||||||
|
}
|
||||||
|
final List<RequestNameValue> newNameValueList = processFromNameValueList(
|
||||||
|
null, nameValueList, configuration, ConvertOptions.defaultOptions());
|
||||||
|
return formUrlEncodedString(newNameValueList, StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理Form表单中的集合项
|
||||||
|
* @param newNameValueList 键值对列表
|
||||||
|
* @param configuration Forest配置
|
||||||
|
* @param name 表单项目名
|
||||||
|
* @param collection 集合对象
|
||||||
|
* @param target 请求目标位置
|
||||||
|
*/
|
||||||
|
protected void processFormCollectionItem(List<RequestNameValue> newNameValueList, ForestConfiguration configuration, String name, Collection collection, int target) {
|
||||||
|
int index = 0;
|
||||||
|
for (Iterator iterator = collection.iterator(); iterator.hasNext(); ) {
|
||||||
|
final Object item = iterator.next();
|
||||||
|
final String subName = name + "[" + index + "]";
|
||||||
|
processFormItem(newNameValueList, configuration, subName, item, target);
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理Form表单中的数组项
|
||||||
|
* @param newNameValueList 键值对列表
|
||||||
|
* @param configuration Forest配置
|
||||||
|
* @param name 表单项目名
|
||||||
|
* @param array 数组
|
||||||
|
* @param target 请求目标位置
|
||||||
|
*/
|
||||||
|
protected void processFormArrayItem(List<RequestNameValue> newNameValueList, ForestConfiguration configuration, String name, Object array, int target) {
|
||||||
|
final int len = Array.getLength(array);
|
||||||
|
for (int i = 0; i < len; i++) {
|
||||||
|
final Object item = Array.get(array, i);
|
||||||
|
final String subName = name + "[" + i + "]";
|
||||||
|
processFormItem(newNameValueList, configuration, subName, item, target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理Form表单中的Map项
|
||||||
|
* @param newNameValueList 键值对列表
|
||||||
|
* @param configuration Forest配置
|
||||||
|
* @param name 表单项目名
|
||||||
|
* @param map Map对象
|
||||||
|
* @param target 请求目标位置
|
||||||
|
*/
|
||||||
|
protected void processFormMapItem(List<RequestNameValue> newNameValueList, ForestConfiguration configuration, String name, Map map, int target) {
|
||||||
|
for (Iterator<Map.Entry> iterator = map.entrySet().iterator(); iterator.hasNext(); ) {
|
||||||
|
final Map.Entry entry = iterator.next();
|
||||||
|
final Object mapKey = entry.getKey();
|
||||||
|
final Object mapValue = entry.getValue();
|
||||||
|
final String subName = name + "[" + mapKey + "]";
|
||||||
|
processFormItem(newNameValueList, configuration, subName, mapValue, target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理Form表单中的项
|
||||||
|
* @param newNameValueList 键值对列表
|
||||||
|
* @param configuration Forest配置
|
||||||
|
* @param name 表单项目名
|
||||||
|
* @param value 表单项目值
|
||||||
|
* @param target 请求目标位置
|
||||||
|
*/
|
||||||
|
protected void processFormItem(List<RequestNameValue> newNameValueList, ForestConfiguration configuration, String name, Object value, int target) {
|
||||||
|
if (StringUtils.isEmpty(name) && value == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (value != null) {
|
||||||
|
final Class<?> itemClass = value.getClass();
|
||||||
|
boolean needCollapse = false;
|
||||||
|
if (value instanceof Collection) {
|
||||||
|
final Collection<?> collection = (Collection<?>) value;
|
||||||
|
if (collection.size() <= 8) {
|
||||||
|
for (Object item : collection) {
|
||||||
|
if (!ReflectUtils.isPrimaryType(item.getClass())) {
|
||||||
|
needCollapse = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (itemClass.isArray() && !ReflectUtils.isPrimaryArrayType(itemClass)) {
|
||||||
|
needCollapse = true;
|
||||||
|
}
|
||||||
|
if (needCollapse) {
|
||||||
|
if (value instanceof Collection) {
|
||||||
|
processFormCollectionItem(newNameValueList, configuration, name, (Collection) value, target);
|
||||||
|
} else if (itemClass.isArray()) {
|
||||||
|
processFormArrayItem(newNameValueList, configuration, name, value, target);
|
||||||
|
}
|
||||||
|
} else if (ReflectUtils.isPrimaryType(itemClass)
|
||||||
|
|| ReflectUtils.isPrimaryArrayType(itemClass)
|
||||||
|
|| value instanceof Collection) {
|
||||||
|
newNameValueList.add(new RequestNameValue(name, value, target));
|
||||||
|
} else if (value instanceof Map) {
|
||||||
|
processFormMapItem(newNameValueList, configuration, name, (Map) value, target);
|
||||||
|
} else {
|
||||||
|
Map<String, Object> itemAttrs = ReflectUtils.convertObjectToMap(value, configuration);
|
||||||
|
for (Map.Entry<String, Object> entry : itemAttrs.entrySet()) {
|
||||||
|
String subAttrName = entry.getKey();
|
||||||
|
Object subAttrValue = entry.getValue();
|
||||||
|
String subName = name + "." + subAttrName;
|
||||||
|
processFormItem(newNameValueList, configuration, subName, subAttrValue, target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理Form表单中的键值对列表
|
||||||
|
*
|
||||||
|
* @param request 请求对象
|
||||||
|
* @param nameValueList 键值对列表
|
||||||
|
* @param configuration Forest 配置对象
|
||||||
|
* @param options 转换选项
|
||||||
|
* @return 处理过的新键值对列表
|
||||||
|
*/
|
||||||
|
protected List<RequestNameValue> processFromNameValueList(
|
||||||
|
final ForestRequest request,
|
||||||
|
final List<RequestNameValue> nameValueList,
|
||||||
|
final ForestConfiguration configuration,
|
||||||
|
final ConvertOptions options) {
|
||||||
|
final List<RequestNameValue> newNameValueList = new LinkedList<>();
|
||||||
|
for (RequestNameValue nameValue : nameValueList) {
|
||||||
|
final String name = nameValue.getName();
|
||||||
|
if (options != null && options.shouldExclude(name)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Object value = nameValue.getValue();
|
||||||
|
if (Lazy.isEvaluatingLazyValue(value, request)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (options != null) {
|
||||||
|
value = options.getValue(value, request);
|
||||||
|
if (options.shouldIgnore(value)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
final int target = nameValue.getTarget();
|
||||||
|
processFormItem(newNameValueList, configuration, name, value, target);
|
||||||
|
}
|
||||||
|
return newNameValueList;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String formUrlEncodedString(List<RequestNameValue> nameValueList, Charset charset) {
|
||||||
|
final ForestJsonConverter jsonConverter = configuration.getJsonConverter();
|
||||||
|
final StringBuilder strBuilder = new StringBuilder();
|
||||||
|
for (int i = 0; i < nameValueList.size(); i++) {
|
||||||
|
final RequestNameValue nameValue = nameValueList.get(i);
|
||||||
|
if (!nameValue.isInBody()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
final String name = nameValue.getName();
|
||||||
|
strBuilder.append(name);
|
||||||
|
Object value = nameValue.getValue();
|
||||||
|
if (value != null) {
|
||||||
|
value = MappingTemplate.getFormValueString(jsonConverter, value);
|
||||||
|
strBuilder.append("=").append(CustomURLEncoder.FORM_VALUE.encode(String.valueOf(value), charset));
|
||||||
|
}
|
||||||
|
if (i < nameValueList.size() - 1) {
|
||||||
|
strBuilder.append("&");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return strBuilder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] encodeRequestBody(final ForestBody body, final Charset charset, final ConvertOptions options) {
|
||||||
|
final List<RequestNameValue> nameValueList = new LinkedList<>();
|
||||||
|
final Charset cs = charset != null ? charset : StandardCharsets.UTF_8;
|
||||||
|
final ForestRequest request = body.getRequest();
|
||||||
|
for (ForestRequestBody bodyItem : body) {
|
||||||
|
if (bodyItem instanceof SupportFormUrlEncoded) {
|
||||||
|
nameValueList.addAll(((SupportFormUrlEncoded) bodyItem).getNameValueList(request));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
final List<RequestNameValue> newNameValueList =
|
||||||
|
processFromNameValueList(request, nameValueList, configuration, options);
|
||||||
|
String strBody = formUrlEncodedString(newNameValueList, cs);
|
||||||
|
byte[] bytes = strBody.getBytes(cs);
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,286 @@
|
|||||||
|
package com.ruoyi.yunxin.convertor;
|
||||||
|
|
||||||
|
import com.dtflys.forest.exceptions.ForestRuntimeException;
|
||||||
|
import com.dtflys.forest.utils.StringUtils;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStreamWriter;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.BitSet;
|
||||||
|
|
||||||
|
public class CustomURLEncoder {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 空格字符
|
||||||
|
*/
|
||||||
|
private static final char SPACE = ' ';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 十六进制字符的大写字符数组
|
||||||
|
*/
|
||||||
|
private static final char[] HEX_DIGITS_UPPER = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* URI用户信息部分中不会被编码的字符集
|
||||||
|
*/
|
||||||
|
private static final char[] USER_INFO_EXCLUDED_CHARACTERS = {'-', '.', '_', '+', '!', '(', ')', '*', ':', '=', '%'};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* URI路径中不会被编码的字符集
|
||||||
|
*/
|
||||||
|
private static final char[] PATH_EXCLUDED_CHARACTERS = {'-', '.', '_', '+', '!', '(', ')', '[', ']', '*', '/', ':', '?', '=', '$', '@', '&', '%', '~'};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询参数值中不会被编码的字符集
|
||||||
|
*/
|
||||||
|
private static final char[] QUERY_VALUE_EXCLUDED_CHARACTERS = {'-', '.', '_', '+', '!', '(', ')', '[', ']', ',', '*', '/', ':', '?', '=', '%', '~'};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (带不转义大括号的) 查询参数值中不会被编码的字符集
|
||||||
|
*/
|
||||||
|
private static final char[] QUERY_VALUE_EXCLUDED_CHARACTERS_WITH_BRACE = {'-', '.', '_', '+', '!', '{', '}', '(', ')', '[', ']', ',', '*', '/', ':', '?', '=', '%', '~'};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询参数值中不会被编码的字符集
|
||||||
|
*/
|
||||||
|
private static final char[] X_WWW_FORM_URLENCODED_VALUE_EXCLUDED_CHARACTERS = {'-', '.', '_', '!', '{', '}', '[', ']', ',', '"', '*', '/', ':', '?', '#', '='};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 强制全编码中不会被编码的字符集
|
||||||
|
*/
|
||||||
|
private static final char[] ALL_EXCLUDED_CHARACTERS = {'*', '-', '.', '_'};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用于用户验证信息的编码{@link CustomURLEncoder}
|
||||||
|
*/
|
||||||
|
public static final CustomURLEncoder USER_INFO = createUserInfoUrlEncoder();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用于URI路径部分的编码{@link CustomURLEncoder}
|
||||||
|
*/
|
||||||
|
public static final CustomURLEncoder PATH = createPathUrlEncoder();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用于查询参数值部分的编码{@link CustomURLEncoder}
|
||||||
|
*/
|
||||||
|
public static final CustomURLEncoder QUERY_VALUE = createQueryValueUrlEncoder();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用于 (带不转义大括号的) 查询参数值部分的编码{@link CustomURLEncoder}
|
||||||
|
*/
|
||||||
|
public static final CustomURLEncoder QUERY_VALUE_WITH_BRACE = createQueryValueWithBraceUrlEncoder();
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用于表单参数值部分的编码{@link CustomURLEncoder}
|
||||||
|
*/
|
||||||
|
public static final CustomURLEncoder FORM_VALUE = createXWwwFormUrlEncodedValueUrlEncoder();
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 强制全编码的编码{@link CustomURLEncoder}
|
||||||
|
*/
|
||||||
|
public static final CustomURLEncoder ALL = createAllUrlEncoder();
|
||||||
|
|
||||||
|
|
||||||
|
private static CustomURLEncoder createURLEncoder(final char[] excludedCharacters, final boolean encodeSpaceAsPlus) {
|
||||||
|
final CustomURLEncoder encoder = new CustomURLEncoder();
|
||||||
|
encoder.setEncodeSpaceAsPlus(encodeSpaceAsPlus);
|
||||||
|
final int len = excludedCharacters.length;
|
||||||
|
for (int i = 0; i < len; i++) {
|
||||||
|
final char ch = excludedCharacters[i];
|
||||||
|
encoder.excludeCharacter(ch);
|
||||||
|
}
|
||||||
|
return encoder;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建用于用户验证信息编码的{@link CustomURLEncoder}
|
||||||
|
*
|
||||||
|
* @return {@link CustomURLEncoder}实例
|
||||||
|
*/
|
||||||
|
public static CustomURLEncoder createUserInfoUrlEncoder() {
|
||||||
|
return createURLEncoder(USER_INFO_EXCLUDED_CHARACTERS, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建用于URI路径编码的{@link CustomURLEncoder}
|
||||||
|
*
|
||||||
|
* @return {@link CustomURLEncoder}实例
|
||||||
|
*/
|
||||||
|
public static CustomURLEncoder createPathUrlEncoder() {
|
||||||
|
return createURLEncoder(PATH_EXCLUDED_CHARACTERS, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建用于查询参数值编码的{@link CustomURLEncoder}
|
||||||
|
*
|
||||||
|
* @return {@link CustomURLEncoder}实例
|
||||||
|
*/
|
||||||
|
public static CustomURLEncoder createQueryValueUrlEncoder() {
|
||||||
|
return createURLEncoder(QUERY_VALUE_EXCLUDED_CHARACTERS, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建用于 (带不转义大括号的) 查询参数值编码的{@link CustomURLEncoder}
|
||||||
|
*
|
||||||
|
* @return {@link CustomURLEncoder}实例
|
||||||
|
*/
|
||||||
|
public static CustomURLEncoder createQueryValueWithBraceUrlEncoder() {
|
||||||
|
return createURLEncoder(QUERY_VALUE_EXCLUDED_CHARACTERS_WITH_BRACE, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建用于表单参数值编码的{@link CustomURLEncoder}
|
||||||
|
*
|
||||||
|
* @return {@link CustomURLEncoder}实例
|
||||||
|
*/
|
||||||
|
public static CustomURLEncoder createXWwwFormUrlEncodedValueUrlEncoder() {
|
||||||
|
return createURLEncoder(X_WWW_FORM_URLENCODED_VALUE_EXCLUDED_CHARACTERS, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建用于强制编码的{@link CustomURLEncoder}
|
||||||
|
*
|
||||||
|
* @return {@link CustomURLEncoder}实例
|
||||||
|
*/
|
||||||
|
public static CustomURLEncoder createAllUrlEncoder() {
|
||||||
|
return createURLEncoder(ALL_EXCLUDED_CHARACTERS, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 安全编码字符集
|
||||||
|
*/
|
||||||
|
private final BitSet excludedCharacters;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否将空格编码为+
|
||||||
|
*/
|
||||||
|
private boolean encodeSpaceAsPlus = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link CustomURLEncoder}构造函数
|
||||||
|
*/
|
||||||
|
public CustomURLEncoder() {
|
||||||
|
this(new BitSet(256));
|
||||||
|
// 排除所有小写英文字母
|
||||||
|
for (char i = 'a'; i <= 'z'; i++) {
|
||||||
|
excludeCharacter(i);
|
||||||
|
}
|
||||||
|
// 排除所有大写英文字母
|
||||||
|
for (char i = 'A'; i <= 'Z'; i++) {
|
||||||
|
excludeCharacter(i);
|
||||||
|
}
|
||||||
|
// 排除所有数字
|
||||||
|
for (char i = '0'; i <= '9'; i++) {
|
||||||
|
excludeCharacter(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* URLEncoder构造函数
|
||||||
|
*
|
||||||
|
* @param excludedCharacters 安全字符,安全字符不被编码
|
||||||
|
*/
|
||||||
|
private CustomURLEncoder(BitSet excludedCharacters) {
|
||||||
|
this.excludedCharacters = excludedCharacters;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 排除不被不被编码的字符
|
||||||
|
*
|
||||||
|
* @param c 字符
|
||||||
|
*/
|
||||||
|
public void excludeCharacter(char c) {
|
||||||
|
excludedCharacters.set(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否将空格编码为+
|
||||||
|
*
|
||||||
|
* @param encodeSpaceAsPlus 是否将空格编码为+
|
||||||
|
*/
|
||||||
|
public void setEncodeSpaceAsPlus(boolean encodeSpaceAsPlus) {
|
||||||
|
this.encodeSpaceAsPlus = encodeSpaceAsPlus;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String encode(final String path, final String charset) {
|
||||||
|
if (path == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
final Charset cs = StringUtils.isEmpty(charset) ?
|
||||||
|
StandardCharsets.UTF_8 : Charset.forName(charset);
|
||||||
|
return encode(path, cs);
|
||||||
|
} catch (Throwable th) {
|
||||||
|
throw new ForestRuntimeException(th);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isURLEncoded(final char[] charArray, final int index) {
|
||||||
|
if (charArray[index] != '%') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
final int len = charArray.length;
|
||||||
|
if (index + 2 < len) {
|
||||||
|
final char ch1 = charArray[index + 1];
|
||||||
|
final char ch2 = charArray[index + 2];
|
||||||
|
return Character.isDigit(ch1) && Character.isDigit(ch2);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将URL中的字符串编码为%形式
|
||||||
|
*
|
||||||
|
* @param path 需要编码的字符串
|
||||||
|
* @param charset 编码
|
||||||
|
* @return 编码后的字符串
|
||||||
|
*/
|
||||||
|
public String encode(final String path, final Charset charset) {
|
||||||
|
final StringBuilder builder = new StringBuilder(path.length());
|
||||||
|
final ByteArrayOutputStream buf = new ByteArrayOutputStream();
|
||||||
|
final OutputStreamWriter writer = new OutputStreamWriter(buf, charset);
|
||||||
|
final char[] charArray = path.toCharArray();
|
||||||
|
final int len = charArray.length;
|
||||||
|
for (int i = 0; i < len; i++) {
|
||||||
|
final char ch = charArray[i];
|
||||||
|
if (isURLEncoded(charArray, i)) {
|
||||||
|
builder.append(ch);
|
||||||
|
} else if (excludedCharacters.get(ch)) {
|
||||||
|
builder.append(ch);
|
||||||
|
} else if (encodeSpaceAsPlus && ch == SPACE) {
|
||||||
|
// 处理空格为加号+
|
||||||
|
builder.append('+');
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
writer.write((char) ch);
|
||||||
|
writer.flush();
|
||||||
|
} catch (IOException e) {
|
||||||
|
buf.reset();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
final byte[] ba = buf.toByteArray();
|
||||||
|
for (byte toEncode : ba) {
|
||||||
|
builder.append('%');
|
||||||
|
final int high = (toEncode & 0xf0) >>> 4;//高位
|
||||||
|
final int low = toEncode & 0x0f;//低位
|
||||||
|
builder.append(HEX_DIGITS_UPPER[high]);
|
||||||
|
builder.append(HEX_DIGITS_UPPER[low]);
|
||||||
|
}
|
||||||
|
buf.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user