using System.Reflection;
using Mapster;
using MapsterMapper;
using MongoDB.Bson;

namespace Dpz.Core.Entity.Base.MapperConfig;

public static class GlobalConfigMapper
{
    private static readonly Lazy<TypeAdapterConfig> CachedConfig = new(CreateTypeAdapterConfig);

    private static readonly Lazy<IMapper> CachedMapper = new(() =>
        CreateMapper(CachedConfig.Value)
    );

    public static TypeAdapterConfig GetTypeAdapterConfig()
    {
        return CachedConfig.Value;
    }

    public static IMapper GetMapper()
    {
        return CachedMapper.Value;
    }

    private static TypeAdapterConfig CreateTypeAdapterConfig()
    {
        var config = new TypeAdapterConfig();

        // 获取所有需要扫描的程序集
        var assemblies = GetRelevantAssemblies();

        // 1. 扫描所有程序集中的类型
        var allTypes = assemblies.SelectMany(a => a.GetExportedTypes()).ToArray();

        // 2. 基于 IHaveCustomMapping 接口,自定义规则映射
        ConfigureStaticInterfaceMappings(allTypes, config);

        // 3. 内置映射规则
        ConfigureBuiltInMappings(config);

        return config;
    }

    public static IMapper CreateMapper(TypeAdapterConfig config)
    {
        return new Mapper(config);
    }

    /// <summary>
    /// 获取所有相关的程序集
    /// 只扫描项目相关的程序集,避免扫描系统程序集提升性能
    /// </summary>
    private static Assembly[] GetRelevantAssemblies()
    {
        var assemblies = AppDomain
            .CurrentDomain.GetAssemblies()
            .Where(a =>
            {
                var name = a.GetName().Name;
                // 只扫描项目相关的程序集
                return name != null
                    && name.StartsWith("Dpz.Core", StringComparison.OrdinalIgnoreCase);
            })
            .Where(a => !a.IsDynamic)
            .ToArray();

        return assemblies;
    }

    /// <summary>
    /// 配置静态接口映射(C# 11+)
    /// </summary>
    private static void ConfigureStaticInterfaceMappings(Type[] types, TypeAdapterConfig config)
    {
        var customMappingTypes = types.Where(x =>
            x.GetInterfaces().Any(i => i == typeof(IHaveCustomMapping))
            && x is { IsAbstract: false, IsInterface: false }
        );

        foreach (var type in customMappingTypes)
        {
            // 调用静态 CreateMappings 方法
            var method = type.GetMethod(
                nameof(IHaveCustomMapping.CreateMappings),
                BindingFlags.Static | BindingFlags.Public,
                null,
                [typeof(TypeAdapterConfig)],
                null
            );
            method?.Invoke(null, [config]);
        }
    }

    /// <summary>
    /// 配置内置映射规则
    /// </summary>
    private static void ConfigureBuiltInMappings(TypeAdapterConfig config)
    {
        config.NewConfig<string, ObjectId>().MapWith(x => ToObjectId(x));
    }

    private static ObjectId ToObjectId(string source)
    {
        return !string.IsNullOrEmpty(source) && ObjectId.TryParse(source, out var oid)
            ? oid
            : ObjectId.Empty;
    }
}
⚠⚠    以下内容为AI分析的结果,请根据实际情况进行判断。

这段代码的总作用是:基于 Mapster(对象映射库)在程序启动时构建并缓存一个全局的 TypeAdapterConfig(映射配置)和 IMapper(映射器),并在配置中自动扫描并应用自定义映射逻辑以及注册一些内置映射规则(例如 string -> MongoDB.Bson.ObjectId 的转换)。下面按部分逐项解释并给出一些注意点与建议。

核心结构(高层流程)

  • 使用两个 Lazy(CachedConfig、CachedMapper)延迟并且线程安全地创建并缓存 TypeAdapterConfig 和 IMapper,避免重复构建。
  • CreateTypeAdapterConfig:
    • 获取需要扫描的程序集(GetRelevantAssemblies)。
    • 获取所有导出类型(exported types)。
    • 调用 ConfigureStaticInterfaceMappings:查找并调用实现了 IHaveCustomMapping 的类型上的静态 CreateMappings(TypeAdapterConfig) 方法,以注册自定义映射规则。
    • 调用 ConfigureBuiltInMappings:注册内置映射(例如 string -> ObjectId)。
  • CreateMapper:基于 config 创建 Mapster 的 Mapper 并返回。

