using System.Reflection;
using Dpz.Core.MessageQueue.Abstractions;
using Dpz.Core.MessageQueue.Attributes;
using Dpz.Core.MessageQueue.Enums;

namespace Dpz.Core.MessageQueue.RabbitMQ;

/// <summary>
/// 默认消息路由约定实现
/// 约定规则:
/// - Exchange: dpz.{module}.exchange  如:dpz.news.exchange
/// - Queue: dpz.{module}.{type}.queue 如:dpz.news.article.queue
/// - RoutingKey: {module}.{type}.{detail} 如:news.article.ithome
/// </summary>
public class DefaultMessageRoutingConvention : IMessageRoutingConvention
{
    private const string Prefix = "dpz";

    public string GetExchangeName<TMessage>()
    {
        // 检查是否有自定义特性
        var attribute = typeof(TMessage).GetCustomAttribute<MessageRouteAttribute>();
        if (!string.IsNullOrEmpty(attribute?.ExchangeName))
        {
            return attribute.ExchangeName;
        }

        // 约定:从消息类型名称提取模块名
        // 例如:NewsArticleMessage → news
        var typeName = typeof(TMessage).Name;
        var moduleName = ExtractModuleName(typeName);

        return $"{Prefix}.{moduleName}.exchange";
    }

    public string GetQueueName<TMessage>()
    {
        var attribute = typeof(TMessage).GetCustomAttribute<MessageRouteAttribute>();
        if (!string.IsNullOrEmpty(attribute?.QueueName))
        {
            return attribute.QueueName;
        }

        // 约定:dpz.{module}.{type}.queue
        // 例如:NewsArticleMessage → dpz.news.article.queue
        var typeName = typeof(TMessage).Name;
        var (moduleName, typeSuffix) = ParseMessageTypeName(typeName);

        return $"{Prefix}.{moduleName}.{typeSuffix}.queue";
    }

    public string GetRoutingKey<TMessage>()
    {
        var attribute = typeof(TMessage).GetCustomAttribute<MessageRouteAttribute>();
        if (!string.IsNullOrEmpty(attribute?.RoutingKey))
        {
            return attribute.RoutingKey;
        }

        // 约定:{module}.{type}.#
        // 例如:NewsArticleMessage → news.article.#
        var typeName = typeof(TMessage).Name;
        var (moduleName, typeSuffix) = ParseMessageTypeName(typeName);

        return $"{moduleName}.{typeSuffix}.#";
    }

    public ExchangeType GetExchangeType<TMessage>()
    {
        var attribute = typeof(TMessage).GetCustomAttribute<MessageRouteAttribute>();
        return attribute?.ExchangeType ?? ExchangeType.Topic;
    }

    /// <summary>
    /// 从消息类型名称中提取模块名
    /// NewsArticleMessage → news
    /// CacheClearMessage → cache
    /// </summary>
    private static string ExtractModuleName(string typeName)
    {
        // 移除 "Message" 后缀
        var name = typeName.EndsWith("Message", StringComparison.OrdinalIgnoreCase)
            ? typeName[..^7]
            : typeName;

        // 提取第一个词作为模块名(按驼峰命名分割)
        var parts = SplitCamelCase(name);
        return parts.Length > 0 ? parts[0].ToLowerInvariant() : "default";
    }

    /// <summary>
    /// 解析消息类型名称
    /// NewsArticleMessage → (news, article)
    /// CacheClearMessage → (cache, clear)
    /// </summary>
    private static (string ModuleName, string TypeName) ParseMessageTypeName(string typeName)
    {
        var name = typeName.EndsWith("Message", StringComparison.OrdinalIgnoreCase)
            ? typeName[..^7]
            : typeName;

        var parts = SplitCamelCase(name);

        if (parts.Length == 0)
        {
            return ("default", "message");
        }

        if (parts.Length == 1)
        {
            return (parts[0].ToLowerInvariant(), "message");
        }

        // 第一个词是模块名,其余的组合成类型名
        var moduleName = parts[0].ToLowerInvariant();
        var typeSuffix = string.Join(".", parts.Skip(1).Select(p => p.ToLowerInvariant()));

        return (moduleName, typeSuffix);
    }

    /// <summary>
    /// 按驼峰命名分割字符串
    /// "NewsArticle" → ["News", "Article"]
    /// </summary>
    private static string[] SplitCamelCase(string input)
    {
        if (string.IsNullOrEmpty(input))
        {
            return [];
        }

        var words = new List<string>();
        var currentWord = new System.Text.StringBuilder();

        foreach (var c in input)
        {
            if (char.IsUpper(c) && currentWord.Length > 0)
            {
                words.Add(currentWord.ToString());
                currentWord.Clear();
            }

            currentWord.Append(c);
        }

        if (currentWord.Length > 0)
        {
            words.Add(currentWord.ToString());
        }

        return words.ToArray();
    }
}
⚠⚠    以下内容为AI分析的结果,请根据实际情况进行判断。

这是一个用于RabbitMQ消息队列的消息路由约定实现类,它定义了如何根据消息类型自动生成Exchange、Queue和RoutingKey的命名规则。

主要功能

这个类实现了 IMessageRoutingConvention 接口,提供了一套约定优于配置的消息路由策略:

命名约定规则

  • Exchange: dpz.{module}.exchange (例如: dpz.news.exchange)
  • Queue: dpz.{module}.{type}.queue (例如: dpz.news.article.queue)
  • RoutingKey: {module}.{type}.# (例如: news.article.#)

核心方法

1. GetExchangeName<TMessage>()

  • 首先检查消息类是否有 MessageRouteAttribute 特性指定的Exchange名称
  • 如果没有,则从消息类型名称中提取模块名,生成约定格式的Exchange名称

2. GetQueueName<TMessage>()

  • 类似地检查特性配置
  • 解析消息类型名称,提取模块名和类型名,生成队列名称

3. GetRoutingKey<TMessage>()

  • 生成路由键,使用通配符 # 支持主题模式的消息路由

4. GetExchangeType<TMessage>()

  • 返回Exchange类型,默认为 Topic 类型

辅助方法

ExtractModuleName()

从消息类型名称提取模块名:

NewsArticleMessage → "news"
CacheClearMessage → "cache"

ParseMessageTypeName()

解析完整的消息类型信息:

NewsArticleMessage → (moduleName: "news", typeName: "article")
CacheClearMessage → (moduleName: "cache", typeName: "clear")

SplitCamelCase()

按驼峰命名法分割字符串:

"NewsArticle" → ["News", "Article"]

设计特点

  1. 约定优于配置: 通过类名自动推断路由信息,减少配置工作
  2. 特性支持: 支持通过 MessageRouteAttribute 覆盖默认约定
  3. 命名规范: 统一的命名前缀 "dpz",便于管理和识别
  4. 灵活性: 支持单模块和多层级的消息类型命名

这种设计让开发者可以通过简单的类命名约定自动获得合适的消息路由配置,同时保留了通过特性进行自定义的灵活性。

评论加载中...