feat: migrate snowflake ids and refresh migrations

This commit is contained in:
2025-12-02 09:04:37 +08:00
parent 462e15abbb
commit 148475fa43
174 changed files with 8020 additions and 34278 deletions

View File

@@ -0,0 +1,23 @@
using System.Reflection;
using MediatR;
using Microsoft.Extensions.DependencyInjection;
namespace TakeoutSaaS.Application.App.Extensions;
/// <summary>
/// 业务应用层服务注册。
/// </summary>
public static class AppApplicationServiceCollectionExtensions
{
/// <summary>
/// 注册业务应用层MediatR 处理器等)。
/// </summary>
/// <param name="services">服务集合。</param>
/// <returns>服务集合。</returns>
public static IServiceCollection AddAppApplication(this IServiceCollection services)
{
services.AddMediatR(Assembly.GetExecutingAssembly());
return services;
}
}

View File

@@ -0,0 +1,53 @@
using MediatR;
using System.ComponentModel.DataAnnotations;
using TakeoutSaaS.Application.App.Merchants.Dto;
using TakeoutSaaS.Domain.Merchants.Enums;
namespace TakeoutSaaS.Application.App.Merchants.Commands;
/// <summary>
/// 创建商户命令。
/// </summary>
public sealed class CreateMerchantCommand : IRequest<MerchantDto>
{
/// <summary>
/// 品牌名称。
/// </summary>
[Required, MaxLength(128)]
public string BrandName { get; init; } = string.Empty;
/// <summary>
/// 品牌简称。
/// </summary>
[MaxLength(64)]
public string? BrandAlias { get; init; }
/// <summary>
/// 品牌 Logo。
/// </summary>
[MaxLength(256)]
public string? LogoUrl { get; init; }
/// <summary>
/// 品类。
/// </summary>
[MaxLength(64)]
public string? Category { get; init; }
/// <summary>
/// 联系电话。
/// </summary>
[Required, MaxLength(32)]
public string ContactPhone { get; init; } = string.Empty;
/// <summary>
/// 联系邮箱。
/// </summary>
[MaxLength(128)]
public string? ContactEmail { get; init; }
/// <summary>
/// 状态,可用于直接设为审核通过等场景。
/// </summary>
public MerchantStatus Status { get; init; } = MerchantStatus.Pending;
}

View File

@@ -0,0 +1,63 @@
using System.Text.Json.Serialization;
using TakeoutSaaS.Domain.Merchants.Enums;
using TakeoutSaaS.Shared.Abstractions.Serialization;
namespace TakeoutSaaS.Application.App.Merchants.Dto;
/// <summary>
/// 商户 DTO。
/// </summary>
public sealed class MerchantDto
{
/// <summary>
/// 商户 ID。
/// </summary>
[JsonConverter(typeof(SnowflakeIdJsonConverter))]
public long Id { get; init; }
/// <summary>
/// 租户 ID。
/// </summary>
[JsonConverter(typeof(SnowflakeIdJsonConverter))]
public long TenantId { get; init; }
/// <summary>
/// 品牌名称。
/// </summary>
public string BrandName { get; init; } = string.Empty;
/// <summary>
/// 品牌简称。
/// </summary>
public string? BrandAlias { get; init; }
/// <summary>
/// 品牌 Logo。
/// </summary>
public string? LogoUrl { get; init; }
/// <summary>
/// 品类。
/// </summary>
public string? Category { get; init; }
/// <summary>
/// 联系电话。
/// </summary>
public string ContactPhone { get; init; } = string.Empty;
/// <summary>
/// 联系邮箱。
/// </summary>
public string? ContactEmail { get; init; }
/// <summary>
/// 入驻状态。
/// </summary>
public MerchantStatus Status { get; init; }
/// <summary>
/// 入驻时间。
/// </summary>
public DateTime? JoinedAt { get; init; }
}

View File

