feat: 新增配额包/支付相关实体与迁移

App:新增 operation_logs/quota_packages/tenant_payments/tenant_quota_package_purchases 表

Identity:修正 Avatar 字段类型(varchar(256)->text),保持现有数据不变
This commit is contained in:
2025-12-17 17:27:45 +08:00
parent 9c28790f5e
commit ab59e2e3e2
103 changed files with 14450 additions and 4 deletions

View File

@@ -0,0 +1,49 @@
using TakeoutSaaS.Shared.Abstractions.Entities;
namespace TakeoutSaaS.Domain.Tenants.Entities;
/// <summary>
/// 运营操作日志。
/// </summary>
public sealed class OperationLog : AuditableEntityBase
{
/// <summary>
/// 操作类型BatchExtend, BatchRemind, StatusChange 等。
/// </summary>
public string OperationType { get; set; } = string.Empty;
/// <summary>
/// 目标类型Subscription, Bill 等。
/// </summary>
public string TargetType { get; set; } = string.Empty;
/// <summary>
/// 目标ID列表JSON
/// </summary>
public string? TargetIds { get; set; }
/// <summary>
/// 操作人ID。
/// </summary>
public string? OperatorId { get; set; }
/// <summary>
/// 操作人名称。
/// </summary>
public string? OperatorName { get; set; }
/// <summary>
/// 操作参数JSON
/// </summary>
public string? Parameters { get; set; }
/// <summary>
/// 操作结果JSON
/// </summary>
public string? Result { get; set; }
/// <summary>
/// 是否成功。
/// </summary>
public bool Success { get; set; }
}

View File

@@ -0,0 +1,45 @@
using TakeoutSaaS.Domain.Tenants.Enums;
using TakeoutSaaS.Shared.Abstractions.Entities;
namespace TakeoutSaaS.Domain.Tenants.Entities;
/// <summary>
/// 配额包定义(平台提供的可购买配额包)。
/// </summary>
public sealed class QuotaPackage : AuditableEntityBase
{
/// <summary>
/// 配额包名称。
/// </summary>
public string Name { get; set; } = string.Empty;
/// <summary>
/// 配额类型。
/// </summary>
public TenantQuotaType QuotaType { get; set; }
/// <summary>
/// 配额数值。
/// </summary>
public decimal QuotaValue { get; set; }
/// <summary>
/// 价格。
/// </summary>
public decimal Price { get; set; }
/// <summary>
/// 是否上架。
/// </summary>
public bool IsActive { get; set; } = true;
/// <summary>
/// 排序。
/// </summary>
public int SortOrder { get; set; } = 0;
/// <summary>
/// 描述。
/// </summary>
public string? Description { get; set; }
}

View File

@@ -0,0 +1,50 @@
using TakeoutSaaS.Domain.Tenants.Enums;
using TakeoutSaaS.Shared.Abstractions.Entities;
namespace TakeoutSaaS.Domain.Tenants.Entities;
/// <summary>
/// 租户支付记录。
/// </summary>
public sealed class TenantPayment : MultiTenantEntityBase
{
/// <summary>
/// 关联的账单 ID。
/// </summary>
public long BillingStatementId { get; set; }
/// <summary>
/// 支付金额。
/// </summary>
public decimal Amount { get; set; }
/// <summary>
/// 支付方式。
/// </summary>
public PaymentMethod Method { get; set; }
/// <summary>
/// 支付状态。
/// </summary>
public PaymentStatus Status { get; set; }
/// <summary>
/// 交易号。
/// </summary>
public string? TransactionNo { get; set; }
/// <summary>
/// 支付凭证 URL。
/// </summary>
public string? ProofUrl { get; set; }
/// <summary>
/// 支付时间。
/// </summary>
public DateTime? PaidAt { get; set; }
/// <summary>
/// 备注信息。
/// </summary>
public string? Notes { get; set; }
}

View File