主要方法详解

  • GetTypeAdapterConfig() / GetMapper()

    • 返回已缓存的 TypeAdapterConfig / IMapper(通过 Lazy.Value 触发第一次构建)。
  • GetRelevantAssemblies()

    • 从 AppDomain.CurrentDomain.GetAssemblies() 里筛选,只保留名字以 "Dpz.Core" 开头并且非动态程序集(!a.IsDynamic)。
    • 目的是只扫描项目相关程序集以提高性能,避免扫描大量系统/第三方程序集。
  • ConfigureStaticInterfaceMappings(Type[] types, TypeAdapterConfig config)

    • 找到实现了 IHaveCustomMapping 的具体类型(非抽象类、非接口)。
    • 通过反射查找并调用类型上的静态方法 CreateMappings(TypeAdapterConfig) 来注册自定义规则。
    • 这是常见的做法,允许每个类型自行集中定义其映射规则(类似 Mapster/AutoMapper 的自定义映射接口模式)。
  • ConfigureBuiltInMappings(TypeAdapterConfig config)

    • 注册内置映射: config.NewConfig<string, ObjectId>().MapWith(x => ToObjectId(x));
    • 也就是将 string 转换为 MongoDB.Bson.ObjectId 时使用 ToObjectId 方法来解析或返回 ObjectId.Empty。
  • ToObjectId(string source)

    • 若 source 不是 null/空且能被 ObjectId.TryParse 解析,则返回解析后的 ObjectId,否则返回 ObjectId.Empty。

实现意图和优势

  • 延迟创建、一次构建并复用映射配置与映射器,避免重复开销并保证全局一致性。
  • 通过反射自动查找并调用实现了映射接口的类型的静态方法,使映射规则的组织更加模块化和可维护。
  • 注册常用内置映射(如 string -> ObjectId)可以集中处理类型转换的通用逻辑。

注意点与建议

  • 反射调用的小问题(代码片段中可能存在的语法/调用错误):
    • GetMethod 的重载用法应该像: method = type.GetMethod(nameof(IHaveCustomMapping.CreateMappings), BindingFlags.Static | BindingFlags.Public, null, new Type[] { typeof(TypeAdapterConfig) }, null); 而不是使用方括号 [typeof(TypeAdapterConfig)](那会导致语法错误)。
    • Invoke 时也应传入 object[]: method?.Invoke(null, new object[] { config });
  • 接口检测:
    • types.Where(x => x.GetInterfaces().Any(i => i == typeof(IHaveCustomMapping))) 可以工作,但如果接口是泛型或通过基接口实现,可能需要更通用的检测(例如 IsAssignableTo 或 IsAssignableFrom,.NET 5+ 可用)。
  • C# 版本与静态接口成员:
    • 如果 IHaveCustomMapping 只是普通接口,通常 CreateMappings 是实例方法并由某个实例调用;此处希望调用静态方法,可能是利用 C# 11 的 static abstract interface members 特性(或约定每个类都实现一个 public static CreateMappings 方法)。请确认 IHaveCustomMapping 的定义与预期一致。
  • 扫描范围:
    • 目前只扫描以 "Dpz.Core" 开头的程序集,若有映射类型位于其它程序集(例如 Dpz.Core.Extensions 或第三方库),需要调整筛选规则以包含这些程序集。
  • 错误处理:
    • 当前对反射找不到方法只是静默忽略(method?),如果某个类型按约定应该提供 CreateMappings,建议在调试或开发阶段至少记录日志或抛出明确异常以便发现问题。
  • 线程安全:
    • 默认的 Lazy 是线程安全的(LazyThreadSafetyMode.ExecutionAndPublication),满足多数场景。如果你在启动并发环境有特殊需求,可明确设置 Lazy 的构造参数。

总结 该类负责在程序中全局初始化与缓存 Mapster 的映射配置与映射器:扫描指定程序集中的类型,自动加载每个类型声明的自定义静态映射规则,并注册一些通用映射(如 string->ObjectId)。这让映射规则集中可维护、性能友好(仅构建一次),但需要注意反射调用的实现细节、程序集扫描范围以及必要的错误/日志处理。

评论加载中...