MoneyTree.EFCore.MultiTenancy 1.0.4

MoneyTree.Core 核心抽象层

📋 概述

MoneyTree.Core 是 MoneyTree 框架的基石模块,提供纯粹的领域抽象,零持久化依赖。所有其他模块均基于此层构建。它定义了实体、值对象、领域事件、工作单元以及统一 API 响应等核心类型。

属性 说明
NuGet 包 MoneyTree.Core
外部依赖 MediatR.Contracts
定位 框架基石,纯领域抽象
设计原则 零持久化依赖、ID 简单直接、无外键关联、自动事务

🏗️ 项目文件结构

MoneyTree.Core/
├── MoneyTree.Core.csproj
├── GlobalUsings.cs
├── Abstractions/                  # 抽象接口层
│   ├── IEntity.cs                 # 实体接口
│   ├── IAggregateRoot.cs          # 聚合根接口
│   ├── IValueObject.cs            # 值对象接口
│   ├── IUnitOfWork.cs             # 工作单元接口(泛型 + 非泛型)
│   ├── IModule.cs                 # 模块化注册接口
│   └── IDomainEvent.cs            # 领域事件接口
├── Primitives/                    # 基类实现
│   ├── EntityBase.cs              # 实体抽象基类(领域事件管理)
│   ├── SnowflakeEntity.cs         # 实体基类(雪花ID,默认推荐)
│   ├── UlidEntity.cs              # 实体基类(ULID string ID,可选)
│   ├── AggregateRoot.cs           # 聚合根基类
│   ├── ValueObject.cs             # 值对象基类(自动值相等)
│   ├── Enumeration.cs             # 枚举类基类
│   ├── DomainEvent.cs             # 领域事件基类
│   ├── DomainException.cs         # 领域异常
│   ├── PageQuery.cs               # 统一分页查询参数
│   ├── PageResult.cs              # 分页结果(无汇总)
│   ├── PageResultTSummary.cs      # 分页结果(带汇总)
│   ├── ApiResult.cs               # 统一 API 响应(无数据)
│   ├── ApiResultT.cs              # 统一 API 响应(带数据)
│   └── SecretEncryptionService.cs # AES-256-GCM 共享加密服务
├── Guard/
│   └── Ensure.cs                  # 守卫断言工具类
├── Attributes/
│   ├── IoCAttribute.cs              # [IoC] 类级 IoC 注册标记(替代 IInjectable)
│   ├── InjectAttribute.cs           # [Inject] 属性注入标记(含 KeyName)
│   └── TimeZoneAwareAttribute.cs    # [TimeZoneAware] 时区感知标记
├── CurrentUser/
│   ├── ICurrentUser.cs            # 当前用户接口
│   └── CurrentUser.cs             # 当前用户基类
└── Extensions/
    ├── UlidExtension.cs            # Ulid 扩展方法
    ├── BaseNConverter.cs           # 任意进制(2~64)转换工具
    ├── DateTimeExtension.cs        # DateTime 扩展(如解析 yyyy-MM 格式)
    ├── EnumHelper.cs               # 枚举描述、下拉框选项、字典转换
    ├── ListExtension.cs            # IList/IEnumerable 安全遍历扩展
    ├── SnowflakeIdGenerator.cs     # 雪花算法 ID 生成器(64 位有序唯一 ID)
    ├── StringExtension.cs          # 字符串扩展(snake_case 转换等)
    └── UserIdHelper.cs             # 用户/会员 ID 与邀请码生成

🧱 领域模型基类

MoneyTree.Core 提供了一套完整的 DDD 领域模型基类,帮助开发者快速构建符合领域驱动设计规范的业务模型。

EntityBase — 实体抽象基类

所有实体基类的根基,统一提供领域事件管理能力(AddDomainEvent/RemoveDomainEvent/ClearDomainEvents)。 Entity(ULID)和 SnowflakeEntity(雪花ID)均继承此类,消除领域事件管理的代码重复。 ID 的类型和生成策略由子类自行声明(因 init 属性默认值需在声明时设置)。

UlidEntity — 实体基类(string ID,Ulid)

继承 EntityBase,ID 为 string 类型(存储 Ulid 的 26 字符 Base32 字符串)。 适用于需要字符串主键的场景。框架预置能力,业务项目按需选用。 主键映射通过 EF Core Fluent API 配置,不使用 [Key] 数据注解。

public abstract class UlidEntity : EntityBase
{
    public string Id { get; init; } = Ulid.NewUlid().ToString();
}

SnowflakeEntity — 实体基类(long ID,雪花算法)

继承 EntityBase默认推荐使用的实体基类。ID 为 long 类型(64 位有序唯一雪花 ID),对应数据库 bigint 列。 线程安全的雪花 ID 生成器在类初始化时自动分配主键。 主键映射通过 EF Core Fluent API 配置,不使用 [Key] 数据注解。

public abstract class SnowflakeEntity : EntityBase
{
    public long Id { get; init; } = _snowflake.Value.NextId();
}