@@ -0,0 +1,39 @@
using TakeoutSaaS.Shared.Abstractions.Entities;
namespace TakeoutSaaS.Domain.Tenants.Entities;
/// <summary>
/// 租户配额包购买记录。
/// </summary>
public sealed class TenantQuotaPackagePurchase : MultiTenantEntityBase
{
/// <summary>
/// 配额包 ID。
/// </summary>
public long QuotaPackageId { get; set; }
/// <summary>
/// 购买时的配额值。
/// </summary>
public decimal QuotaValue { get; set; }
/// <summary>
/// 购买价格。
/// </summary>
public decimal Price { get; set; }
/// <summary>
/// 购买时间。
/// </summary>
public DateTime PurchasedAt { get; set; }
/// <summary>
/// 过期时间(可选)。
/// </summary>
public DateTime? ExpiredAt { get; set; }
/// <summary>
/// 备注。
/// </summary>
public string? Notes { get; set; }
}

View File

@@ -0,0 +1,22 @@
namespace TakeoutSaaS.Domain.Tenants.Enums;
/// <summary>
/// 支付方式。
/// </summary>
public enum PaymentMethod
{
/// <summary>
/// 线上支付。
/// </summary>
Online = 0,
/// <summary>
/// 银行转账。
/// </summary>
BankTransfer = 1,
/// <summary>
/// 其他方式。
/// </summary>
Other = 2
}

View File

@@ -0,0 +1,27 @@
namespace TakeoutSaaS.Domain.Tenants.Enums;
/// <summary>
/// 支付状态。
/// </summary>
public enum PaymentStatus
{
/// <summary>
/// 待支付。
/// </summary>
Pending = 0,
/// <summary>
/// 支付成功。
/// </summary>
Success = 1,
/// <summary>
/// 支付失败。
/// </summary>
Failed = 2,
/// <summary>
/// 已退款。
/// </summary>
Refunded = 3
}

View File

@@ -0,0 +1,131 @@
using TakeoutSaaS.Domain.Tenants.Entities;
using TakeoutSaaS.Domain.Tenants.Enums;
namespace TakeoutSaaS.Domain.Tenants.Repositories;
/// <summary>
/// 配额包仓储。
/// </summary>
public interface IQuotaPackageRepository
{
#region
/// <summary>
/// 按 ID 查找配额包。
/// </summary>
/// <param name="id">配额包 ID雪花算法。</param>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>配额包实体,未找到返回 null。</returns>
Task<QuotaPackage?> FindByIdAsync(long id, CancellationToken cancellationToken = default);
/// <summary>
/// 分页查询配额包。
/// </summary>
/// <param name="quotaType">配额类型,为空不按类型过滤。</param>
/// <param name="isActive">启用状态,为空不按状态过滤。</param>
/// <param name="page">页码(从 1 开始)。</param>
/// <param name="pageSize">每页大小。</param>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>分页数据与总数。</returns>
Task<(IReadOnlyList<QuotaPackage> Items, int Total)> SearchPagedAsync(
TenantQuotaType? quotaType,
bool? isActive,
int page,
int pageSize,
CancellationToken cancellationToken = default);
/// <summary>
/// 新增配额包。
/// </summary>
/// <param name="quotaPackage">配额包实体。</param>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>异步任务。</returns>
Task AddAsync(QuotaPackage quotaPackage, CancellationToken cancellationToken = default);
/// <summary>
/// 更新配额包。
/// </summary>
/// <param name="quotaPackage">配额包实体。</param>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>异步任务。</returns>
Task UpdateAsync(QuotaPackage quotaPackage, CancellationToken cancellationToken = default);
/// <summary>
/// 软删除配额包。
/// </summary>
/// <param name="id">配额包 ID雪花算法。</param>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>删除成功返回 true未找到返回 false。</returns>
Task<bool> SoftDeleteAsync(long id, CancellationToken cancellationToken = default);
#endregion
#region
/// <summary>
/// 分页查询租户配额购买记录(包含配额包信息)。
/// </summary>
/// <param name="tenantId">租户 ID雪花算法。</param>
/// <param name="page">页码(从 1 开始)。</param>
/// <param name="pageSize">每页大小。</param>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>分页数据与总数。</returns>
Task<(IReadOnlyList<(TenantQuotaPackagePurchase Purchase, QuotaPackage Package)> Items, int Total)> GetPurchasesPagedAsync(
long tenantId,
int page,
int pageSize,
CancellationToken cancellationToken = default);
/// <summary>
/// 新增配额购买记录。
/// </summary>
/// <param name="purchase">购买记录实体。</param>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>异步任务。</returns>
Task AddPurchaseAsync(TenantQuotaPackagePurchase purchase, CancellationToken cancellationToken = default);
#endregion
#region 使
/// <summary>
/// 查询租户配额使用情况。
/// </summary>
/// <param name="tenantId">租户 ID雪花算法。</param>
/// <param name="quotaType">配额类型,为空查询全部。</param>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>配额使用情况列表。</returns>
Task<IReadOnlyList<TenantQuotaUsage>> GetUsageByTenantAsync(
long tenantId,
TenantQuotaType? quotaType,
CancellationToken cancellationToken = default);
/// <summary>
/// 查找特定配额使用记录。
/// </summary>
/// <param name="tenantId">租户 ID雪花算法。</param>
/// <param name="quotaType">配额类型。</param>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>配额使用记录,未找到返回 null。</returns>
Task<TenantQuotaUsage?> FindUsageAsync(
long tenantId,
TenantQuotaType quotaType,
CancellationToken cancellationToken = default);
/// <summary>
/// 更新配额使用情况。
/// </summary>
/// <param name="usage">配额使用实体。</param>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>异步任务。</returns>
Task UpdateUsageAsync(TenantQuotaUsage usage, CancellationToken cancellationToken = default);
#endregion
/// <summary>
/// 持久化。
/// </summary>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>异步任务。</returns>
Task SaveChangesAsync(CancellationToken cancellationToken = default);
}

