diff --git a/ruoyi-admin/pom.xml b/ruoyi-admin/pom.xml
index 3c1a591f..6a726227 100644
--- a/ruoyi-admin/pom.xml
+++ b/ruoyi-admin/pom.xml
@@ -53,6 +53,12 @@
com.ruoyi
ruoyi-system
+
+
+ forest-core
+ com.dtflys.forest
+
+
diff --git a/ruoyi-cai/src/main/java/com/ruoyi/cai/service/impl/SysPushServiceImpl.java b/ruoyi-cai/src/main/java/com/ruoyi/cai/service/impl/SysPushServiceImpl.java
index cabadc9b..4daf28bf 100644
--- a/ruoyi-cai/src/main/java/com/ruoyi/cai/service/impl/SysPushServiceImpl.java
+++ b/ruoyi-cai/src/main/java/com/ruoyi/cai/service/impl/SysPushServiceImpl.java
@@ -294,7 +294,7 @@ public class SysPushServiceImpl extends ServiceImpl impl
if(imResp != null && imResp.isSuccess()){
sysPushLog.setStatus(SystemPushLogStatusEnum.SEND_SUCCESS.getCode());
}else{
- sysPushLog.setStatus(SystemPushLogStatusEnum.SEND_SUCCESS.getCode());
+ sysPushLog.setStatus(SystemPushLogStatusEnum.SEND_FAIL.getCode());
}
sysPushLog.setResult(JSON.toJSONString(imResp));
}catch (Exception e){
diff --git a/ruoyi-sms/pom.xml b/ruoyi-sms/pom.xml
index 0592d421..2d024a24 100644
--- a/ruoyi-sms/pom.xml
+++ b/ruoyi-sms/pom.xml
@@ -23,17 +23,17 @@
ruoyi-common
-
+
+ <!– 排除京东短信内存在的fastjson等待作者后续修复 –>
com.alibaba
fastjson
-
+ -->
diff --git a/ruoyi-yunxin/src/main/java/com/ruoyi/yunxin/YunExecutor.java b/ruoyi-yunxin/src/main/java/com/ruoyi/yunxin/YunExecutor.java
index 501cb9c7..eb697f90 100644
--- a/ruoyi-yunxin/src/main/java/com/ruoyi/yunxin/YunExecutor.java
+++ b/ruoyi-yunxin/src/main/java/com/ruoyi/yunxin/YunExecutor.java
@@ -17,15 +17,14 @@ public class YunExecutor {
public static Executor YUN_EXECUTOR;
static {
- ThreadPoolExecutor roomExecutor = new ThreadPoolExecutor(CPU_NUM,
+ YUN_EXECUTOR = new ThreadPoolExecutor(CPU_NUM,
CPU_NUM << 2,
5,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(5),
init("yunxinThreadPoll-%d"),
new ThreadPoolExecutor.CallerRunsPolicy());
- YUN_EXECUTOR = TtlExecutors.getTtlExecutor(roomExecutor);
-
+// YUN_EXECUTOR = TtlExecutors.getTtlExecutor(roomExecutor);
}
private static ThreadFactory init(String nameFormat){
diff --git a/ruoyi-yunxin/src/main/java/com/ruoyi/yunxin/client/ImMessageClient.java b/ruoyi-yunxin/src/main/java/com/ruoyi/yunxin/client/ImMessageClient.java
index 9c8794c3..7ae3700f 100644
--- a/ruoyi-yunxin/src/main/java/com/ruoyi/yunxin/client/ImMessageClient.java
+++ b/ruoyi-yunxin/src/main/java/com/ruoyi/yunxin/client/ImMessageClient.java
@@ -1,8 +1,7 @@
package com.ruoyi.yunxin.client;
-import com.dtflys.forest.annotation.BaseRequest;
-import com.dtflys.forest.annotation.Body;
-import com.dtflys.forest.annotation.Post;
+import com.dtflys.forest.annotation.*;
+import com.ruoyi.yunxin.convertor.CustomFormConvertor;
import com.ruoyi.yunxin.interceptor.GlodonTokenInterceptor;
import com.ruoyi.yunxin.req.SendAttachMsgReq;
import com.ruoyi.yunxin.req.SendBatchAttachMsgReq;
@@ -21,6 +20,7 @@ public interface ImMessageClient {
* @return
*/
@Post(url = "/nimserver/msg/sendMsg.action")
+ @BodyType(type = "form", encoder = CustomFormConvertor.class)
YxDataR sendMsg(@Body SendMsgReq req);
@Post(url = "/nimserver/msg/sendBatchMsg.action")
diff --git a/ruoyi-yunxin/src/main/java/com/ruoyi/yunxin/convertor/CustomFormConvertor.java b/ruoyi-yunxin/src/main/java/com/ruoyi/yunxin/convertor/CustomFormConvertor.java
new file mode 100644
index 00000000..a4bda868
--- /dev/null
+++ b/ruoyi-yunxin/src/main/java/com/ruoyi/yunxin/convertor/CustomFormConvertor.java
@@ -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, ForestEncoder {
+
+ private final ForestConfiguration configuration;
+
+ public CustomFormConvertor(ForestConfiguration configuration) {
+ this.configuration = configuration;
+ }
+
+
+ @Override
+ public T convertToJavaObject(String source, Type targetType) {
+ return null;
+ }
+
+ @Override
+ public T convertToJavaObject(byte[] source, Class targetType, Charset charset) {
+ return null;
+ }
+
+ @Override
+ public 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 map = jsonConverter.convertObjectToMap(obj);
+ final List nameValueList = new LinkedList<>();
+ for (Map.Entry entry : map.entrySet()) {
+ final RequestNameValue nameValue = new RequestNameValue(entry.getKey(), MappingParameter.TARGET_BODY);
+ nameValue.setValue(entry.getValue());
+ nameValueList.add(nameValue);
+ }
+ final List 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 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 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 newNameValueList, ForestConfiguration configuration, String name, Map map, int target) {
+ for (Iterator 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 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 itemAttrs = ReflectUtils.convertObjectToMap(value, configuration);
+ for (Map.Entry 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 processFromNameValueList(
+ final ForestRequest request,
+ final List nameValueList,
+ final ForestConfiguration configuration,
+ final ConvertOptions options) {
+ final List 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 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 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 newNameValueList =
+ processFromNameValueList(request, nameValueList, configuration, options);
+ String strBody = formUrlEncodedString(newNameValueList, cs);
+ byte[] bytes = strBody.getBytes(cs);
+ return bytes;
+ }
+}
diff --git a/ruoyi-yunxin/src/main/java/com/ruoyi/yunxin/convertor/CustomURLEncoder.java b/ruoyi-yunxin/src/main/java/com/ruoyi/yunxin/convertor/CustomURLEncoder.java
new file mode 100644
index 00000000..cd94c281
--- /dev/null
+++ b/ruoyi-yunxin/src/main/java/com/ruoyi/yunxin/convertor/CustomURLEncoder.java
@@ -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();
+ }
+
+}