public class Order : SnowflakeEntity  // 或继承 AggregateRoot(已基于 SnowflakeEntity)
{
    public long CustomerId { get; set; }  // 关联 User 的 ID(雪花 ID)
}

AggregateRoot — 聚合根

继承自 SnowflakeEntity,标记为聚合根。聚合根是领域模型中唯一允许外部访问的入口。 所有聚合根的 ID 均为 long(雪花 ID)。

ValueObject — 值对象

基于属性值的相等比较,而非 ID 引用。通过 GetEqualityComponents() 定义参与比较的属性。

public class Address : ValueObject
{
    public string Street { get; }
    public string City { get; }

    protected override IEnumerable<object> GetEqualityComponents()
    {
        yield return Street;
        yield return City;
    }
}

Enumeration — 枚举类

替代原始 enum 类型,支持更多的行为和属性:

public class OrderStatus : Enumeration
{
    public static readonly OrderStatus Pending = new(1, "待处理");
    public static readonly OrderStatus Shipped = new(2, "已发货");
    public static readonly OrderStatus Delivered = new(3, "已签收");
}

DomainEvent — 领域事件

领域事件基类,结合 MediatR.Contracts 实现事件驱动:

public class OrderCreatedDomainEvent : DomainEvent
{
    public string OrderId { get; }

    public OrderCreatedDomainEvent(string orderId)
    {
        OrderId = orderId;
    }
}

🔄 工作单元与依赖注入

类型 用途
IUnitOfWork 工作单元接口,SaveChangesAsync 自动事务提交
IUnitOfWork<TDbContext> 泛型工作单元,绑定特定 DbContext
IoCAttribute 类级特性,自动注册并启用属性注入
IModule 模块化批量注册接口
[Inject] 属性注入标记特性

👤 当前用户抽象

类型 用途
ICurrentUser 当前用户接口,提供用户 ID、用户名、租户 ID、角色
CurrentUser 当前用户基类,框架自动从 HttpContext 填充
属性 类型 说明
Id long 当前用户 ID(雪花 ID)
UserName string 用户名
TenantId long? 当前租户 ID(雪花 ID)
Roles IReadOnlyList<string> 角色列表

📦 ApiResult 设计

ApiResult 使用数字状态码 Code 替代 HTTP 状态码,提供丰富的静态工厂方法:

状态码 静态工厂方法 说明
200 ApiResult.Success() 请求成功
400 ApiResult.BadRequest(error) 请求参数错误
401 ApiResult.Unauthorized(message) 未认证
403 ApiResult.Forbidden(message) 无权限
404 ApiResult.NotFound(entityName, id) 资源未找到
409 ApiResult.Conflict(error) 资源冲突
500 ApiResult.Error(error) 服务器内部错误

ApiResult 属性

属性 类型 说明
Code int 状态码
Message string? 错误或成功的提示信息
Errors IReadOnlyList<string> 多条错误信息列表

ApiResult<T> 额外属性

属性 类型 说明
Data T? 成功时的返回数据

实例方法

方法 说明
Map(mapper) 映射成功的值
Bind(binder) 绑定操作(Monad 模式)
Match(onSuccess, onFailure) 模式匹配

响应格式

{
    "code": 200,
    "message": null,
    "data": { "id": "01H2X5J4K3M7Q8R9S0T1V2W3X4", "name": "订单001" }
}

📄 PageQuery 分页设计

基于毫秒时间戳锚点的统一分页设计,避免传统偏移分页的数据漂移问题。

属性 类型 默认值 说明
Timestamp double 当前时间偏移 毫秒时间戳锚点(相对 2026-01-01 UTC 的偏移),首页自动生成,翻页回传
PageTime DateTimeOffset 基于 Timestamp 计算的分页时间(只读),PageIndex=1 时返回当前 UTC 时间并刷新 Timestamp
PageIndex int 1 目标页码(从 1 开始)
PageSize int 20 每页大小(1-100)
SortField string? null 排序字段名
SortDirection SortDirection Desc 排序方向

分页流程

客户端首次请求(PageIndex=1)
        │
        ▼
PageTime 返回当前 UTC 时间,并刷新 Timestamp
查询:WHERE CreatedAt <= PageTime OFFSET (PageIndex-1)*PageSize LIMIT PageSize
        │
        ▼
返回 { items, summary, totalCount, timestamp }
        │
        ▼
客户端翻页(PageIndex=2,回传 Timestamp)
        │
        ▼
PageTime 基于 Timestamp 还原分页时间,数据视图保持一致

PageResult<T>(无 Summary)

属性 类型 说明
Items IReadOnlyList<T> 当前页数据
TotalCount int 总记录数
PageIndex int 当前页码
PageSize int 每页大小
Timestamp double 时间戳锚点(与 PageQuery.Timestamp 语义一致)

PageResult<T, TSummary>(带 Summary)

属性 类型 说明
Items IReadOnlyList<T> 当前页数据
Summary TSummary 汇总数据
TotalCount int 总记录数
PageIndex int 当前页码
PageSize int 每页大小
Timestamp double 时间戳锚点(与 PageQuery.Timestamp 语义一致)