View File

@@ -0,0 +1,112 @@
using TakeoutSaaS.Domain.Tenants.Entities;
using TakeoutSaaS.Domain.Tenants.Enums;
namespace TakeoutSaaS.Domain.Tenants.Repositories;
/// <summary>
/// 统计数据仓储接口。
/// </summary>
public interface IStatisticsRepository
{
#region
/// <summary>
/// 获取所有订阅(用于统计)。
/// </summary>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>所有订阅记录。</returns>
Task<IReadOnlyList<TenantSubscription>> GetAllSubscriptionsAsync(CancellationToken cancellationToken = default);
/// <summary>
/// 获取即将到期的订阅(含租户和套餐信息)。
/// </summary>
/// <param name="daysAhead">到期天数。</param>
/// <param name="onlyWithoutAutoRenew">是否仅查询未开启自动续费的。</param>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>即将到期的订阅信息列表。</returns>
Task<IReadOnlyList<ExpiringSubscriptionInfo>> GetExpiringSubscriptionsAsync(
int daysAhead,
bool onlyWithoutAutoRenew,
CancellationToken cancellationToken = default);
#endregion
#region
/// <summary>
/// 获取所有已付款账单(用于收入统计)。
/// </summary>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>已付款账单列表。</returns>
Task<IReadOnlyList<TenantBillingStatement>> GetPaidBillsAsync(CancellationToken cancellationToken = default);
#endregion
#region 使
/// <summary>
/// 获取配额使用排行(含租户信息)。
/// </summary>
/// <param name="quotaType">配额类型。</param>
/// <param name="topN">前 N 名。</param>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>配额使用排行列表。</returns>
Task<IReadOnlyList<QuotaUsageRankInfo>> GetQuotaUsageRankingAsync(
TenantQuotaType quotaType,
int topN,
CancellationToken cancellationToken = default);
#endregion
}
/// <summary>
/// 即将到期的订阅信息(含关联数据)。
/// </summary>
public record ExpiringSubscriptionInfo
{
/// <summary>
/// 订阅实体。
/// </summary>
public required TenantSubscription Subscription { get; init; }
/// <summary>
/// 租户名称。
/// </summary>
public required string TenantName { get; init; }
/// <summary>
/// 套餐名称。
/// </summary>
public required string PackageName { get; init; }
}
/// <summary>
/// 配额使用排行信息(含租户名称)。
/// </summary>
public record QuotaUsageRankInfo
{
/// <summary>
/// 租户 ID。
/// </summary>
public long TenantId { get; init; }
/// <summary>
/// 租户名称。
/// </summary>
public required string TenantName { get; init; }
/// <summary>
/// 已使用值。
/// </summary>
public decimal UsedValue { get; init; }
/// <summary>
/// 限制值。
/// </summary>
public decimal LimitValue { get; init; }
/// <summary>
/// 使用百分比。
/// </summary>
public decimal UsagePercentage { get; init; }
}