@@ -0,0 +1,54 @@
using MediatR;
using Microsoft.Extensions.Logging;
using TakeoutSaaS.Application.App.Merchants.Commands;
using TakeoutSaaS.Application.App.Merchants.Dto;
using TakeoutSaaS.Domain.Merchants.Entities;
using TakeoutSaaS.Domain.Merchants.Repositories;
namespace TakeoutSaaS.Application.App.Merchants.Handlers;
/// <summary>
/// 创建商户命令处理器。
/// </summary>
public sealed class CreateMerchantCommandHandler(IMerchantRepository merchantRepository, ILogger<CreateMerchantCommandHandler> logger)
: IRequestHandler<CreateMerchantCommand, MerchantDto>
{
private readonly IMerchantRepository _merchantRepository = merchantRepository;
private readonly ILogger<CreateMerchantCommandHandler> _logger = logger;
/// <inheritdoc />
public async Task<MerchantDto> Handle(CreateMerchantCommand request, CancellationToken cancellationToken)
{
var merchant = new Merchant
{
BrandName = request.BrandName.Trim(),
BrandAlias = request.BrandAlias?.Trim(),
LogoUrl = request.LogoUrl?.Trim(),
Category = request.Category?.Trim(),
ContactPhone = request.ContactPhone.Trim(),
ContactEmail = request.ContactEmail?.Trim(),
Status = request.Status,
JoinedAt = DateTime.UtcNow
};
await _merchantRepository.AddMerchantAsync(merchant, cancellationToken);
await _merchantRepository.SaveChangesAsync(cancellationToken);
_logger.LogInformation("创建商户 {MerchantId} - {BrandName}", merchant.Id, merchant.BrandName);
return MapToDto(merchant);
}
private static MerchantDto MapToDto(Merchant merchant) => new()
{
Id = merchant.Id,
TenantId = merchant.TenantId,
BrandName = merchant.BrandName,
BrandAlias = merchant.BrandAlias,
LogoUrl = merchant.LogoUrl,
Category = merchant.Category,
ContactPhone = merchant.ContactPhone,
ContactEmail = merchant.ContactEmail,
Status = merchant.Status,
JoinedAt = merchant.JoinedAt
};
}

View File

@@ -0,0 +1,42 @@
using MediatR;
using TakeoutSaaS.Application.App.Merchants.Dto;
using TakeoutSaaS.Application.App.Merchants.Queries;
using TakeoutSaaS.Domain.Merchants.Repositories;
using TakeoutSaaS.Shared.Abstractions.Tenancy;
namespace TakeoutSaaS.Application.App.Merchants.Handlers;
/// <summary>
/// 获取商户详情查询处理器。
/// </summary>
public sealed class GetMerchantByIdQueryHandler(IMerchantRepository merchantRepository, ITenantProvider tenantProvider)
: IRequestHandler<GetMerchantByIdQuery, MerchantDto?>
{
private readonly IMerchantRepository _merchantRepository = merchantRepository;
private readonly ITenantProvider _tenantProvider = tenantProvider;
/// <inheritdoc />
public async Task<MerchantDto?> Handle(GetMerchantByIdQuery request, CancellationToken cancellationToken)
{
var tenantId = _tenantProvider.GetCurrentTenantId();
var merchant = await _merchantRepository.FindByIdAsync(request.MerchantId, tenantId, cancellationToken);
if (merchant == null)
{
return null;
}
return new MerchantDto
{
Id = merchant.Id,
TenantId = merchant.TenantId,
BrandName = merchant.BrandName,
BrandAlias = merchant.BrandAlias,
LogoUrl = merchant.LogoUrl,
Category = merchant.Category,
ContactPhone = merchant.ContactPhone,
ContactEmail = merchant.ContactEmail,
Status = merchant.Status,
JoinedAt = merchant.JoinedAt
};
}
}

View File

@@ -0,0 +1,42 @@
using MediatR;
using TakeoutSaaS.Application.App.Merchants.Dto;
using TakeoutSaaS.Application.App.Merchants.Queries;
using TakeoutSaaS.Domain.Merchants.Repositories;
using TakeoutSaaS.Shared.Abstractions.Tenancy;
namespace TakeoutSaaS.Application.App.Merchants.Handlers;
/// <summary>
/// 商户列表查询处理器。
/// </summary>
public sealed class SearchMerchantsQueryHandler(
IMerchantRepository merchantRepository,
ITenantProvider tenantProvider)
: IRequestHandler<SearchMerchantsQuery, IReadOnlyList<MerchantDto>>
{
private readonly IMerchantRepository _merchantRepository = merchantRepository;
private readonly ITenantProvider _tenantProvider = tenantProvider;
/// <inheritdoc />
public async Task<IReadOnlyList<MerchantDto>> Handle(SearchMerchantsQuery request, CancellationToken cancellationToken)
{
var tenantId = _tenantProvider.GetCurrentTenantId();
var merchants = await _merchantRepository.SearchAsync(tenantId, request.Status, cancellationToken);
return merchants
.Select(merchant => new MerchantDto
{
Id = merchant.Id,
TenantId = merchant.TenantId,
BrandName = merchant.BrandName,
BrandAlias = merchant.BrandAlias,
LogoUrl = merchant.LogoUrl,
Category = merchant.Category,
ContactPhone = merchant.ContactPhone,
ContactEmail = merchant.ContactEmail,
Status = merchant.Status,
JoinedAt = merchant.JoinedAt
})
.ToList();
}
}

