feat: migrate snowflake ids and refresh migrations
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
/// 字典项键。
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
/// 角色集合。
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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, "用户不存在");
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)。
|
||||
|
||||
@@ -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)。
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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}";
|
||||
|
||||
@@ -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" />
|
||||
|
||||
Reference in New Issue
Block a user