Jelajahi Sumber

bump: 发布1.8.0

Uncarbon 3 tahun lalu
induk
melakukan
2d5482af81

+ 18 - 16
helio-starter-dubbo/src/main/java/cc/uncarbon/framework/dubbo/filter/ConsumerContextFilter.java

@@ -19,26 +19,28 @@ import org.apache.dubbo.rpc.*;
 @Activate(group = CommonConstants.CONSUMER)
 public class ConsumerContextFilter implements Filter {
 
+    /**
+     * 将用户上下文、租户上下文等业务字段,放进 Dubbo 附件中
+     * @since 1.6.0 不再转换成 JSON 字符串后放入附件中,直接使用可序列化的对象
+     * @since 1.8.0 判断isConsumerSide,非消费者不设置附件
+     */
     @Override
     public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
-        /*
-        将用户上下文、租户上下文等业务字段,放进 Dubbo 附件中
-        @since 1.6.0 不再转换成 JSON 字符串后放入附件中,直接使用可序列化的对象
-         */
+        RpcContextAttachment clientAttachment = RpcContext.getClientAttachment();
+        if (clientAttachment.isConsumerSide()) {
+            UserContext userContext = UserContextHolder.getUserContext();
+            if (userContext != null) {
+                log.debug("[Dubbo RPC] 设置当前用户上下文 >> {}", userContext);
+                clientAttachment.setAttachment(UserContext.CAMEL_NAME, userContext);
+            }
 
-        UserContext userContext = UserContextHolder.getUserContext();
-        if (userContext != null) {
-            log.debug("[Dubbo RPC] 设置当前用户上下文 >> {}", userContext);
-            RpcContext.getClientAttachment().setAttachment(UserContext.CAMEL_NAME, userContext);
+            TenantContext tenantContext = TenantContextHolder.getTenantContext();
+            if (tenantContext != null && tenantContext.getTenantId() != null) {
+                // 实际启用了租户
+                log.debug("[Dubbo RPC] 设置当前租户上下文 >> {}", tenantContext);
+                clientAttachment.setAttachment(TenantContext.CAMEL_NAME, tenantContext);
+            }
         }
-
-        TenantContext tenantContext = TenantContextHolder.getTenantContext();
-        if (tenantContext != null && tenantContext.getTenantId() != null) {
-            // 实际启用了租户
-            log.debug("[Dubbo RPC] 设置当前租户上下文 >> {}", tenantContext);
-            RpcContext.getClientAttachment().setAttachment(TenantContext.CAMEL_NAME, tenantContext);
-        }
-
         return invoker.invoke(invocation);
     }
 

+ 22 - 19
helio-starter-dubbo/src/main/java/cc/uncarbon/framework/dubbo/filter/ProviderContextFilter.java

@@ -19,29 +19,32 @@ import org.apache.dubbo.rpc.*;
 @Activate(group = CommonConstants.PROVIDER)
 public class ProviderContextFilter implements Filter {
 
+    /**
+     * 从 Dubbo 附件中,取出用户上下文、租户上下文等业务字段
+     * @since 1.6.0 附件内容不再是 JSON 字符串,直接使用可序列化的对象
+     * @since 1.8.0 判断isProviderSide,非提供者不从附件取值
+     */
     @Override
     public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
-        /*
-        从 Dubbo 附件中,取出用户上下文、租户上下文等业务字段
-        since 1.6.0 附件内容不再是 JSON 字符串,直接使用可序列化的对象
-         */
+        RpcContextAttachment serverAttachment = RpcContext.getServerAttachment();
+        if (serverAttachment.isProviderSide()) {
+            // 清理数据,避免线程池化复用残留
+            UserContextHolder.clear();
+            TenantContextHolder.clear();
 
-        // 清理数据,避免线程池化复用残留
-        UserContextHolder.clear();
-        TenantContextHolder.clear();
+            Object attachment = serverAttachment.getObjectAttachment(UserContext.CAMEL_NAME);
+            if (attachment instanceof UserContext) {
+                UserContext userContext = (UserContext) attachment;
+                UserContextHolder.setUserContext(userContext);
+                log.debug("[Dubbo RPC] 取出当前用户上下文 >> {}", userContext);
+            }
 
-        Object attachment = RpcContext.getServerAttachment().getObjectAttachment(UserContext.CAMEL_NAME);
-        if (attachment instanceof UserContext) {
-            UserContext userContext = (UserContext) attachment;
-            UserContextHolder.setUserContext(userContext);
-            log.debug("[Dubbo RPC] 取出当前用户上下文 >> {}", userContext);
-        }
-
-        attachment = RpcContext.getServerAttachment().getObjectAttachment(TenantContext.CAMEL_NAME);
-        if (attachment instanceof TenantContext) {
-            TenantContext tenantContext = (TenantContext) attachment;
-            TenantContextHolder.setTenantContext(tenantContext);
-            log.debug("[Dubbo RPC] 取出当前租户上下文 >> {}", tenantContext);
+            attachment = serverAttachment.getObjectAttachment(TenantContext.CAMEL_NAME);
+            if (attachment instanceof TenantContext) {
+                TenantContext tenantContext = (TenantContext) attachment;
+                TenantContextHolder.setTenantContext(tenantContext);
+                log.debug("[Dubbo RPC] 取出当前租户上下文 >> {}", tenantContext);
+            }
         }
 
         return invoker.invoke(invocation);

+ 58 - 8
helio-starter-i18n/src/main/java/cc/uncarbon/framework/i18n/util/I18nUtil.java

@@ -24,13 +24,23 @@ public class I18nUtil {
     private final MessageSource MESSAGE_SOURCE = SpringUtil.getBean(MessageSource.class);
     private static final String SLF4J_STYLE_PLACEHOLDER = StrPool.DELIM_START + StrPool.DELIM_END;
 
-
+    /**
+     * 取Spring提供的MessageSource
+     */
     public MessageSource getMessageSource() {
         return MESSAGE_SOURCE;
     }
 
     /**
-     * 根据消息键和参数,获取国际化翻译值
+     * 取默认locale
+     * 强国际化SaaS需求,也许可以改造成从特定ThreadLocal或者UserContextHolder中获取
+     */
+    public Locale getDefaultLocale() {
+        return Locale.getDefault();
+    }
+
+    /**
+     * 根据消息键和参数,获取国际化翻译值;默认使用当前JVM的默认locale
      * 支持模板填充,如: Nickname '{}' is already exist, do you like '{}'?
      *
      * @param code 消息代码
@@ -38,12 +48,25 @@ public class I18nUtil {
      * @return null or 国际化翻译值
      */
     public String messageOf(String code, Object... templateParams) {
+        return messageOf(getDefaultLocale(), code, templateParams);
+    }
+
+    /**
+     * 根据消息键和参数,获取国际化翻译值
+     * 支持模板填充,如: Nickname '{}' is already exist, do you like '{}'?
+     *
+     * @param locale locale
+     * @param code 消息代码
+     * @param templateParams 模板填充参数
+     * @return null or 国际化翻译值
+     */
+    public String messageOf(Locale locale, String code, Object... templateParams) {
         if (code == null) {
             return null;
         }
 
         try {
-            String msg = getMessageSource().getMessage(code, null, Locale.getDefault());
+            String msg = getMessageSource().getMessage(code, null, locale != null ? locale : getDefaultLocale());
             if (StrUtil.isEmpty(msg)) {
                 return msg;
             }
@@ -61,7 +84,7 @@ public class I18nUtil {
     }
 
     /**
-     * 根据消息键和参数,获取国际化翻译值
+     * 根据消息键和参数,获取国际化翻译值;默认使用当前JVM的默认locale
      * 支持模板填充,如: Nickname '{}' is already exist, do you like '{}'?
      *
      * @param code 消息代码
@@ -70,12 +93,26 @@ public class I18nUtil {
      * @return null or 国际化翻译值
      */
     public String messageOf(String code, String defaultValue, Object... templateParams) {
-        String msg = messageOf(code, templateParams);
+        return messageOf(getDefaultLocale(), code, defaultValue, templateParams);
+    }
+
+    /**
+     * 根据消息键和参数,获取国际化翻译值
+     * 支持模板填充,如: Nickname '{}' is already exist, do you like '{}'?
+     *
+     * @param locale locale
+     * @param code 消息代码
+     * @param defaultValue 未找到对应国际化翻译值的情况下,默认返回值
+     * @param templateParams 模板填充参数
+     * @return null or 国际化翻译值
+     */
+    public String messageOf(Locale locale, String code, String defaultValue, Object... templateParams) {
+        String msg = messageOf(locale, code, templateParams);
         return msg == null ? defaultValue : msg;
     }
 
     /**
-     * 根据枚举值和参数,获取国际化翻译值
+     * 根据枚举值和参数,获取国际化翻译值;默认使用当前JVM的默认locale
      * 支持模板填充,如: Nickname '{}' has been existing, do you like '{}'?
      *
      * @param enumField 枚举值
@@ -83,6 +120,19 @@ public class I18nUtil {
      * @return null or 国际化翻译值
      */
     public String messageOf(Enum<?> enumField, Object... templateParams) {
+        return messageOf(getDefaultLocale(), enumField, templateParams);
+    }
+
+    /**
+     * 根据枚举值和参数,获取国际化翻译值
+     * 支持模板填充,如: Nickname '{}' has been existing, do you like '{}'?
+     *
+     * @param locale locale
+     * @param enumField 枚举值
+     * @param templateParams 模板填充参数
+     * @return null or 国际化翻译值
+     */
+    public String messageOf(Locale locale, Enum<?> enumField, Object... templateParams) {
         if (enumField == null) {
             return null;
         }
@@ -91,14 +141,14 @@ public class I18nUtil {
 
         // 尝试以较长的 枚举类短名.枚举值name 为 code 尝试获取翻译值
         i18nCode = String.format("%s.%s", enumField.getDeclaringClass().getSimpleName(), enumField.name());
-        String i18nMessage = messageOf(i18nCode, templateParams);
+        String i18nMessage = messageOf(locale, i18nCode, templateParams);
         if (StrUtil.isNotEmpty(i18nMessage)) {
             return i18nMessage;
         }
 
         // 尝试以 枚举值name 为 code 尝试获取翻译值
         i18nCode = enumField.name();
-        i18nMessage = messageOf(i18nCode, templateParams);
+        i18nMessage = messageOf(locale, i18nCode, templateParams);
         if (StrUtil.isNotEmpty(i18nMessage)) {
             return i18nMessage;
         }

+ 5 - 2
helio-starter-satoken/src/main/java/cc/uncarbon/framework/satoken/interceptor/DefaultSaTokenParseInterceptor.java

@@ -7,18 +7,21 @@ import cc.uncarbon.framework.core.context.UserContextHolder;
 import cc.uncarbon.framework.satoken.util.IPUtil;
 import cn.dev33.satoken.session.SaSession;
 import cn.dev33.satoken.stp.StpUtil;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
 import lombok.NonNull;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.web.servlet.AsyncHandlerInterceptor;
 import org.springframework.web.servlet.resource.ResourceHttpRequestHandler;
 
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
 /**
  * 从请求头解析用户信息,并赋值到上下文;默认用于C端用户的鉴权
  *
  * @author Uncarbon
+ * @deprecated since 1.8.0,已内置至脚手架同名类中
  */
+@Deprecated
 @Slf4j
 public class DefaultSaTokenParseInterceptor implements AsyncHandlerInterceptor {
 

+ 4 - 1
helio-starter-satoken/src/main/java/cc/uncarbon/framework/satoken/util/IPUtil.java

@@ -1,13 +1,16 @@
 package cc.uncarbon.framework.satoken.util;
 
-import javax.servlet.http.HttpServletRequest;
 import lombok.experimental.UtilityClass;
 
+import javax.servlet.http.HttpServletRequest;
+
 /**
  * IP地址工具类
  * 方法来自于互联网
  * @author Uncarbon
+ * @deprecated since 1.8.0,改为使用helio-starter-web包中的同名工具类
  */
+@Deprecated
 @UtilityClass
 public class IPUtil {
     private static final String[] HEADERS_TO_TRY = {

+ 23 - 2
helio-starter-web/src/main/java/cc/uncarbon/framework/web/util/IPUtil.java

@@ -1,8 +1,13 @@
 package cc.uncarbon.framework.web.util;
 
-import javax.servlet.http.HttpServletRequest;
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.text.StrPool;
+import cn.hutool.core.util.StrUtil;
 import lombok.experimental.UtilityClass;
 
+import javax.servlet.http.HttpServletRequest;
+import java.util.List;
+
 /**
  * IP地址工具类
  * 方法来自于互联网
@@ -27,7 +32,7 @@ public class IPUtil {
     /***
      * 获取客户端IP地址(可以穿透代理)
      * @param request 请求对象
-     * @return IP地址
+     * @return 客户端IP地址
      */
     public String getClientIPAddress(HttpServletRequest request) {
         for (String header : HEADERS_TO_TRY) {
@@ -39,4 +44,20 @@ public class IPUtil {
         return request.getRemoteAddr();
     }
 
+    /***
+     * 获取客户端IP地址(可以穿透代理)
+     * @param request 请求对象
+     * @param indexOfCommaSplit 先按逗号分隔后,再取第index个IP地址(从0开始);兼容启用了云防护盾CDN的服务器(可能获取到的IP会带上中间代理节点的IP地址)
+     * @return 客户端IP地址
+     */
+    public String getClientIPAddress(HttpServletRequest request, int indexOfCommaSplit) {
+        String source = getClientIPAddress(request);
+        if (!StrUtil.contains(source, StrPool.COMMA)) {
+            // 不含逗号
+            return source;
+        }
+        List<String> split = StrUtil.split(source, StrPool.COMMA);
+        return StrUtil.cleanBlank(CollUtil.get(split, indexOfCommaSplit));
+    }
+
 }

+ 145 - 0
helio-starter-web/src/main/java/cc/uncarbon/framework/web/util/MockMultipartFile.java

@@ -0,0 +1,145 @@
+/*
+ * Copyright 2002-2020 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package cc.uncarbon.framework.web.util;
+
+import org.springframework.lang.NonNull;
+import org.springframework.lang.Nullable;
+import org.springframework.util.Assert;
+import org.springframework.util.FileCopyUtils;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Mock implementation of the {@link MultipartFile}
+ * interface.
+ * 从 spring-test 库中提取的
+ *
+ * @author Juergen Hoeller
+ * @author Eric Crampton
+ * @since 2.0
+ */
+public class MockMultipartFile implements MultipartFile {
+
+	private final String name;
+
+	private final String originalFilename;
+
+	@Nullable
+	private final String contentType;
+
+	private final byte[] content;
+
+
+	/**
+	 * Create a new MockMultipartFile with the given content.
+	 * @param name the name of the file
+	 * @param content the content of the file
+	 */
+	public MockMultipartFile(String name, @Nullable byte[] content) {
+		this(name, "", null, content);
+	}
+
+	/**
+	 * Create a new MockMultipartFile with the given content.
+	 * @param name the name of the file
+	 * @param contentStream the content of the file as stream
+	 * @throws IOException if reading from the stream failed
+	 */
+	public MockMultipartFile(String name, InputStream contentStream) throws IOException {
+		this(name, "", null, FileCopyUtils.copyToByteArray(contentStream));
+	}
+
+	/**
+	 * Create a new MockMultipartFile with the given content.
+	 * @param name the name of the file
+	 * @param originalFilename the original filename (as on the client's machine)
+	 * @param contentType the content type (if known)
+	 * @param content the content of the file
+	 */
+	public MockMultipartFile(
+			String name, @Nullable String originalFilename, @Nullable String contentType, @Nullable byte[] content) {
+
+		Assert.hasLength(name, "Name must not be empty");
+		this.name = name;
+		this.originalFilename = (originalFilename != null ? originalFilename : "");
+		this.contentType = contentType;
+		this.content = (content != null ? content : new byte[0]);
+	}
+
+	/**
+	 * Create a new MockMultipartFile with the given content.
+	 * @param name the name of the file
+	 * @param originalFilename the original filename (as on the client's machine)
+	 * @param contentType the content type (if known)
+	 * @param contentStream the content of the file as stream
+	 * @throws IOException if reading from the stream failed
+	 */
+	public MockMultipartFile(
+			String name, @Nullable String originalFilename, @Nullable String contentType, InputStream contentStream)
+			throws IOException {
+
+		this(name, originalFilename, contentType, FileCopyUtils.copyToByteArray(contentStream));
+	}
+
+
+	@Override
+	public String getName() {
+		return this.name;
+	}
+
+	@Override
+	@NonNull
+	public String getOriginalFilename() {
+		return this.originalFilename;
+	}
+
+	@Override
+	@Nullable
+	public String getContentType() {
+		return this.contentType;
+	}
+
+	@Override
+	public boolean isEmpty() {
+		return (this.content.length == 0);
+	}
+
+	@Override
+	public long getSize() {
+		return this.content.length;
+	}
+
+	@Override
+	public byte[] getBytes() throws IOException {
+		return this.content;
+	}
+
+	@Override
+	public InputStream getInputStream() throws IOException {
+		return new ByteArrayInputStream(this.content);
+	}
+
+	@Override
+	public void transferTo(File dest) throws IOException, IllegalStateException {
+		FileCopyUtils.copy(this.content, dest);
+	}
+
+}

+ 8 - 8
pom.xml

@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-parent</artifactId>
-        <version>2.7.9</version>
+        <version>2.7.12</version>
         <relativePath/>
     </parent>
 
@@ -34,28 +34,28 @@
 
     <properties>
         <!-- core versions -->
-        <revision>1.7.3</revision>
+        <revision>1.8.0</revision>
         <helio-starters.version>${revision}</helio-starters.version>
         <!-- JDK8则改为'1.8'  JDK11则改为'11' -->
         <java.version>1.8</java.version>
         <!-- 若变更Spring Boot版本, 上面parent依赖版本也要变 -->
-        <spring-boot.version>2.7.9</spring-boot.version>
+        <spring-boot.version>2.7.12</spring-boot.version>
 
 
         <!-- Spring Cloud versions -->
         <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-dependencies -->
-        <spring-cloud.version>2021.0.4</spring-cloud.version>
-        <spring-cloud-starter-bootstrap.version>3.1.4</spring-cloud-starter-bootstrap.version>
+        <spring-cloud.version>2021.0.7</spring-cloud.version>
+        <spring-cloud-starter-bootstrap.version>3.1.6</spring-cloud-starter-bootstrap.version>
 
         <!-- Spring Cloud Alibaba versions -->
-        <spring-cloud-alibaba.version>2021.0.4.0</spring-cloud-alibaba.version>
-        <dubbo.version>3.1.2</dubbo.version>
+        <spring-cloud-alibaba.version>2021.0.5.0</spring-cloud-alibaba.version>
+        <dubbo.version>3.1.10</dubbo.version>
         <seata.vesion>1.5.2</seata.vesion>
         <nacos-client.vesion>2.1.2</nacos-client.vesion>
 
 
         <!-- 3rd-party versions -->
-        <hutool.version>5.8.15</hutool.version>
+        <hutool.version>5.8.19</hutool.version>
         <sa-token.version>1.34.0</sa-token.version>
         <transmittable-thread-local.version>2.14.2</transmittable-thread-local.version>
         <!-- 如果 mp 较新版存在问题,可以回退至 3.4.3.4 试试  -->