View File

@@ -0,0 +1,15 @@
using MediatR;
using TakeoutSaaS.Application.App.Merchants.Dto;
namespace TakeoutSaaS.Application.App.Merchants.Queries;
/// <summary>
/// 按 ID 获取商户。
/// </summary>
public sealed class GetMerchantByIdQuery : IRequest<MerchantDto?>
{
/// <summary>
/// 商户 ID。
/// </summary>
public long MerchantId { get; init; }
}

View File

@@ -0,0 +1,16 @@
using MediatR;
using TakeoutSaaS.Application.App.Merchants.Dto;
using TakeoutSaaS.Domain.Merchants.Enums;
namespace TakeoutSaaS.Application.App.Merchants.Queries;
/// <summary>
/// 搜索商户列表。
/// </summary>
public sealed class SearchMerchantsQuery : IRequest<IReadOnlyList<MerchantDto>>
{
/// <summary>
/// 按状态过滤。
/// </summary>
public MerchantStatus? Status { get; init; }
}

View File

@@ -10,17 +10,17 @@ public interface IDictionaryAppService
{
Task<DictionaryGroupDto> CreateGroupAsync(CreateDictionaryGroupRequest request, CancellationToken cancellationToken = default);
Task<DictionaryGroupDto> UpdateGroupAsync(Guid groupId, UpdateDictionaryGroupRequest request, CancellationToken cancellationToken = default);
Task<DictionaryGroupDto> UpdateGroupAsync(long groupId, UpdateDictionaryGroupRequest request, CancellationToken cancellationToken = default);
Task DeleteGroupAsync(Guid groupId, CancellationToken cancellationToken = default);
Task DeleteGroupAsync(long groupId, CancellationToken cancellationToken = default);
Task<IReadOnlyList<DictionaryGroupDto>> SearchGroupsAsync(DictionaryGroupQuery request, CancellationToken cancellationToken = default);
Task<DictionaryItemDto> CreateItemAsync(CreateDictionaryItemRequest request, CancellationToken cancellationToken = default);
Task<DictionaryItemDto> UpdateItemAsync(Guid itemId, UpdateDictionaryItemRequest request, CancellationToken cancellationToken = default);
Task<DictionaryItemDto> UpdateItemAsync(long itemId, UpdateDictionaryItemRequest request, CancellationToken cancellationToken = default);
Task DeleteItemAsync(Guid itemId, CancellationToken cancellationToken = default);
Task DeleteItemAsync(long itemId, CancellationToken cancellationToken = default);
Task<IReadOnlyDictionary<string, IReadOnlyList<DictionaryItemDto>>> GetCachedItemsAsync(DictionaryBatchQueryRequest request, CancellationToken cancellationToken = default);
}

View File

@@ -13,15 +13,15 @@ public interface IDictionaryCache
/// <summary>
/// 获取缓存。
/// </summary>
Task<IReadOnlyList<DictionaryItemDto>?> GetAsync(Guid tenantId, string code, CancellationToken cancellationToken = default);
Task<IReadOnlyList<DictionaryItemDto>?> GetAsync(long tenantId, string code, CancellationToken cancellationToken = default);
/// <summary>
/// 写入缓存。
/// </summary>
Task SetAsync(Guid tenantId, string code, IReadOnlyList<DictionaryItemDto> items, CancellationToken cancellationToken = default);
Task SetAsync(long tenantId, string code, IReadOnlyList<DictionaryItemDto> items, CancellationToken cancellationToken = default);
/// <summary>
/// 移除缓存。
/// </summary>
Task RemoveAsync(Guid tenantId, string code, CancellationToken cancellationToken = default);
Task RemoveAsync(long tenantId, string code, CancellationToken cancellationToken = default);
}

