Browse Source

优化Redis分布式队列实现逻辑;优化MQ消息环境隔离逻辑;其他代码优化;

Woody 6 months ago
parent
commit
66b007a934

+ 34 - 25
framework-base/src/main/java/com/chelvc/framework/base/context/ApplicationContextHolder.java

@@ -48,6 +48,7 @@ import org.springframework.beans.factory.config.YamlPropertiesFactoryBean;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
 import org.springframework.context.ApplicationContext;
 import org.springframework.context.ApplicationContextAware;
+import org.springframework.context.EnvironmentAware;
 import org.springframework.context.annotation.ComponentScan;
 import org.springframework.context.i18n.LocaleContextHolder;
 import org.springframework.context.support.GenericApplicationContext;
@@ -79,7 +80,7 @@ import org.springframework.web.bind.annotation.RestController;
 @Slf4j
 @Component
 @Order(Ordered.HIGHEST_PRECEDENCE)
-public class ApplicationContextHolder implements ApplicationContextAware, PropertyRefreshListener {
+public class ApplicationContextHolder implements EnvironmentAware, ApplicationContextAware, PropertyRefreshListener {
     /**
      * 服务端口属性
      */
@@ -124,11 +125,21 @@ public class ApplicationContextHolder implements ApplicationContextAware, Proper
      */
     private static final Map<String, WeakReference<Object>> PROPERTY_OBJECT_MAPPING = Maps.newConcurrentMap();
 
+    /**
+     * 应用环境对象
+     */
+    private static Environment ENVIRONMENT;
+
     /**
      * 应用上下文对象
      */
     private static ApplicationContext APPLICATION_CONTEXT;
 
+    @Override
+    public void setEnvironment(Environment environment) {
+        ENVIRONMENT = environment;
+    }
+
     @Override
     public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
         APPLICATION_CONTEXT = applicationContext;
@@ -147,7 +158,8 @@ public class ApplicationContextHolder implements ApplicationContextAware, Proper
      * @return 服务端口
      */
     public static Integer getPort() {
-        return getPort(getApplicationContext());
+        Environment environment = getEnvironment(false);
+        return environment == null ? null : getPort(environment);
     }
 
     /**
@@ -168,11 +180,11 @@ public class ApplicationContextHolder implements ApplicationContextAware, Proper
     /**
      * 获取服务端口
      *
-     * @param applicationContext 应用上下文实例
+     * @param environment 应用环境
      * @return 服务端口
      */
-    public static Integer getPort(@NonNull ApplicationContext applicationContext) {
-        String property = applicationContext.getEnvironment().getProperty(SERVER_PORT_PROPERTY);
+    public static Integer getPort(@NonNull Environment environment) {
+        String property = environment.getProperty(SERVER_PORT_PROPERTY);
         return StringUtils.ifEmpty(property, Integer::parseInt);
     }
 
@@ -182,7 +194,8 @@ public class ApplicationContextHolder implements ApplicationContextAware, Proper
      * @return 环境标识
      */
     public static String getProfile() {
-        return getProfile(getApplicationContext());
+        Environment environment = getEnvironment(false);
+        return environment == null ? null : getProfile(environment);
     }
 
     /**
@@ -199,11 +212,11 @@ public class ApplicationContextHolder implements ApplicationContextAware, Proper
     /**
      * 获取环境标识
      *
-     * @param applicationContext 应用上下文实例
+     * @param environment 应用环境
      * @return 环境标识
      */
-    public static String getProfile(@NonNull ApplicationContext applicationContext) {
-        return applicationContext.getEnvironment().getProperty(APPLICATION_PROFILE_PROPERTY);
+    public static String getProfile(@NonNull Environment environment) {
+        return environment.getProperty(APPLICATION_PROFILE_PROPERTY);
     }
 
     /**
@@ -213,11 +226,7 @@ public class ApplicationContextHolder implements ApplicationContextAware, Proper
      */
     public static boolean isProduction() {
         Boolean bool = getProperty("environment.production", Boolean.class);
-        if (bool == null) {
-            ApplicationContext applicationContext = getApplicationContext(false);
-            return applicationContext != null && Objects.equals(getProfile(applicationContext), "prod");
-        }
-        return bool;
+        return Boolean.TRUE.equals(bool) || Objects.equals(getProfile(), "prod");
     }
 
     /**
@@ -226,7 +235,8 @@ public class ApplicationContextHolder implements ApplicationContextAware, Proper
      * @return true/false
      */
     public static boolean isMultipleServer() {
-        return isMultipleServer(getApplicationContext());
+        ApplicationContext applicationContext = getApplicationContext(false);
+        return applicationContext != null && isMultipleServer(applicationContext);
     }
 
     /**
@@ -251,16 +261,14 @@ public class ApplicationContextHolder implements ApplicationContextAware, Proper
     /**
      * 获取应用环境对象
      *
-     * @param required 应用环境是否必须
+     * @param required 是否必须
      * @return 应用环境对象
      */
     public static Environment getEnvironment(boolean required) {
-        ApplicationContext applicationContext = getApplicationContext(required);
-        Environment environment = ObjectUtils.ifNull(applicationContext, ApplicationContext::getEnvironment);
         if (required) {
-            return AssertUtils.nonnull(environment, () -> "Application environment has not been initialized");
+            return AssertUtils.nonnull(ENVIRONMENT, () -> "Application environment has not been initialized");
         }
-        return environment;
+        return ENVIRONMENT;
     }
 
     /**
@@ -269,17 +277,18 @@ public class ApplicationContextHolder implements ApplicationContextAware, Proper
      * @return 应用名称
      */
     public static String getApplicationName() {
-        return getApplicationName(getApplicationContext());
+        Environment environment = getEnvironment(false);
+        return environment == null ? null : getApplicationName(environment);
     }
 
     /**
      * 获取应用名称
      *
-     * @param applicationContext 应用上下文实例
+     * @param environment 应用环境
      * @return 应用名称
      */
-    public static String getApplicationName(@NonNull ApplicationContext applicationContext) {
-        return applicationContext.getEnvironment().getProperty(APPLICATION_NAME_PROPERTY);
+    public static String getApplicationName(@NonNull Environment environment) {
+        return environment.getProperty(APPLICATION_NAME_PROPERTY);
     }
 
     /**
@@ -305,7 +314,7 @@ public class ApplicationContextHolder implements ApplicationContextAware, Proper
     /**
      * 获取应用上下文对象
      *
-     * @param required 应用上下文是否必须
+     * @param required 是否必须
      * @return 应用上下文对象
      */
     public static ApplicationContext getApplicationContext(boolean required) {

+ 16 - 6
framework-common/src/main/java/com/chelvc/framework/common/util/MobileUtils.java

@@ -24,13 +24,18 @@ public final class MobileUtils {
      */
     public static final String DEFAULT_DESENSITIZE_EXPRESSION = "---****-?";
 
+    /**
+     * 默认手机号脱敏数字
+     */
+    private static final String DEFAULT_DESENSITIZE_NUMBER =
+            "0-90-9⁰-⁹₀-₉⓿-❾⓪①②③④⑤⑥⑦⑧⑨〇零一壹二贰弍三叁弎仨四肆五伍六陆七柒八捌九玖";
+
     /**
      * 脱敏手机号默认匹配模式
      */
-    private static final Pattern DEFAULT_DESENSITIZE_PATTERN = Pattern.compile(
-            "[1一壹][^0-9\\u4e00-\\u9fa5]*[3-9零〇一壹二贰弍三叁弎仨四肆五伍六陆七柒八捌九玖]" +
-                    "([^0-9\\u4e00-\\u9fa5]*[0-9零〇一壹二贰弍三叁弎仨四肆五伍六陆七柒八捌九玖]){9}"
-    );
+    private static final Pattern DEFAULT_DESENSITIZE_PATTERN = Pattern.compile(String.format(
+            "[11¹₁❶①一壹]([^%s]*[%s]){10}", DEFAULT_DESENSITIZE_NUMBER, DEFAULT_DESENSITIZE_NUMBER
+    ));
 
     /**
      * 手机号处理工具实例
@@ -200,7 +205,10 @@ public final class MobileUtils {
             if (mode == null) {
                 mode = DesensitizeUtils.analyse(expression);
             }
-            buffer.append(text, offset, matcher.start());
+            int start = matcher.start();
+            if (start > 0) {
+                buffer.append(text, offset, start);
+            }
             String mobile = matcher.group();
             if (mobile.length() > 11) {
                 mobile = StringUtils.clear(mobile);
@@ -208,7 +216,9 @@ public final class MobileUtils {
             if (ChineseUtils.isChineseNumber(mobile)) {
                 mobile = ChineseUtils.Number.format(mobile);
             }
-            buffer.append(mode.desensitize(mobile));
+            if (StringUtils.notEmpty(mobile)) {
+                buffer.append(mode.desensitize(mobile));
+            }
             offset = matcher.end();
         }
         if (buffer == null) {

+ 0 - 5
framework-kafka/src/main/java/com/chelvc/framework/kafka/config/KafkaProperties.java

@@ -39,11 +39,6 @@ public class KafkaProperties extends org.springframework.boot.autoconfigure.kafk
         }
     }
 
-    /**
-     * 命名空间
-     */
-    private String namespace;
-
     /**
      * 降级策略列表
      */

+ 2 - 13
framework-kafka/src/main/java/com/chelvc/framework/kafka/context/KafkaContextHolder.java

@@ -18,7 +18,6 @@ import com.chelvc.framework.base.context.SessionContextHolder;
 import com.chelvc.framework.common.util.JacksonUtils;
 import com.chelvc.framework.common.util.ObjectUtils;
 import com.chelvc.framework.common.util.StringUtils;
-import com.chelvc.framework.kafka.config.KafkaProperties;
 import lombok.NonNull;
 import org.apache.kafka.clients.consumer.ConsumerRecord;
 import org.apache.kafka.common.header.Header;
@@ -40,16 +39,6 @@ public final class KafkaContextHolder {
     private KafkaContextHolder() {
     }
 
-    /**
-     * 获取命名空间
-     *
-     * @return 命名空间
-     */
-    public static String getNamespace() {
-        KafkaProperties properties = ApplicationContextHolder.getBean(KafkaProperties.class, false);
-        return ObjectUtils.ifNull(properties, KafkaProperties::getNamespace);
-    }
-
     /**
      * 获取Kafka模版实例
      *
@@ -76,8 +65,8 @@ public final class KafkaContextHolder {
      * @return 环境隔离标记
      */
     public static String isolate(@NonNull String original) {
-        String namespace = getNamespace();
-        return StringUtils.isEmpty(namespace) ? original : (namespace + original);
+        String namespace = ApplicationContextHolder.getProfile();
+        return StringUtils.isEmpty(namespace) ? original : (namespace + "-" + original);
     }
 
     /**

+ 1 - 1
framework-nacos/src/main/java/com/chelvc/framework/nacos/config/MultipleNacosConfigurer.java

@@ -28,7 +28,7 @@ public class MultipleNacosConfigurer implements ApplicationListener<ApplicationS
     @Override
     public void onApplicationEvent(ApplicationStartedEvent event) {
         ApplicationContext applicationContext = event.getApplicationContext();
-        String applicationName = ApplicationContextHolder.getApplicationName(applicationContext);
+        String applicationName = ApplicationContextHolder.getApplicationName(applicationContext.getEnvironment());
         for (Resource resource : ApplicationContextHolder.getApplicationResources()) {
             // 将打包的服务注册到nacos(排除当前服务)
             String service = ApplicationContextHolder.getApplicationName(resource);

+ 0 - 5
framework-redis/src/main/java/com/chelvc/framework/redis/config/RedisProperties.java

@@ -29,11 +29,6 @@ public class RedisProperties {
      */
     @Data
     public static class Stream {
-        /**
-         * 命名空间
-         */
-        private String namespace;
-
         /**
          * 消费者空闲时间(秒)
          */

+ 2 - 12
framework-redis/src/main/java/com/chelvc/framework/redis/context/RedisStreamHolder.java

@@ -89,16 +89,6 @@ public final class RedisStreamHolder {
         return (System.currentTimeMillis() - expiration * 1000) + "-0";
     }
 
-    /**
-     * 获取命名空间
-     *
-     * @return 命名空间
-     */
-    public static String getNamespace() {
-        RedisProperties properties = ApplicationContextHolder.getBean(RedisProperties.class, false);
-        return properties == null ? null : properties.getStream().getNamespace();
-    }
-
     /**
      * 获取消息流处理RedisTemplate实例
      *
@@ -132,8 +122,8 @@ public final class RedisStreamHolder {
      * @return 环境隔离标记
      */
     public static String isolate(@NonNull String original) {
-        String namespace = getNamespace();
-        return StringUtils.isEmpty(namespace) ? original : (namespace + original);
+        String namespace = ApplicationContextHolder.getProfile();
+        return StringUtils.isEmpty(namespace) ? original : (namespace + "-" + original);
     }
 
     /**

+ 40 - 9
framework-redis/src/main/java/com/chelvc/framework/redis/queue/RedisQueue.java

@@ -5,10 +5,15 @@ import java.util.Collection;
 import java.util.Collections;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
 
+import com.chelvc.framework.base.context.ApplicationContextHolder;
 import com.chelvc.framework.common.util.AssertUtils;
 import com.chelvc.framework.common.util.ObjectUtils;
+import com.chelvc.framework.common.util.StringUtils;
 import com.chelvc.framework.redis.context.RedisContextHolder;
+import com.google.common.collect.Maps;
 import lombok.NonNull;
 import org.springframework.data.redis.core.RedisTemplate;
 
@@ -20,16 +25,17 @@ import org.springframework.data.redis.core.RedisTemplate;
  * @date 2024/8/19
  */
 public class RedisQueue<E> extends AbstractQueue<E> {
+    /**
+     * 队列名称/实例映射表
+     */
+    @SuppressWarnings("rawtypes")
+    private static final Map<String, RedisQueue> INSTANCES = Maps.newConcurrentMap();
+
     private final String name;
-    private final RedisTemplate<String, E> template;
 
     public RedisQueue(@NonNull String name) {
-        this(name, null);
-    }
-
-    public RedisQueue(@NonNull String name, RedisTemplate<String, E> template) {
-        this.name = name;
-        this.template = template;
+        String profile = ApplicationContextHolder.getProfile();
+        this.name = StringUtils.isEmpty(profile) ? name : (profile + "-" + name);
     }
 
     /**
@@ -64,13 +70,38 @@ public class RedisQueue<E> extends AbstractQueue<E> {
         }
     }
 
+    /**
+     * 构建消息队列
+     *
+     * @param name 队列名称
+     * @param <E>  消息类型
+     * @return 队列实例
+     */
+    public static <E> RedisQueue<E> build(@NonNull String name) {
+        return build(name, RedisQueue::new);
+    }
+
+    /**
+     * 构建消息队列
+     *
+     * @param name     队列名称
+     * @param function 队列构建函数
+     * @param <E>      消息类型
+     * @return 队列实例
+     */
+    @SuppressWarnings("unchecked")
+    public static <E> RedisQueue<E> build(@NonNull String name, @NonNull Function<String, RedisQueue<E>> function) {
+        RedisQueue<E> queue = INSTANCES.get(name);
+        return queue == null ? INSTANCES.computeIfAbsent(name, function) : queue;
+    }
+
     /**
      * 获取RedisTemplate实例
      *
      * @return RedisTemplate实例
      */
-    private RedisTemplate<String, E> template() {
-        return ObjectUtils.ifNull(this.template, RedisContextHolder::getDefaultTemplate);
+    protected RedisTemplate<String, E> template() {
+        return RedisContextHolder.getDefaultTemplate();
     }
 
     @Override

+ 77 - 24
framework-redis/src/main/java/com/chelvc/framework/redis/queue/TemporalRedisQueue.java

@@ -8,9 +8,12 @@ import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.function.Function;
 
+import com.chelvc.framework.base.context.ApplicationContextHolder;
 import com.chelvc.framework.common.util.AssertUtils;
 import com.chelvc.framework.common.util.ObjectUtils;
+import com.chelvc.framework.common.util.StringUtils;
 import com.chelvc.framework.redis.context.RedisContextHolder;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Maps;
@@ -30,6 +33,12 @@ import org.springframework.data.redis.core.script.RedisScript;
  * @date 2024/8/29
  */
 public class TemporalRedisQueue<E> extends AbstractQueue<E> {
+    /**
+     * 队列名称/实例映射表
+     */
+    @SuppressWarnings("rawtypes")
+    private static final Map<String, TemporalRedisQueue> INSTANCES = Maps.newConcurrentMap();
+
     /**
      * Redis队列添加元素脚本(如果存在则忽略)
      */
@@ -47,26 +56,25 @@ public class TemporalRedisQueue<E> extends AbstractQueue<E> {
     );
 
     private final long idle;
+    private final long timeout;
     private final String name;
-    private final RedisTemplate<String, E> template;
+    private final List<String> keys;
 
     public TemporalRedisQueue(@NonNull String name) {
-        this(name, Duration.ofMinutes(1));
+        this(name, Duration.ofMinutes(1), Duration.ZERO);
     }
 
     public TemporalRedisQueue(@NonNull String name, @NonNull Duration idle) {
-        this(name, null, idle);
+        this(name, idle, Duration.ZERO);
     }
 
-    public TemporalRedisQueue(@NonNull String name, RedisTemplate<String, E> template) {
-        this(name, template, Duration.ofMinutes(1));
-    }
-
-    public TemporalRedisQueue(@NonNull String name, RedisTemplate<String, E> template, @NonNull Duration idle) {
+    public TemporalRedisQueue(@NonNull String name, @NonNull Duration idle, @NonNull Duration timeout) {
+        String profile = ApplicationContextHolder.getProfile();
+        this.name = StringUtils.isEmpty(profile) ? name : (profile + "-" + name);
         this.idle = idle.toMillis();
         AssertUtils.check(this.idle > 0, () -> "idle must be greater than 0");
-        this.name = name;
-        this.template = template;
+        this.timeout = timeout.toMillis();
+        this.keys = Collections.singletonList(this.name);
     }
 
     /**
@@ -102,13 +110,65 @@ public class TemporalRedisQueue<E> extends AbstractQueue<E> {
         }
     }
 
+    /**
+     * 构建消息队列
+     *
+     * @param name 队列名称
+     * @param <E>  消息类型
+     * @return 队列实例
+     */
+    public static <E> TemporalRedisQueue<E> build(@NonNull String name) {
+        return build(name, TemporalRedisQueue::new);
+    }
+
+    /**
+     * 构建消息队列
+     *
+     * @param name     队列名称
+     * @param function 队列构建函数
+     * @param <E>      消息类型
+     * @return 队列实例
+     */
+    @SuppressWarnings("unchecked")
+    public static <E> TemporalRedisQueue<E> build(@NonNull String name,
+                                                  @NonNull Function<String, TemporalRedisQueue<E>> function) {
+        TemporalRedisQueue<E> queue = INSTANCES.get(name);
+        return queue == null ? INSTANCES.computeIfAbsent(name, function) : queue;
+    }
+
+    /**
+     * 添加元素
+     *
+     * @param args 添加参数
+     * @return true/false
+     */
+    private boolean add(Object[] args) {
+        Long count;
+        if (this.timeout > 0) {
+            long max = System.currentTimeMillis() - this.timeout - 1000;
+            List<Object> values = this.template().executePipelined(new SessionCallback<Object>() {
+                @Override
+                @SuppressWarnings("unchecked")
+                public <K, V> Object execute(RedisOperations<K, V> operations) throws DataAccessException {
+                    operations.execute(ADD_SCRIPT, (List<K>) keys, args);
+                    operations.opsForZSet().removeRangeByScore((K) name, 0, max);
+                    return null;
+                }
+            });
+            count = ObjectUtils.isEmpty(values) ? null : (Long) values.get(0);
+        } else {
+            count = this.template().execute(ADD_SCRIPT, this.keys, args);
+        }
+        return count != null && count > 0;
+    }
+
     /**
      * 获取RedisTemplate实例
      *
      * @return RedisTemplate实例
      */
-    private RedisTemplate<String, E> template() {
-        return ObjectUtils.ifNull(this.template, RedisContextHolder::getDefaultTemplate);
+    protected RedisTemplate<String, E> template() {
+        return RedisContextHolder.getDefaultTemplate();
     }
 
     /**
@@ -179,21 +239,15 @@ public class TemporalRedisQueue<E> extends AbstractQueue<E> {
 
     @Override
     public boolean offer(E e) {
-        if (e == null) {
-            return false;
-        }
-
-        Object[] args = new Object[]{System.currentTimeMillis(), e};
-        Long count = this.template().execute(ADD_SCRIPT, Collections.singletonList(this.name), args);
-        return count != null && count > 0;
+        return e != null && this.add(new Object[]{System.currentTimeMillis(), e});
     }
 
     @Override
     @SuppressWarnings("unchecked")
     public E poll() {
-        List<String> keys = Collections.singletonList(this.name);
-        long min = 0, max = System.currentTimeMillis(), score = max + this.idle;
-        List<E> values = this.template().execute(POLL_SCRIPT, keys, min, max, score);
+        long max = System.currentTimeMillis();
+        long min = this.timeout > 0 ? max - this.timeout : 0, score = max + this.idle;
+        List<E> values = this.template().execute(POLL_SCRIPT, this.keys, min, max, score);
         return ObjectUtils.isEmpty(values) ? null : values.get(0);
     }
 
@@ -222,8 +276,7 @@ public class TemporalRedisQueue<E> extends AbstractQueue<E> {
             args[i++] = timestamp++;
             args[i++] = e;
         }
-        Long count = this.template().execute(ADD_SCRIPT, Collections.singletonList(this.name), args);
-        return count != null && count > 0;
+        return this.add(args);
     }
 
     @Override

+ 0 - 5
framework-rocketmq/src/main/java/com/chelvc/framework/rocketmq/config/RocketMQProperties.java

@@ -33,11 +33,6 @@ public class RocketMQProperties {
      */
     private String endpoints;
 
-    /**
-     * 命名空间
-     */
-    private String namespace;
-
     /**
      * 降级策略列表
      */

+ 10 - 19
framework-rocketmq/src/main/java/com/chelvc/framework/rocketmq/context/RocketMQContextHolder.java

@@ -17,7 +17,6 @@ import com.chelvc.framework.base.context.SessionContextHolder;
 import com.chelvc.framework.common.util.JacksonUtils;
 import com.chelvc.framework.common.util.ObjectUtils;
 import com.chelvc.framework.common.util.StringUtils;
-import com.chelvc.framework.rocketmq.config.RocketMQProperties;
 import lombok.NonNull;
 import org.apache.rocketmq.client.apis.ClientException;
 import org.apache.rocketmq.client.apis.ClientServiceProvider;
@@ -53,16 +52,6 @@ public final class RocketMQContextHolder {
     private RocketMQContextHolder() {
     }
 
-    /**
-     * 获取命名空间
-     *
-     * @return 命名空间
-     */
-    public static String getNamespace() {
-        RocketMQProperties properties = ApplicationContextHolder.getBean(RocketMQProperties.class, false);
-        return ObjectUtils.ifNull(properties, RocketMQProperties::getNamespace);
-    }
-
     /**
      * 获取消息生产者
      *
@@ -102,10 +91,11 @@ public final class RocketMQContextHolder {
      * @return 消息主题
      */
     public static String topic(@NonNull Message message) {
-        String namespace = getNamespace();
+        String namespace = ApplicationContextHolder.getProfile();
         String topic = message.getTopic(), tag = message.getTag().orElse(null);
-        if (StringUtils.notEmpty(namespace) && StringUtils.notEmpty(topic) && topic.startsWith(namespace)) {
-            topic = topic.substring(namespace.length());
+        if (StringUtils.notEmpty(namespace) && StringUtils.notEmpty(topic) && topic.startsWith(namespace)
+                && topic.length() > namespace.length() && topic.charAt(namespace.length()) == '-') {
+            topic = topic.substring(namespace.length() + 1);
         }
         return StringUtils.isEmpty(tag) ? topic : (topic + TOPIC_TAG_DELIMITER + tag);
     }
@@ -117,10 +107,11 @@ public final class RocketMQContextHolder {
      * @return 消息主题
      */
     public static String topic(@NonNull MessageView message) {
-        String namespace = getNamespace();
+        String namespace = ApplicationContextHolder.getProfile();
         String topic = message.getTopic(), tag = message.getTag().orElse(null);
-        if (StringUtils.notEmpty(namespace) && StringUtils.notEmpty(topic) && topic.startsWith(namespace)) {
-            topic = topic.substring(namespace.length());
+        if (StringUtils.notEmpty(namespace) && StringUtils.notEmpty(topic) && topic.startsWith(namespace)
+                && topic.length() > namespace.length() && topic.charAt(namespace.length()) == '-') {
+            topic = topic.substring(namespace.length() + 1);
         }
         return StringUtils.isEmpty(tag) ? topic : (topic + TOPIC_TAG_DELIMITER + tag);
     }
@@ -152,8 +143,8 @@ public final class RocketMQContextHolder {
      * @return 环境隔离标记
      */
     public static String isolate(@NonNull String original) {
-        String namespace = getNamespace();
-        return StringUtils.isEmpty(namespace) ? original : (namespace + original);
+        String namespace = ApplicationContextHolder.getProfile();
+        return StringUtils.isEmpty(namespace) ? original : (namespace + "-" + original);
     }
 
     /**