🛡️ Ensure 守卫工具

提供一组参数校验断言方法:

方法 说明
Ensure.NotNull(value, paramName) 断言非空
Ensure.NotNullOrEmpty(value, paramName) 断言非空且非空白字符串
Ensure.NotNullOrWhiteSpace(value, paramName) 断言非空、非空白字符串
Ensure.NotEmpty(collection, paramName) 断言集合非空
Ensure.InRange(value, min, max, paramName) 断言数字在范围内

🧰 Extensions 工具类

SnowflakeIdGenerator — 雪花 ID 生成器

基于 Twitter Snowflake 算法的 64 位有序唯一 ID 生成器,线程安全。

参数 位数 说明
时间戳 41 位 毫秒级,默认纪元 2026-01-01
数据中心 ID 5 位 支持 0~31 个数据中心
机器 ID 5 位 支持 0~31 个节点
序列号 12 位 每毫秒 4096 个 ID
// 使用全局单例
var generator = SnowflakeIdGenerator.Instance;
// 或手动指定 datacenterId 和 workerId 创建独立实例
var customGen = new SnowflakeIdGenerator(datacenterId: 1, workerId: 2);

// 生成 ID
long id = generator.NextId();          // 686542123456789012
string idStr = generator.NextIdString(); // "686542123456789012"

// 从 ID 反向提取生成时间
var timestamp = SnowflakeIdGenerator.ExtractTimestampFromId(id);

UserIdHelper — 用户 ID 与邀请码生成

提供多种适用于用户与会员场景的 ID 生成策略:

方法 说明 示例
GenerateUserId(prefix) 雪花算法 + 前缀 "USR686542123456789012"
GenerateUserIdShort(length, prefix) Base62 短码,适合 URL "USRaB3xK9mQ"
GenerateMemberNumber(prefix) 日期 + 6 位随机数 "M20260701123456"
GenerateReferralCode(length) 加密强随机邀请码,默认 8 位 "A3K9MXPQ"

BaseNConverter — 任意进制转换

支持 2~64 进制字符串与整数(ulong/long/BigInteger)互转:

// 编码
string base62 = BaseNConverter.ToBase(12345UL, 62);     // "3D7"
string hex = BaseNConverter.ToBase(255UL, 16);           // "FF"

// 解码
ulong value = BaseNConverter.FromBaseToUInt64("3D7", 62); // 12345
BigInteger big = BaseNConverter.FromBaseToBigInteger("FF", 16); // 255

EnumHelper — 枚举帮助类

方法 说明
GetEnumDescription(value) 获取枚举 [Description] 特性值
ToDropdownItems<T>() 转换为下拉框选项(Code/Value/Label)
ToDictionary<T>() 转换为 Dictionary<int, string>
FromCode<T>(code) 根据数值获取枚举实例
FromName<T>(name) 根据名称获取枚举实例

ListExtension — 集合安全遍历

方法 说明
ForEach(action) 对 IList/IEnumerable 执行操作,索引遍历避免分配
ForEachSafe(action, throwOnModification) 安全遍历,检测集合修改时可抛异常或提前终止
ForEachWithCount(action) 遍历并返回已处理元素数量

StringExtension — 字符串扩展

方法 说明
ToSnakeCase() 将 PascalCase/camelCase 转换为 snake_case
IsNullOrWhiteSpace() 扩展方法形式的空字符串判断

DateTimeExtension — 日期扩展

方法 说明
ParseYearMonthString(value) 解析 "yyyy-MM" 格式字符串为当月第一天

⚠️ 领域异常

DomainException 用于在领域层抛出业务异常,包含错误码:

throw new DomainException("已发货的订单无法取消", 409, "ORDER_SHIPPED");

🎯 设计原则

  • 零持久化依赖:Core 层不引用 EF Core 或任何数据库包
  • ID 简单直接:默认使用 long(雪花 ID),对应数据库 bigint
  • 无外键关联:通过 long 类型的 CustomerId 存储关联 ID
  • 自动事务IUnitOfWork.SaveChangesAsync() 内部自动处理事务
  • 时区时间:配合 [TimeZoneAware] 特性或 UseTimeZoneAware() Fluent API 保障时区正确(按需配置,非全量应用)。MySQL 使用 CrossTimeZoneConverter,PG/SQL Server 使用 UtcDateTimeConverter

Showing the top 20 packages that depend on MoneyTree.EFCore.MultiTenancy.

Packages Downloads
MoneyTree.Audit.Shared
MoneyTree Audit 共享层。提供审计实体模型、EF Core 配置和 EfAuditLogger 直写实现。业务服务引用此包后,可通过 DbContext 在同事务中直接写入审计表,零网络调用、强一致、实时。
1
MoneyTree.Audit.Shared
MoneyTree Audit 共享层。提供审计实体模型、EF Core 配置和 EfAuditLogger 直写实现。业务服务引用此包后,可通过独立 DbContext 直写审计表,审计日志不受业务事务回滚影响。
1

.NET 10.0

Version Downloads Last updated
1.0.4 1 7/21/2026
1.0.3 1 7/12/2026