View File

@@ -0,0 +1,295 @@
using TakeoutSaaS.Domain.Tenants.Entities;
using TakeoutSaaS.Domain.Tenants.Enums;
namespace TakeoutSaaS.Domain.Tenants.Repositories;
/// <summary>
/// 订阅管理仓储接口。
/// </summary>
public interface ISubscriptionRepository
{
#region
/// <summary>
/// 按 ID 查询订阅。
/// </summary>
/// <param name="subscriptionId">订阅 ID。</param>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>订阅实体,未找到返回 null。</returns>
Task<TenantSubscription?> FindByIdAsync(long subscriptionId, CancellationToken cancellationToken = default);
/// <summary>
/// 按 ID 列表批量查询订阅。
/// </summary>
/// <param name="subscriptionIds">订阅 ID 列表。</param>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>订阅实体列表。</returns>
Task<IReadOnlyList<TenantSubscription>> FindByIdsAsync(
IEnumerable<long> subscriptionIds,
CancellationToken cancellationToken = default);
/// <summary>
/// 分页查询订阅列表(含关联信息)。
/// </summary>
/// <param name="filter">查询过滤条件。</param>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>分页结果。</returns>
Task<(IReadOnlyList<SubscriptionWithRelations> Items, int Total)> SearchPagedAsync(
SubscriptionSearchFilter filter,
CancellationToken cancellationToken = default);
/// <summary>
/// 获取订阅详情(含关联信息)。
/// </summary>
/// <param name="subscriptionId">订阅 ID。</param>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>订阅详情信息。</returns>
Task<SubscriptionDetailInfo?> GetDetailAsync(long subscriptionId, CancellationToken cancellationToken = default);
/// <summary>
/// 按 ID 列表批量查询订阅(含租户信息)。
/// </summary>
/// <param name="subscriptionIds">订阅 ID 列表。</param>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>订阅与租户信息列表。</returns>
Task<IReadOnlyList<SubscriptionWithTenant>> FindByIdsWithTenantAsync(
IEnumerable<long> subscriptionIds,
CancellationToken cancellationToken = default);
#endregion
#region
/// <summary>
/// 按 ID 查询套餐。
/// </summary>
/// <param name="packageId">套餐 ID。</param>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>套餐实体,未找到返回 null。</returns>
Task<TenantPackage?> FindPackageByIdAsync(long packageId, CancellationToken cancellationToken = default);
#endregion
#region
/// <summary>
/// 更新订阅。
/// </summary>
/// <param name="subscription">订阅实体。</param>
/// <param name="cancellationToken">取消标记。</param>
Task UpdateAsync(TenantSubscription subscription, CancellationToken cancellationToken = default);
#endregion
#region
/// <summary>
/// 添加订阅变更历史。
/// </summary>
/// <param name="history">历史记录实体。</param>
/// <param name="cancellationToken">取消标记。</param>
Task AddHistoryAsync(TenantSubscriptionHistory history, CancellationToken cancellationToken = default);
/// <summary>
/// 获取订阅变更历史(含套餐名称)。
/// </summary>
/// <param name="subscriptionId">订阅 ID。</param>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>历史记录列表。</returns>
Task<IReadOnlyList<SubscriptionHistoryWithPackageNames>> GetHistoryAsync(
long subscriptionId,
CancellationToken cancellationToken = default);
#endregion
#region 使
/// <summary>
/// 获取租户配额使用情况。
/// </summary>
/// <param name="tenantId">租户 ID。</param>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>配额使用列表。</returns>
Task<IReadOnlyList<TenantQuotaUsage>> GetQuotaUsagesAsync(
long tenantId,
CancellationToken cancellationToken = default);
#endregion
#region
/// <summary>
/// 添加租户通知。
/// </summary>
/// <param name="notification">通知实体。</param>
/// <param name="cancellationToken">取消标记。</param>
Task AddNotificationAsync(TenantNotification notification, CancellationToken cancellationToken = default);
#endregion
#region
/// <summary>
/// 添加操作日志。
/// </summary>
/// <param name="log">日志实体。</param>
/// <param name="cancellationToken">取消标记。</param>
Task AddOperationLogAsync(OperationLog log, CancellationToken cancellationToken = default);
#endregion
/// <summary>
/// 保存变更。
/// </summary>
/// <param name="cancellationToken">取消标记。</param>
Task SaveChangesAsync(CancellationToken cancellationToken = default);
}
#region
/// <summary>
/// 订阅查询过滤条件。
/// </summary>
public record SubscriptionSearchFilter
{
/// <summary>
/// 订阅状态。
/// </summary>
public SubscriptionStatus? Status { get; init; }
/// <summary>
/// 套餐 ID。
/// </summary>
public long? TenantPackageId { get; init; }
/// <summary>
/// 租户 ID。
/// </summary>
public long? TenantId { get; init; }
/// <summary>
/// 租户关键词(名称或编码)。
/// </summary>
public string? TenantKeyword { get; init; }
/// <summary>
/// 即将到期天数。
/// </summary>
public int? ExpiringWithinDays { get; init; }
/// <summary>
/// 自动续费状态。
/// </summary>
public bool? AutoRenew { get; init; }
/// <summary>
/// 页码(从 1 开始)。
/// </summary>
public int Page { get; init; } = 1;
/// <summary>
/// 每页数量。
/// </summary>
public int PageSize { get; init; } = 20;
}
/// <summary>
/// 订阅及关联信息。
/// </summary>
public record SubscriptionWithRelations
{
/// <summary>
/// 订阅实体。
/// </summary>
public required TenantSubscription Subscription { get; init; }
/// <summary>
/// 租户名称。
/// </summary>
public required string TenantName { get; init; }
/// <summary>
/// 租户编码。
/// </summary>
public required string TenantCode { get; init; }
/// <summary>
/// 套餐名称。
/// </summary>
public required string PackageName { get; init; }
/// <summary>
/// 排期套餐名称(可选)。
/// </summary>
public string? ScheduledPackageName { get; init; }
}
/// <summary>
/// 订阅详情信息。
/// </summary>
public record SubscriptionDetailInfo
{
/// <summary>
/// 订阅实体。
/// </summary>
public required TenantSubscription Subscription { get; init; }
/// <summary>
/// 租户名称。
/// </summary>
public required string TenantName { get; init; }
/// <summary>
/// 租户编码。
/// </summary>
public required string TenantCode { get; init; }
/// <summary>
/// 当前套餐。
/// </summary>
public TenantPackage? Package { get; init; }
/// <summary>
/// 排期套餐。
/// </summary>
public TenantPackage? ScheduledPackage { get; init; }
}
/// <summary>
/// 订阅与租户信息。
/// </summary>
public record SubscriptionWithTenant
{
/// <summary>
/// 订阅实体。
/// </summary>
public required TenantSubscription Subscription { get; init; }
/// <summary>
/// 租户实体。
/// </summary>
public required Tenant Tenant { get; init; }
}
/// <summary>
/// 订阅历史(含套餐名称)。
/// </summary>
public record SubscriptionHistoryWithPackageNames
{
/// <summary>
/// 历史记录实体。
/// </summary>
public required TenantSubscriptionHistory History { get; init; }
/// <summary>
/// 原套餐名称。
/// </summary>
public required string FromPackageName { get; init; }
/// <summary>
/// 目标套餐名称。
/// </summary>
public required string ToPackageName { get; init; }
}
#endregion