View File

@@ -1,3 +1,5 @@
using System.Text.Json.Serialization;
using TakeoutSaaS.Shared.Abstractions.Serialization;
using System.ComponentModel.DataAnnotations;
namespace TakeoutSaaS.Application.Dictionary.Contracts;
@@ -11,7 +13,8 @@ public sealed class CreateDictionaryItemRequest
/// 所属分组 ID。
/// </summary>
[Required]
public Guid GroupId { get; set; }
[JsonConverter(typeof(SnowflakeIdJsonConverter))]
public long GroupId { get; set; }
/// <summary>
/// 字典项键。

View File

@@ -1,5 +1,8 @@
using TakeoutSaaS.Domain.Dictionary.Enums;
using System.Text.Json.Serialization;
using TakeoutSaaS.Shared.Abstractions.Serialization;
namespace TakeoutSaaS.Application.Dictionary.Models;
/// <summary>
@@ -7,7 +10,8 @@ namespace TakeoutSaaS.Application.Dictionary.Models;
/// </summary>
public sealed class DictionaryGroupDto
{
public Guid Id { get; init; }
[JsonConverter(typeof(SnowflakeIdJsonConverter))]
public long Id { get; init; }
public string Code { get; init; } = string.Empty;

View File

@@ -1,3 +1,6 @@
using System.Text.Json.Serialization;
using TakeoutSaaS.Shared.Abstractions.Serialization;
namespace TakeoutSaaS.Application.Dictionary.Models;
/// <summary>
@@ -5,9 +8,11 @@ namespace TakeoutSaaS.Application.Dictionary.Models;
/// </summary>
public sealed class DictionaryItemDto
{
public Guid Id { get; init; }
[JsonConverter(typeof(SnowflakeIdJsonConverter))]
public long Id { get; init; }
public Guid GroupId { get; init; }
[JsonConverter(typeof(SnowflakeIdJsonConverter))]
public long GroupId { get; init; }
public string Key { get; init; } = string.Empty;

View File

@@ -47,7 +47,7 @@ public sealed class DictionaryAppService : IDictionaryAppService
var group = new DictionaryGroup
{
Id = Guid.NewGuid(),
Id = 0,
TenantId = targetTenant,
Code = normalizedCode,
Name = request.Name.Trim(),
@@ -62,7 +62,7 @@ public sealed class DictionaryAppService : IDictionaryAppService
return MapGroup(group, includeItems: false);
}
public async Task<DictionaryGroupDto> UpdateGroupAsync(Guid groupId, UpdateDictionaryGroupRequest request, CancellationToken cancellationToken = default)
public async Task<DictionaryGroupDto> UpdateGroupAsync(long groupId, UpdateDictionaryGroupRequest request, CancellationToken cancellationToken = default)
{
var group = await RequireGroupAsync(groupId, cancellationToken);
EnsureScopePermission(group.Scope);
@@ -77,7 +77,7 @@ public sealed class DictionaryAppService : IDictionaryAppService
return MapGroup(group, includeItems: false);
}
public async Task DeleteGroupAsync(Guid groupId, CancellationToken cancellationToken = default)
public async Task DeleteGroupAsync(long groupId, CancellationToken cancellationToken = default)
{
var group = await RequireGroupAsync(groupId, cancellationToken);
EnsureScopePermission(group.Scope);
@@ -120,7 +120,7 @@ public sealed class DictionaryAppService : IDictionaryAppService
var item = new DictionaryItem
{
Id = Guid.NewGuid(),
Id = 0,
TenantId = group.TenantId,
GroupId = group.Id,
Key = request.Key.Trim(),
@@ -138,7 +138,7 @@ public sealed class DictionaryAppService : IDictionaryAppService
return MapItem(item);
}
public async Task<DictionaryItemDto> UpdateItemAsync(Guid itemId, UpdateDictionaryItemRequest request, CancellationToken cancellationToken = default)
public async Task<DictionaryItemDto> UpdateItemAsync(long itemId, UpdateDictionaryItemRequest request, CancellationToken cancellationToken = default)
{
var item = await RequireItemAsync(itemId, cancellationToken);
var group = await RequireGroupAsync(item.GroupId, cancellationToken);
@@ -156,7 +156,7 @@ public sealed class DictionaryAppService : IDictionaryAppService
return MapItem(item);
}
public async Task DeleteItemAsync(Guid itemId, CancellationToken cancellationToken = default)
public async Task DeleteItemAsync(long itemId, CancellationToken cancellationToken = default)
{
var item = await RequireItemAsync(itemId, cancellationToken);
var group = await RequireGroupAsync(item.GroupId, cancellationToken);
@@ -186,8 +186,8 @@ public sealed class DictionaryAppService : IDictionaryAppService
foreach (var code in normalizedCodes)
{
var systemItems = await GetOrLoadCacheAsync(Guid.Empty, code, cancellationToken);
if (tenantId == Guid.Empty)
var systemItems = await GetOrLoadCacheAsync(0, code, cancellationToken);
if (tenantId == 0)
{
result[code] = systemItems;
continue;
@@ -200,7 +200,7 @@ public sealed class DictionaryAppService : IDictionaryAppService
return result;
}
private async Task<DictionaryGroup> RequireGroupAsync(Guid groupId, CancellationToken cancellationToken)
private async Task<DictionaryGroup> RequireGroupAsync(long groupId, CancellationToken cancellationToken)
{
var group = await _repository.FindGroupByIdAsync(groupId, cancellationToken);
if (group == null)
@@ -211,7 +211,7 @@ public sealed class DictionaryAppService : IDictionaryAppService
return group;
}
private async Task<DictionaryItem> RequireItemAsync(Guid itemId, CancellationToken cancellationToken)
private async Task<DictionaryItem> RequireItemAsync(long itemId, CancellationToken cancellationToken)
{
var item = await _repository.FindItemByIdAsync(itemId, cancellationToken);
if (item == null)
@@ -222,16 +222,16 @@ public sealed class DictionaryAppService : IDictionaryAppService
return item;
}
private Guid ResolveTargetTenant(DictionaryScope scope)
private long ResolveTargetTenant(DictionaryScope scope)
{
var tenantId = _tenantProvider.GetCurrentTenantId();
if (scope == DictionaryScope.System)
{
EnsurePlatformTenant(tenantId);
return Guid.Empty;
return 0;
}
if (tenantId == Guid.Empty)
if (tenantId == 0)
{
throw new BusinessException(ErrorCodes.BadRequest, "业务参数需指定租户");
}
@@ -241,28 +241,28 @@ public sealed class DictionaryAppService : IDictionaryAppService
private static string NormalizeCode(string code) => code.Trim().ToLowerInvariant();
private static DictionaryScope ResolveScopeForQuery(DictionaryScope? requestedScope, Guid tenantId)
private static DictionaryScope ResolveScopeForQuery(DictionaryScope? requestedScope, long tenantId)
{
if (requestedScope.HasValue)
{
return requestedScope.Value;
}
return tenantId == Guid.Empty ? DictionaryScope.System : DictionaryScope.Business;
return tenantId == 0 ? DictionaryScope.System : DictionaryScope.Business;
}
private void EnsureScopePermission(DictionaryScope scope)
{
var tenantId = _tenantProvider.GetCurrentTenantId();
if (scope == DictionaryScope.System && tenantId != Guid.Empty)
if (scope == DictionaryScope.System && tenantId != 0)
{
throw new BusinessException(ErrorCodes.Forbidden, "仅平台管理员可操作系统字典");
}
}
private void EnsurePlatformTenant(Guid tenantId)
private void EnsurePlatformTenant(long tenantId)
{
if (tenantId != Guid.Empty)
if (tenantId != 0)
{
throw new BusinessException(ErrorCodes.Forbidden, "仅平台管理员可操作系统字典");
}
@@ -279,7 +279,7 @@ public sealed class DictionaryAppService : IDictionaryAppService
// 系统参数更新需要逐租户重新合并,由调用方在下一次请求时重新加载
}
private async Task<IReadOnlyList<DictionaryItemDto>> GetOrLoadCacheAsync(Guid tenantId, string code, CancellationToken cancellationToken)
private async Task<IReadOnlyList<DictionaryItemDto>> GetOrLoadCacheAsync(long tenantId, string code, CancellationToken cancellationToken)
{
var cached = await _cache.GetAsync(tenantId, code, cancellationToken);
if (cached != null)

View File

@@ -12,5 +12,5 @@ public interface IAdminAuthService
{
Task<TokenResponse> LoginAsync(AdminLoginRequest request, CancellationToken cancellationToken = default);
Task<TokenResponse> RefreshTokenAsync(RefreshTokenRequest request, CancellationToken cancellationToken = default);
Task<CurrentUserProfile> GetProfileAsync(Guid userId, CancellationToken cancellationToken = default);
Task<CurrentUserProfile> GetProfileAsync(long userId, CancellationToken cancellationToken = default);
}

View File

@@ -12,5 +12,5 @@ public interface IMiniAuthService
{
Task<TokenResponse> LoginWithWeChatAsync(WeChatLoginRequest request, CancellationToken cancellationToken = default);
Task<TokenResponse> RefreshTokenAsync(RefreshTokenRequest request, CancellationToken cancellationToken = default);
Task<CurrentUserProfile> GetProfileAsync(Guid userId, CancellationToken cancellationToken = default);
Task<CurrentUserProfile> GetProfileAsync(long userId, CancellationToken cancellationToken = default);
}

View File

@@ -10,7 +10,7 @@ namespace TakeoutSaaS.Application.Identity.Abstractions;
/// </summary>
public interface IRefreshTokenStore
{
Task<RefreshTokenDescriptor> IssueAsync(Guid userId, DateTime expiresAt, CancellationToken cancellationToken = default);
Task<RefreshTokenDescriptor> IssueAsync(long userId, DateTime expiresAt, CancellationToken cancellationToken = default);
Task<RefreshTokenDescriptor?> GetAsync(string refreshToken, CancellationToken cancellationToken = default);
Task RevokeAsync(string refreshToken, CancellationToken cancellationToken = default);
}

View File

@@ -8,7 +8,7 @@ public sealed class CurrentUserProfile
/// <summary>
/// 用户 ID。
/// </summary>
public Guid UserId { get; init; }
public long UserId { get; init; }
/// <summary>
/// 登录账号。
@@ -23,12 +23,12 @@ public sealed class CurrentUserProfile
/// <summary>
/// 所属租户 ID。
/// </summary>
public Guid TenantId { get; init; }
public long TenantId { get; init; }
/// <summary>
/// 所属商户 ID平台管理员为空
/// </summary>
public Guid? MerchantId { get; init; }
public long? MerchantId { get; init; }
/// <summary>
/// 角色集合。

View File

@@ -9,6 +9,6 @@ namespace TakeoutSaaS.Application.Identity.Models;
/// <param name="Revoked">是否已撤销</param>
public sealed record RefreshTokenDescriptor(
string Token,
Guid UserId,
long UserId,
DateTime ExpiresAt,
bool Revoked);

View File

@@ -77,7 +77,7 @@ public sealed class AdminAuthService(
/// <param name="cancellationToken">取消令牌</param>
/// <returns>用户档案</returns>
/// <exception cref="BusinessException">用户不存在时抛出</exception>
public async Task<CurrentUserProfile> GetProfileAsync(Guid userId, CancellationToken cancellationToken = default)
public async Task<CurrentUserProfile> GetProfileAsync(long userId, CancellationToken cancellationToken = default)
{
var user = await userRepository.FindByIdAsync(userId, cancellationToken)
?? throw new BusinessException(ErrorCodes.NotFound, "用户不存在");

View File

@@ -44,7 +44,7 @@ public sealed class MiniAuthService(
// 3. 获取当前租户 ID多租户支持
var tenantId = tenantProvider.GetCurrentTenantId();
if (tenantId == Guid.Empty)
if (tenantId == 0)
{
throw new BusinessException(ErrorCodes.BadRequest, "缺少租户标识");
}
@@ -95,7 +95,7 @@ public sealed class MiniAuthService(
/// <param name="cancellationToken">取消令牌</param>
/// <returns>用户档案</returns>
/// <exception cref="BusinessException">用户不存在时抛出</exception>
public async Task<CurrentUserProfile> GetProfileAsync(Guid userId, CancellationToken cancellationToken = default)
public async Task<CurrentUserProfile> GetProfileAsync(long userId, CancellationToken cancellationToken = default)
{
var user = await miniUserRepository.FindByIdAsync(userId, cancellationToken)
?? throw new BusinessException(ErrorCodes.NotFound, "用户不存在");
@@ -113,7 +113,7 @@ public sealed class MiniAuthService(
/// <param name="tenantId">租户 ID</param>
/// <param name="cancellationToken">取消令牌</param>
/// <returns>用户实体和是否为新用户的元组</returns>
private async Task<(MiniUser user, bool isNew)> GetOrBindMiniUserAsync(string openId, string? unionId, string? nickname, string? avatar, Guid tenantId, CancellationToken cancellationToken)
private async Task<(MiniUser user, bool isNew)> GetOrBindMiniUserAsync(string openId, string? unionId, string? nickname, string? avatar, long tenantId, CancellationToken cancellationToken)
{
// 检查用户是否已存在
var existing = await miniUserRepository.FindByOpenIdAsync(openId, cancellationToken);

View File

@@ -8,7 +8,7 @@ public sealed class OrderCreatedEvent
/// <summary>
/// 订单标识。
/// </summary>
public Guid OrderId { get; init; }
public long OrderId { get; init; }
/// <summary>
/// 订单编号。
@@ -23,7 +23,7 @@ public sealed class OrderCreatedEvent
/// <summary>
/// 所属租户。
/// </summary>
public Guid TenantId { get; init; }
public long TenantId { get; init; }
/// <summary>
/// 创建时间UTC

View File

@@ -8,7 +8,7 @@ public sealed class PaymentSucceededEvent
/// <summary>
/// 订单标识。
/// </summary>
public Guid OrderId { get; init; }
public long OrderId { get; init; }
/// <summary>
/// 支付流水号。
@@ -23,7 +23,7 @@ public sealed class PaymentSucceededEvent
/// <summary>
/// 所属租户。
/// </summary>
public Guid TenantId { get; init; }
public long TenantId { get; init; }
/// <summary>
/// 支付时间UTC

View File

@@ -44,7 +44,7 @@ public sealed class VerificationCodeService(
var codeOptions = codeOptionsMonitor.CurrentValue;
var templateCode = ResolveTemplate(request.Scene, smsOptions);
var phone = NormalizePhoneNumber(request.PhoneNumber);
var tenantKey = tenantProvider.GetCurrentTenantId() == Guid.Empty ? "platform" : tenantProvider.GetCurrentTenantId().ToString("N");
var tenantKey = tenantProvider.GetCurrentTenantId() == 0 ? "platform" : tenantProvider.GetCurrentTenantId().ToString();
var cacheKey = $"{codeOptions.CachePrefix}:{tenantKey}:{request.Scene}:{phone}";
var cooldownKey = $"{cacheKey}:cooldown";
@@ -90,7 +90,7 @@ public sealed class VerificationCodeService(
var codeOptions = codeOptionsMonitor.CurrentValue;
var phone = NormalizePhoneNumber(request.PhoneNumber);
var tenantKey = tenantProvider.GetCurrentTenantId() == Guid.Empty ? "platform" : tenantProvider.GetCurrentTenantId().ToString("N");
var tenantKey = tenantProvider.GetCurrentTenantId() == 0 ? "platform" : tenantProvider.GetCurrentTenantId().ToString();
var cacheKey = $"{codeOptions.CachePrefix}:{tenantKey}:{request.Scene}:{phone}";
var cachedCode = await cache.GetStringAsync(cacheKey, cancellationToken).ConfigureAwait(false);

View File

@@ -212,7 +212,7 @@ public sealed class FileStorageService(
private string BuildObjectKey(UploadFileType type, string extension)
{
var tenantId = tenantProvider.GetCurrentTenantId();
var tenantSegment = tenantId == Guid.Empty ? "platform" : tenantId.ToString("N");
var tenantSegment = tenantId == 0 ? "platform" : tenantId.ToString();
var folder = type.ToFolderName();
var now = DateTime.UtcNow;
var fileName = $"{Guid.NewGuid():N}{extension}";

View File

@@ -8,6 +8,7 @@
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.3.0" />
<PackageReference Include="Microsoft.Extensions.Identity.Core" Version="8.0.1" />
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="8.0.0" />
<PackageReference Include="MediatR.Extensions.Microsoft.DependencyInjection" Version="11.1.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Modules\TakeoutSaaS.Module.Sms\TakeoutSaaS.Module.Sms.csproj" />