View File

@@ -64,4 +64,34 @@ public interface ITenantBillingRepository
/// <param name="cancellationToken">取消标记。</param>
/// <returns>异步任务。</returns>
Task SaveChangesAsync(CancellationToken cancellationToken = default);
/// <summary>
/// 管理员端分页查询账单列表(跨租户)。
/// </summary>
/// <param name="tenantId">租户 ID 筛选(可选)。</param>
/// <param name="status">账单状态筛选(可选)。</param>
/// <param name="from">开始时间UTC可选。</param>
/// <param name="to">结束时间UTC可选。</param>
/// <param name="keyword">关键词搜索(账单号或租户名)。</param>
/// <param name="pageNumber">页码(从 1 开始)。</param>
/// <param name="pageSize">页大小。</param>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>账单集合与总数。</returns>
Task<(IReadOnlyList<TenantBillingStatement> Items, int Total)> SearchPagedAsync(
long? tenantId,
TenantBillingStatus? status,
DateTime? from,
DateTime? to,
string? keyword,
int pageNumber,
int pageSize,
CancellationToken cancellationToken = default);
/// <summary>
/// 按 ID 获取账单(不限租户,管理员端使用)。
/// </summary>
/// <param name="billingId">账单 ID。</param>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>账单实体或 null。</returns>
Task<TenantBillingStatement?> FindByIdAsync(long billingId, CancellationToken cancellationToken = default);
}

View File

@@ -0,0 +1,48 @@
using TakeoutSaaS.Domain.Tenants.Entities;
namespace TakeoutSaaS.Domain.Tenants.Repositories;
/// <summary>
/// 租户支付记录仓储。
/// </summary>
public interface ITenantPaymentRepository
{
/// <summary>
/// 查询指定账单的支付记录列表。
/// </summary>
/// <param name="billingStatementId">账单 ID。</param>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>支付记录集合。</returns>
Task<IReadOnlyList<TenantPayment>> GetByBillingIdAsync(long billingStatementId, CancellationToken cancellationToken = default);
/// <summary>
/// 按 ID 获取支付记录。
/// </summary>
/// <param name="paymentId">支付记录 ID。</param>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>支付记录实体或 null。</returns>
Task<TenantPayment?> FindByIdAsync(long paymentId, CancellationToken cancellationToken = default);
/// <summary>
/// 新增支付记录。
/// </summary>
/// <param name="payment">支付记录实体。</param>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>异步任务。</returns>
Task AddAsync(TenantPayment payment, CancellationToken cancellationToken = default);
/// <summary>
/// 更新支付记录。
/// </summary>
/// <param name="payment">支付记录实体。</param>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>异步任务。</returns>
Task UpdateAsync(TenantPayment payment, CancellationToken cancellationToken = default);
/// <summary>
/// 保存变更。
/// </summary>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>异步任务。</returns>
Task SaveChangesAsync(CancellationToken cancellationToken = default);
}

View File

@@ -16,6 +16,14 @@ public interface ITenantRepository
/// <returns>租户实体,未找到返回 null。</returns>
Task<Tenant?> FindByIdAsync(long tenantId, CancellationToken cancellationToken = default);
/// <summary>
/// 批量获取租户。
/// </summary>
/// <param name="tenantIds">租户 ID 列表。</param>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>租户列表(仅返回找到的租户)。</returns>
Task<IReadOnlyList<Tenant>> FindByIdsAsync(IReadOnlyCollection<long> tenantIds, CancellationToken cancellationToken = default);
/// <summary>
/// 按状态与关键词查询租户列表。
/// </summary>