feat: migrate snowflake ids and refresh migrations
This commit is contained in:
@@ -67,7 +67,7 @@ public sealed class AuthController : BaseApiController
|
||||
public async Task<ApiResponse<CurrentUserProfile>> GetProfile(CancellationToken cancellationToken)
|
||||
{
|
||||
var userId = User.GetUserId();
|
||||
if (userId == Guid.Empty)
|
||||
if (userId == 0)
|
||||
{
|
||||
return ApiResponse<CurrentUserProfile>.Error(ErrorCodes.Unauthorized, "Token 缺少有效的用户标识");
|
||||
}
|
||||
|
||||
@@ -55,10 +55,10 @@ public sealed class DictionaryController : BaseApiController
|
||||
/// <summary>
|
||||
/// 更新字典分组。
|
||||
/// </summary>
|
||||
[HttpPut("{groupId:guid}")]
|
||||
[HttpPut("{groupId:long}")]
|
||||
[PermissionAuthorize("dictionary:group:update")]
|
||||
[ProducesResponseType(typeof(ApiResponse<DictionaryGroupDto>), StatusCodes.Status200OK)]
|
||||
public async Task<ApiResponse<DictionaryGroupDto>> UpdateGroup(Guid groupId, [FromBody] UpdateDictionaryGroupRequest request, CancellationToken cancellationToken)
|
||||
public async Task<ApiResponse<DictionaryGroupDto>> UpdateGroup(long groupId, [FromBody] UpdateDictionaryGroupRequest request, CancellationToken cancellationToken)
|
||||
{
|
||||
var group = await _dictionaryAppService.UpdateGroupAsync(groupId, request, cancellationToken);
|
||||
return ApiResponse<DictionaryGroupDto>.Ok(group);
|
||||
@@ -67,10 +67,10 @@ public sealed class DictionaryController : BaseApiController
|
||||
/// <summary>
|
||||
/// 删除字典分组。
|
||||
/// </summary>
|
||||
[HttpDelete("{groupId:guid}")]
|
||||
[HttpDelete("{groupId:long}")]
|
||||
[PermissionAuthorize("dictionary:group:delete")]
|
||||
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status200OK)]
|
||||
public async Task<ApiResponse<object>> DeleteGroup(Guid groupId, CancellationToken cancellationToken)
|
||||
public async Task<ApiResponse<object>> DeleteGroup(long groupId, CancellationToken cancellationToken)
|
||||
{
|
||||
await _dictionaryAppService.DeleteGroupAsync(groupId, cancellationToken);
|
||||
return ApiResponse.Success();
|
||||
@@ -79,10 +79,10 @@ public sealed class DictionaryController : BaseApiController
|
||||
/// <summary>
|
||||
/// 创建字典项。
|
||||
/// </summary>
|
||||
[HttpPost("{groupId:guid}/items")]
|
||||
[HttpPost("{groupId:long}/items")]
|
||||
[PermissionAuthorize("dictionary:item:create")]
|
||||
[ProducesResponseType(typeof(ApiResponse<DictionaryItemDto>), StatusCodes.Status200OK)]
|
||||
public async Task<ApiResponse<DictionaryItemDto>> CreateItem(Guid groupId, [FromBody] CreateDictionaryItemRequest request, CancellationToken cancellationToken)
|
||||
public async Task<ApiResponse<DictionaryItemDto>> CreateItem(long groupId, [FromBody] CreateDictionaryItemRequest request, CancellationToken cancellationToken)
|
||||
{
|
||||
request.GroupId = groupId;
|
||||
var item = await _dictionaryAppService.CreateItemAsync(request, cancellationToken);
|
||||
@@ -92,10 +92,10 @@ public sealed class DictionaryController : BaseApiController
|
||||
/// <summary>
|
||||
/// 更新字典项。
|
||||
/// </summary>
|
||||
[HttpPut("items/{itemId:guid}")]
|
||||
[HttpPut("items/{itemId:long}")]
|
||||
[PermissionAuthorize("dictionary:item:update")]
|
||||
[ProducesResponseType(typeof(ApiResponse<DictionaryItemDto>), StatusCodes.Status200OK)]
|
||||
public async Task<ApiResponse<DictionaryItemDto>> UpdateItem(Guid itemId, [FromBody] UpdateDictionaryItemRequest request, CancellationToken cancellationToken)
|
||||
public async Task<ApiResponse<DictionaryItemDto>> UpdateItem(long itemId, [FromBody] UpdateDictionaryItemRequest request, CancellationToken cancellationToken)
|
||||
{
|
||||
var item = await _dictionaryAppService.UpdateItemAsync(itemId, request, cancellationToken);
|
||||
return ApiResponse<DictionaryItemDto>.Ok(item);
|
||||
@@ -104,10 +104,10 @@ public sealed class DictionaryController : BaseApiController
|
||||
/// <summary>
|
||||
/// 删除字典项。
|
||||
/// </summary>
|
||||
[HttpDelete("items/{itemId:guid}")]
|
||||
[HttpDelete("items/{itemId:long}")]
|
||||
[PermissionAuthorize("dictionary:item:delete")]
|
||||
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status200OK)]
|
||||
public async Task<ApiResponse<object>> DeleteItem(Guid itemId, CancellationToken cancellationToken)
|
||||
public async Task<ApiResponse<object>> DeleteItem(long itemId, CancellationToken cancellationToken)
|
||||
{
|
||||
await _dictionaryAppService.DeleteItemAsync(itemId, cancellationToken);
|
||||
return ApiResponse.Success();
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
using MediatR;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using TakeoutSaaS.Application.App.Merchants.Commands;
|
||||
using TakeoutSaaS.Application.App.Merchants.Dto;
|
||||
using TakeoutSaaS.Application.App.Merchants.Queries;
|
||||
using TakeoutSaaS.Domain.Merchants.Enums;
|
||||
using TakeoutSaaS.Module.Authorization.Attributes;
|
||||
using TakeoutSaaS.Shared.Abstractions.Constants;
|
||||
using TakeoutSaaS.Shared.Abstractions.Results;
|
||||
using TakeoutSaaS.Shared.Web.Api;
|
||||
|
||||
namespace TakeoutSaaS.AdminApi.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// 商户管理。
|
||||
/// </summary>
|
||||
[ApiVersion("1.0")]
|
||||
[Authorize]
|
||||
[Route("api/admin/v{version:apiVersion}/merchants")]
|
||||
public sealed class MerchantsController : BaseApiController
|
||||
{
|
||||
private readonly IMediator _mediator;
|
||||
|
||||
/// <summary>
|
||||
/// 初始化控制器。
|
||||
/// </summary>
|
||||
public MerchantsController(IMediator mediator)
|
||||
{
|
||||
_mediator = mediator;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建商户。
|
||||
/// </summary>
|
||||
[HttpPost]
|
||||
[PermissionAuthorize("merchant:create")]
|
||||
[ProducesResponseType(typeof(ApiResponse<MerchantDto>), StatusCodes.Status200OK)]
|
||||
public async Task<ApiResponse<MerchantDto>> Create([FromBody] CreateMerchantCommand command, CancellationToken cancellationToken)
|
||||
{
|
||||
var result = await _mediator.Send(command, cancellationToken);
|
||||
return ApiResponse<MerchantDto>.Ok(result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查询商户列表。
|
||||
/// </summary>
|
||||
[HttpGet]
|
||||
[PermissionAuthorize("merchant:read")]
|
||||
[ProducesResponseType(typeof(ApiResponse<IReadOnlyList<MerchantDto>>), StatusCodes.Status200OK)]
|
||||
public async Task<ApiResponse<IReadOnlyList<MerchantDto>>> List([FromQuery] MerchantStatus? status, CancellationToken cancellationToken)
|
||||
{
|
||||
var result = await _mediator.Send(new SearchMerchantsQuery { Status = status }, cancellationToken);
|
||||
return ApiResponse<IReadOnlyList<MerchantDto>>.Ok(result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取商户详情。
|
||||
/// </summary>
|
||||
[HttpGet("{merchantId:long}")]
|
||||
[PermissionAuthorize("merchant:read")]
|
||||
[ProducesResponseType(typeof(ApiResponse<MerchantDto>), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status404NotFound)]
|
||||
public async Task<ApiResponse<MerchantDto>> Detail(long merchantId, CancellationToken cancellationToken)
|
||||
{
|
||||
var result = await _mediator.Send(new GetMerchantByIdQuery { MerchantId = merchantId }, cancellationToken);
|
||||
return result == null
|
||||
? ApiResponse<MerchantDto>.Error(ErrorCodes.NotFound, "商户不存在")
|
||||
: ApiResponse<MerchantDto>.Ok(result);
|
||||
}
|
||||
}
|
||||
@@ -6,10 +6,12 @@ using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Serilog;
|
||||
using TakeoutSaaS.Application.App.Extensions;
|
||||
using TakeoutSaaS.Application.Identity.Extensions;
|
||||
using TakeoutSaaS.Application.Messaging.Extensions;
|
||||
using TakeoutSaaS.Application.Sms.Extensions;
|
||||
using TakeoutSaaS.Application.Storage.Extensions;
|
||||
using TakeoutSaaS.Infrastructure.App.Extensions;
|
||||
using TakeoutSaaS.Infrastructure.Identity.Extensions;
|
||||
using TakeoutSaaS.Module.Authorization.Extensions;
|
||||
using TakeoutSaaS.Module.Dictionary.Extensions;
|
||||
@@ -40,6 +42,8 @@ builder.Services.AddSharedSwagger(options =>
|
||||
});
|
||||
builder.Services.AddIdentityApplication();
|
||||
builder.Services.AddIdentityInfrastructure(builder.Configuration, enableAdminSeed: true);
|
||||
builder.Services.AddAppInfrastructure(builder.Configuration);
|
||||
builder.Services.AddAppApplication();
|
||||
builder.Services.AddJwtAuthentication(builder.Configuration);
|
||||
builder.Services.AddAuthorization();
|
||||
builder.Services.AddPermissionAuthorization();
|
||||
|
||||
@@ -8,6 +8,10 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2" />
|
||||
<PackageReference Include="Serilog.AspNetCore" Version="8.0.1" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Core\TakeoutSaaS.Shared.Web\TakeoutSaaS.Shared.Web.csproj" />
|
||||
|
||||
@@ -149,5 +149,39 @@
|
||||
"WorkerCount": 5,
|
||||
"DashboardEnabled": false,
|
||||
"DashboardPath": "/hangfire"
|
||||
},
|
||||
"App": {
|
||||
"Seed": {
|
||||
"Enabled": true,
|
||||
"DefaultTenant": {
|
||||
"TenantId": 1000000000001,
|
||||
"Code": "demo",
|
||||
"Name": "Demo租户",
|
||||
"ShortName": "Demo",
|
||||
"ContactName": "DemoAdmin",
|
||||
"ContactPhone": "13800000000"
|
||||
},
|
||||
"DictionaryGroups": [
|
||||
{
|
||||
"Code": "order_status",
|
||||
"Name": "订单状态",
|
||||
"Scope": "Business",
|
||||
"Items": [
|
||||
{ "Key": "pending", "Value": "待支付", "SortOrder": 10 },
|
||||
{ "Key": "paid", "Value": "已支付", "SortOrder": 20 },
|
||||
{ "Key": "finished", "Value": "已完成", "SortOrder": 30 }
|
||||
]
|
||||
},
|
||||
{
|
||||
"Code": "store_tags",
|
||||
"Name": "门店标签",
|
||||
"Scope": "Business",
|
||||
"Items": [
|
||||
{ "Key": "hot", "Value": "热门", "SortOrder": 10 },
|
||||
{ "Key": "new", "Value": "新店", "SortOrder": 20 }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ public sealed class MeController : BaseApiController
|
||||
public async Task<ApiResponse<CurrentUserProfile>> Get(CancellationToken cancellationToken)
|
||||
{
|
||||
var userId = User.GetUserId();
|
||||
if (userId == Guid.Empty)
|
||||
if (userId == 0)
|
||||
{
|
||||
return ApiResponse<CurrentUserProfile>.Error(ErrorCodes.Unauthorized, "Token 缺少有效的用户标识");
|
||||
}
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -23,15 +23,15 @@ public abstract class AuditableEntityBase : EntityBase, IAuditableEntity
|
||||
/// <summary>
|
||||
/// 创建人用户标识,匿名或系统操作时为 null。
|
||||
/// </summary>
|
||||
public Guid? CreatedBy { get; set; }
|
||||
public long? CreatedBy { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 最后更新人用户标识,匿名或系统操作时为 null。
|
||||
/// </summary>
|
||||
public Guid? UpdatedBy { get; set; }
|
||||
public long? UpdatedBy { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 删除人用户标识(软删除),未删除时为 null。
|
||||
/// </summary>
|
||||
public Guid? DeletedBy { get; set; }
|
||||
public long? DeletedBy { get; set; }
|
||||
}
|
||||
|
||||
@@ -8,5 +8,5 @@ public abstract class EntityBase
|
||||
/// <summary>
|
||||
/// 实体唯一标识。
|
||||
/// </summary>
|
||||
public Guid Id { get; set; }
|
||||
public long Id { get; set; }
|
||||
}
|
||||
|
||||
@@ -23,15 +23,15 @@ public interface IAuditableEntity : ISoftDeleteEntity
|
||||
/// <summary>
|
||||
/// 创建人用户标识,匿名或系统操作时为 null。
|
||||
/// </summary>
|
||||
Guid? CreatedBy { get; set; }
|
||||
long? CreatedBy { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 最后更新人用户标识,匿名或系统操作时为 null。
|
||||
/// </summary>
|
||||
Guid? UpdatedBy { get; set; }
|
||||
long? UpdatedBy { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 删除人用户标识(软删除),未删除时为 null。
|
||||
/// </summary>
|
||||
Guid? DeletedBy { get; set; }
|
||||
long? DeletedBy { get; set; }
|
||||
}
|
||||
|
||||
@@ -8,5 +8,5 @@ public interface IMultiTenantEntity
|
||||
/// <summary>
|
||||
/// 所属租户 ID。
|
||||
/// </summary>
|
||||
Guid TenantId { get; set; }
|
||||
long TenantId { get; set; }
|
||||
}
|
||||
|
||||
@@ -8,5 +8,5 @@ public abstract class MultiTenantEntityBase : AuditableEntityBase, IMultiTenantE
|
||||
/// <summary>
|
||||
/// 所属租户 ID。
|
||||
/// </summary>
|
||||
public Guid TenantId { get; set; }
|
||||
public long TenantId { get; set; }
|
||||
}
|
||||
|
||||
13
src/Core/TakeoutSaaS.Shared.Abstractions/Ids/IIdGenerator.cs
Normal file
13
src/Core/TakeoutSaaS.Shared.Abstractions/Ids/IIdGenerator.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
namespace TakeoutSaaS.Shared.Abstractions.Ids;
|
||||
|
||||
/// <summary>
|
||||
/// 雪花 ID 生成器接口。
|
||||
/// </summary>
|
||||
public interface IIdGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// 生成下一个唯一长整型 ID。
|
||||
/// </summary>
|
||||
/// <returns>雪花 ID。</returns>
|
||||
long NextId();
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace TakeoutSaaS.Shared.Abstractions.Ids;
|
||||
|
||||
/// <summary>
|
||||
/// 雪花 ID 生成器配置。
|
||||
/// </summary>
|
||||
public sealed class IdGeneratorOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// 配置节名称。
|
||||
/// </summary>
|
||||
public const string SectionName = "IdGenerator";
|
||||
|
||||
/// <summary>
|
||||
/// 工作节点标识,0-31。
|
||||
/// </summary>
|
||||
[Range(0, 31)]
|
||||
public int WorkerId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 机房标识,0-31。
|
||||
/// </summary>
|
||||
[Range(0, 31)]
|
||||
public int DatacenterId { get; set; }
|
||||
}
|
||||
@@ -99,6 +99,65 @@ public sealed record ApiResponse<T>
|
||||
return TraceContext.TraceId;
|
||||
}
|
||||
|
||||
return Activity.Current?.Id ?? Guid.NewGuid().ToString("N");
|
||||
if (!string.IsNullOrWhiteSpace(TraceContext.TraceId))
|
||||
{
|
||||
return TraceContext.TraceId;
|
||||
}
|
||||
|
||||
if (Activity.Current?.Id is { } id && !string.IsNullOrWhiteSpace(id))
|
||||
{
|
||||
return id;
|
||||
}
|
||||
|
||||
return IdFallbackGenerator.Instance.NextId().ToString();
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class IdFallbackGenerator
|
||||
{
|
||||
private static readonly Lazy<IdFallbackGenerator> Lazy = new(() => new IdFallbackGenerator());
|
||||
public static IdFallbackGenerator Instance => Lazy.Value;
|
||||
|
||||
private readonly object _sync = new();
|
||||
private long _lastTimestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
||||
private long _sequence;
|
||||
|
||||
private IdFallbackGenerator()
|
||||
{
|
||||
}
|
||||
|
||||
public long NextId()
|
||||
{
|
||||
lock (_sync)
|
||||
{
|
||||
var timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
||||
if (timestamp == _lastTimestamp)
|
||||
{
|
||||
_sequence = (_sequence + 1) & 4095;
|
||||
if (_sequence == 0)
|
||||
{
|
||||
timestamp = WaitNextMillis(_lastTimestamp);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_sequence = 0;
|
||||
}
|
||||
|
||||
_lastTimestamp = timestamp;
|
||||
return ((timestamp - 1577836800000L) << 22) | _sequence;
|
||||
}
|
||||
}
|
||||
|
||||
private static long WaitNextMillis(long lastTimestamp)
|
||||
{
|
||||
var timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
||||
while (timestamp <= lastTimestamp)
|
||||
{
|
||||
Thread.SpinWait(100);
|
||||
timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
||||
}
|
||||
|
||||
return timestamp;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ public interface ICurrentUserAccessor
|
||||
/// <summary>
|
||||
/// 当前用户 ID,未登录时为 Guid.Empty。
|
||||
/// </summary>
|
||||
Guid UserId { get; }
|
||||
long UserId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否已登录。
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace TakeoutSaaS.Shared.Abstractions.Serialization;
|
||||
|
||||
/// <summary>
|
||||
/// 将 long 类型的雪花 ID 以字符串形式序列化/反序列化,避免前端精度丢失。
|
||||
/// </summary>
|
||||
public sealed class SnowflakeIdJsonConverter : JsonConverter<long>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override long Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
return reader.TokenType switch
|
||||
{
|
||||
JsonTokenType.Number => reader.GetInt64(),
|
||||
JsonTokenType.String when long.TryParse(reader.GetString(), out var value) => value,
|
||||
JsonTokenType.Null => 0,
|
||||
_ => throw new JsonException("无法解析雪花 ID")
|
||||
};
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Write(Utf8JsonWriter writer, long value, JsonSerializerOptions options)
|
||||
{
|
||||
writer.WriteStringValue(value == 0 ? "0" : value.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 可空雪花 ID 转换器。
|
||||
/// </summary>
|
||||
public sealed class NullableSnowflakeIdJsonConverter : JsonConverter<long?>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override long? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
return reader.TokenType switch
|
||||
{
|
||||
JsonTokenType.Number => reader.GetInt64(),
|
||||
JsonTokenType.String when long.TryParse(reader.GetString(), out var value) => value,
|
||||
JsonTokenType.Null => null,
|
||||
_ => throw new JsonException("无法解析雪花 ID")
|
||||
};
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Write(Utf8JsonWriter writer, long? value, JsonSerializerOptions options)
|
||||
{
|
||||
writer.WriteStringValue(value.HasValue ? value.Value.ToString() : null);
|
||||
}
|
||||
}
|
||||
@@ -8,5 +8,5 @@ public interface ITenantProvider
|
||||
/// <summary>
|
||||
/// 获取当前租户 ID,未解析时返回 Guid.Empty。
|
||||
/// </summary>
|
||||
Guid GetCurrentTenantId();
|
||||
long GetCurrentTenantId();
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ public sealed class TenantContext
|
||||
/// <summary>
|
||||
/// 未解析到租户时的默认上下文。
|
||||
/// </summary>
|
||||
public static TenantContext Empty { get; } = new(Guid.Empty, null, "unresolved");
|
||||
public static TenantContext Empty { get; } = new(0, null, "unresolved");
|
||||
|
||||
/// <summary>
|
||||
/// 初始化租户上下文。
|
||||
@@ -16,7 +16,7 @@ public sealed class TenantContext
|
||||
/// <param name="tenantId">租户 ID</param>
|
||||
/// <param name="tenantCode">租户编码(可选)</param>
|
||||
/// <param name="source">解析来源</param>
|
||||
public TenantContext(Guid tenantId, string? tenantCode, string source)
|
||||
public TenantContext(long tenantId, string? tenantCode, string source)
|
||||
{
|
||||
TenantId = tenantId;
|
||||
TenantCode = tenantCode;
|
||||
@@ -26,7 +26,7 @@ public sealed class TenantContext
|
||||
/// <summary>
|
||||
/// 当前租户 ID,未解析时为 Guid.Empty。
|
||||
/// </summary>
|
||||
public Guid TenantId { get; }
|
||||
public long TenantId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 当前租户编码(例如子域名或业务编码),可为空。
|
||||
@@ -41,5 +41,5 @@ public sealed class TenantContext
|
||||
/// <summary>
|
||||
/// 是否已成功解析到租户。
|
||||
/// </summary>
|
||||
public bool IsResolved => TenantId != Guid.Empty;
|
||||
public bool IsResolved => TenantId != 0;
|
||||
}
|
||||
|
||||
111
src/Core/TakeoutSaaS.Shared.Kernel/Ids/SnowflakeIdGenerator.cs
Normal file
111
src/Core/TakeoutSaaS.Shared.Kernel/Ids/SnowflakeIdGenerator.cs
Normal file
@@ -0,0 +1,111 @@
|
||||
using System.Diagnostics;
|
||||
using System.Security.Cryptography;
|
||||
using System.Threading;
|
||||
using TakeoutSaaS.Shared.Abstractions.Ids;
|
||||
|
||||
namespace TakeoutSaaS.Shared.Kernel.Ids;
|
||||
|
||||
/// <summary>
|
||||
/// 基于雪花算法的长整型 ID 生成器。
|
||||
/// </summary>
|
||||
public sealed class SnowflakeIdGenerator : IIdGenerator
|
||||
{
|
||||
private const long Twepoch = 1577836800000L; // 2020-01-01 UTC
|
||||
private const int WorkerIdBits = 5;
|
||||
private const int DatacenterIdBits = 5;
|
||||
private const int SequenceBits = 12;
|
||||
|
||||
private const long MaxWorkerId = -1L ^ (-1L << WorkerIdBits);
|
||||
private const long MaxDatacenterId = -1L ^ (-1L << DatacenterIdBits);
|
||||
|
||||
private const int WorkerIdShift = SequenceBits;
|
||||
private const int DatacenterIdShift = SequenceBits + WorkerIdBits;
|
||||
private const int TimestampLeftShift = SequenceBits + WorkerIdBits + DatacenterIdBits;
|
||||
private const long SequenceMask = -1L ^ (-1L << SequenceBits);
|
||||
|
||||
private readonly long _workerId;
|
||||
private readonly long _datacenterId;
|
||||
private long _lastTimestamp = -1L;
|
||||
private long _sequence;
|
||||
private readonly object _syncRoot = new();
|
||||
|
||||
/// <summary>
|
||||
/// 初始化生成器。
|
||||
/// </summary>
|
||||
/// <param name="workerId">工作节点 ID。</param>
|
||||
/// <param name="datacenterId">机房 ID。</param>
|
||||
public SnowflakeIdGenerator(long workerId = 0, long datacenterId = 0)
|
||||
{
|
||||
_workerId = Normalize(workerId, MaxWorkerId, nameof(workerId));
|
||||
_datacenterId = Normalize(datacenterId, MaxDatacenterId, nameof(datacenterId));
|
||||
_sequence = RandomNumberGenerator.GetInt32(0, (int)SequenceMask);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public long NextId()
|
||||
{
|
||||
lock (_syncRoot)
|
||||
{
|
||||
var timestamp = CurrentTimeMillis();
|
||||
|
||||
if (timestamp < _lastTimestamp)
|
||||
{
|
||||
// 时钟回拨时等待到下一毫秒。
|
||||
var wait = _lastTimestamp - timestamp;
|
||||
Thread.Sleep(TimeSpan.FromMilliseconds(wait));
|
||||
timestamp = CurrentTimeMillis();
|
||||
if (timestamp < _lastTimestamp)
|
||||
{
|
||||
throw new InvalidOperationException($"系统时钟回拨 {_lastTimestamp - timestamp} 毫秒,无法生成 ID。");
|
||||
}
|
||||
}
|
||||
|
||||
if (_lastTimestamp == timestamp)
|
||||
{
|
||||
_sequence = (_sequence + 1) & SequenceMask;
|
||||
if (_sequence == 0)
|
||||
{
|
||||
timestamp = WaitNextMillis(_lastTimestamp);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_sequence = 0;
|
||||
}
|
||||
|
||||
_lastTimestamp = timestamp;
|
||||
|
||||
var id = ((timestamp - Twepoch) << TimestampLeftShift)
|
||||
| (_datacenterId << DatacenterIdShift)
|
||||
| (_workerId << WorkerIdShift)
|
||||
| _sequence;
|
||||
|
||||
Debug.Assert(id > 0);
|
||||
return id;
|
||||
}
|
||||
}
|
||||
|
||||
private static long WaitNextMillis(long lastTimestamp)
|
||||
{
|
||||
var timestamp = CurrentTimeMillis();
|
||||
while (timestamp <= lastTimestamp)
|
||||
{
|
||||
Thread.SpinWait(50);
|
||||
timestamp = CurrentTimeMillis();
|
||||
}
|
||||
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
private static long CurrentTimeMillis() => DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
||||
|
||||
private static long Normalize(long value, long max, string name)
|
||||
{
|
||||
if (value < 0 || value > max)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(name, value, $"取值范围 0~{max}");
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using TakeoutSaaS.Shared.Abstractions.Diagnostics;
|
||||
using TakeoutSaaS.Shared.Abstractions.Ids;
|
||||
|
||||
namespace TakeoutSaaS.Shared.Web.Middleware;
|
||||
|
||||
@@ -17,11 +18,13 @@ public sealed class CorrelationIdMiddleware
|
||||
|
||||
private readonly RequestDelegate _next;
|
||||
private readonly ILogger<CorrelationIdMiddleware> _logger;
|
||||
private readonly IIdGenerator _idGenerator;
|
||||
|
||||
public CorrelationIdMiddleware(RequestDelegate next, ILogger<CorrelationIdMiddleware> logger)
|
||||
public CorrelationIdMiddleware(RequestDelegate next, ILogger<CorrelationIdMiddleware> logger, IIdGenerator idGenerator)
|
||||
{
|
||||
_next = next;
|
||||
_logger = logger;
|
||||
_idGenerator = idGenerator;
|
||||
}
|
||||
|
||||
public async Task InvokeAsync(HttpContext context)
|
||||
@@ -52,7 +55,7 @@ public sealed class CorrelationIdMiddleware
|
||||
}
|
||||
}
|
||||
|
||||
private static string ResolveTraceId(HttpContext context)
|
||||
private string ResolveTraceId(HttpContext context)
|
||||
{
|
||||
if (TryGetHeader(context, TraceHeader, out var traceId))
|
||||
{
|
||||
@@ -64,7 +67,7 @@ public sealed class CorrelationIdMiddleware
|
||||
return requestId;
|
||||
}
|
||||
|
||||
return Guid.NewGuid().ToString("N");
|
||||
return _idGenerator.NextId().ToString();
|
||||
}
|
||||
|
||||
private static bool TryGetHeader(HttpContext context, string headerName, out string value)
|
||||
|
||||
@@ -9,20 +9,20 @@ namespace TakeoutSaaS.Shared.Web.Security;
|
||||
public static class ClaimsPrincipalExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取当前用户 Id(不存在时返回 Guid.Empty)
|
||||
/// 获取当前用户 Id(不存在时返回 0)。
|
||||
/// </summary>
|
||||
public static Guid GetUserId(this ClaimsPrincipal? principal)
|
||||
public static long GetUserId(this ClaimsPrincipal? principal)
|
||||
{
|
||||
if (principal == null)
|
||||
{
|
||||
return Guid.Empty;
|
||||
return 0;
|
||||
}
|
||||
|
||||
var identifier = principal.FindFirstValue(ClaimTypes.NameIdentifier)
|
||||
?? principal.FindFirstValue("sub");
|
||||
|
||||
return Guid.TryParse(identifier, out var userId)
|
||||
return long.TryParse(identifier, out var userId)
|
||||
? userId
|
||||
: Guid.Empty;
|
||||
: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,23 +20,23 @@ public sealed class HttpContextCurrentUserAccessor : ICurrentUserAccessor
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Guid UserId
|
||||
public long UserId
|
||||
{
|
||||
get
|
||||
{
|
||||
var principal = _httpContextAccessor.HttpContext?.User;
|
||||
if (principal == null || !principal.Identity?.IsAuthenticated == true)
|
||||
{
|
||||
return Guid.Empty;
|
||||
return 0;
|
||||
}
|
||||
|
||||
var identifier = principal.FindFirstValue(ClaimTypes.NameIdentifier)
|
||||
?? principal.FindFirstValue("sub");
|
||||
|
||||
return Guid.TryParse(identifier, out var id) ? id : Guid.Empty;
|
||||
return long.TryParse(identifier, out var id) ? id : 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsAuthenticated => UserId != Guid.Empty;
|
||||
public bool IsAuthenticated => UserId != 0;
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ public sealed class MetricAlertRule : MultiTenantEntityBase
|
||||
/// <summary>
|
||||
/// 关联指标。
|
||||
/// </summary>
|
||||
public Guid MetricDefinitionId { get; set; }
|
||||
public long MetricDefinitionId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 触发条件 JSON。
|
||||
|
||||
@@ -10,7 +10,7 @@ public sealed class MetricSnapshot : MultiTenantEntityBase
|
||||
/// <summary>
|
||||
/// 指标定义 ID。
|
||||
/// </summary>
|
||||
public Guid MetricDefinitionId { get; set; }
|
||||
public long MetricDefinitionId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 维度键(JSON)。
|
||||
|
||||
@@ -11,7 +11,7 @@ public sealed class Coupon : MultiTenantEntityBase
|
||||
/// <summary>
|
||||
/// 模板标识。
|
||||
/// </summary>
|
||||
public Guid CouponTemplateId { get; set; }
|
||||
public long CouponTemplateId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 券码或序列号。
|
||||
@@ -21,12 +21,12 @@ public sealed class Coupon : MultiTenantEntityBase
|
||||
/// <summary>
|
||||
/// 归属用户。
|
||||
/// </summary>
|
||||
public Guid UserId { get; set; }
|
||||
public long UserId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 订单 ID(已使用时记录)。
|
||||
/// </summary>
|
||||
public Guid? OrderId { get; set; }
|
||||
public long? OrderId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 状态。
|
||||
|
||||
@@ -11,7 +11,7 @@ public sealed class ChatMessage : MultiTenantEntityBase
|
||||
/// <summary>
|
||||
/// 会话标识。
|
||||
/// </summary>
|
||||
public Guid ChatSessionId { get; set; }
|
||||
public long ChatSessionId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 发送方类型。
|
||||
@@ -21,7 +21,7 @@ public sealed class ChatMessage : MultiTenantEntityBase
|
||||
/// <summary>
|
||||
/// 发送方用户 ID。
|
||||
/// </summary>
|
||||
public Guid? SenderUserId { get; set; }
|
||||
public long? SenderUserId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 消息内容。
|
||||
|
||||
@@ -16,17 +16,17 @@ public sealed class ChatSession : MultiTenantEntityBase
|
||||
/// <summary>
|
||||
/// 顾客用户 ID。
|
||||
/// </summary>
|
||||
public Guid CustomerUserId { get; set; }
|
||||
public long CustomerUserId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 当前客服员工 ID。
|
||||
/// </summary>
|
||||
public Guid? AgentUserId { get; set; }
|
||||
public long? AgentUserId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 所属门店(可空为平台)。
|
||||
/// </summary>
|
||||
public Guid? StoreId { get; set; }
|
||||
public long? StoreId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 会话状态。
|
||||
|
||||
@@ -16,12 +16,12 @@ public sealed class SupportTicket : MultiTenantEntityBase
|
||||
/// <summary>
|
||||
/// 客户用户 ID。
|
||||
/// </summary>
|
||||
public Guid CustomerUserId { get; set; }
|
||||
public long CustomerUserId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 关联订单(如有)。
|
||||
/// </summary>
|
||||
public Guid? OrderId { get; set; }
|
||||
public long? OrderId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 工单主题。
|
||||
@@ -46,7 +46,7 @@ public sealed class SupportTicket : MultiTenantEntityBase
|
||||
/// <summary>
|
||||
/// 指派的客服。
|
||||
/// </summary>
|
||||
public Guid? AssignedAgentId { get; set; }
|
||||
public long? AssignedAgentId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 关闭时间。
|
||||
|
||||
@@ -10,12 +10,12 @@ public sealed class TicketComment : MultiTenantEntityBase
|
||||
/// <summary>
|
||||
/// 工单标识。
|
||||
/// </summary>
|
||||
public Guid SupportTicketId { get; set; }
|
||||
public long SupportTicketId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 评论人 ID。
|
||||
/// </summary>
|
||||
public Guid? AuthorUserId { get; set; }
|
||||
public long? AuthorUserId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 评论内容。
|
||||
|
||||
@@ -11,7 +11,7 @@ public sealed class DeliveryEvent : MultiTenantEntityBase
|
||||
/// <summary>
|
||||
/// 配送单标识。
|
||||
/// </summary>
|
||||
public Guid DeliveryOrderId { get; set; }
|
||||
public long DeliveryOrderId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 事件类型。
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace TakeoutSaaS.Domain.Deliveries.Entities;
|
||||
/// </summary>
|
||||
public sealed class DeliveryOrder : MultiTenantEntityBase
|
||||
{
|
||||
public Guid OrderId { get; set; }
|
||||
public long OrderId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 配送服务商。
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using TakeoutSaaS.Domain.Deliveries.Entities;
|
||||
|
||||
namespace TakeoutSaaS.Domain.Deliveries.Repositories;
|
||||
|
||||
/// <summary>
|
||||
/// 配送聚合仓储契约。
|
||||
/// </summary>
|
||||
public interface IDeliveryRepository
|
||||
{
|
||||
/// <summary>
|
||||
/// 依据标识获取配送单。
|
||||
/// </summary>
|
||||
Task<DeliveryOrder?> FindByIdAsync(long deliveryOrderId, long tenantId, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 依据订单标识获取配送单。
|
||||
/// </summary>
|
||||
Task<DeliveryOrder?> FindByOrderIdAsync(long orderId, long tenantId, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 获取配送事件轨迹。
|
||||
/// </summary>
|
||||
Task<IReadOnlyList<DeliveryEvent>> GetEventsAsync(long deliveryOrderId, long tenantId, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 新增配送单。
|
||||
/// </summary>
|
||||
Task AddDeliveryOrderAsync(DeliveryOrder deliveryOrder, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 新增配送事件。
|
||||
/// </summary>
|
||||
Task AddEventAsync(DeliveryEvent deliveryEvent, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 持久化变更。
|
||||
/// </summary>
|
||||
Task SaveChangesAsync(CancellationToken cancellationToken = default);
|
||||
}
|
||||
@@ -10,7 +10,7 @@ public sealed class DictionaryItem : MultiTenantEntityBase
|
||||
/// <summary>
|
||||
/// 关联分组 ID。
|
||||
/// </summary>
|
||||
public Guid GroupId { get; set; }
|
||||
public long GroupId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 字典项键。
|
||||
|
||||
@@ -14,7 +14,7 @@ public interface IDictionaryRepository
|
||||
/// <summary>
|
||||
/// 依据 ID 获取分组。
|
||||
/// </summary>
|
||||
Task<DictionaryGroup?> FindGroupByIdAsync(Guid id, CancellationToken cancellationToken = default);
|
||||
Task<DictionaryGroup?> FindGroupByIdAsync(long id, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 依据编码获取分组。
|
||||
@@ -39,17 +39,17 @@ public interface IDictionaryRepository
|
||||
/// <summary>
|
||||
/// 依据 ID 获取字典项。
|
||||
/// </summary>
|
||||
Task<DictionaryItem?> FindItemByIdAsync(Guid id, CancellationToken cancellationToken = default);
|
||||
Task<DictionaryItem?> FindItemByIdAsync(long id, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 获取某分组下的所有字典项。
|
||||
/// </summary>
|
||||
Task<IReadOnlyList<DictionaryItem>> GetItemsByGroupIdAsync(Guid groupId, CancellationToken cancellationToken = default);
|
||||
Task<IReadOnlyList<DictionaryItem>> GetItemsByGroupIdAsync(long groupId, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 按分组编码集合获取字典项(可包含系统参数)。
|
||||
/// </summary>
|
||||
Task<IReadOnlyList<DictionaryItem>> GetItemsByCodesAsync(IEnumerable<string> codes, Guid tenantId, bool includeSystem, CancellationToken cancellationToken = default);
|
||||
Task<IReadOnlyList<DictionaryItem>> GetItemsByCodesAsync(IEnumerable<string> codes, long tenantId, bool includeSystem, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 新增字典项。
|
||||
|
||||
@@ -11,17 +11,17 @@ public sealed class AffiliateOrder : MultiTenantEntityBase
|
||||
/// <summary>
|
||||
/// 推广人标识。
|
||||
/// </summary>
|
||||
public Guid AffiliatePartnerId { get; set; }
|
||||
public long AffiliatePartnerId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 关联订单。
|
||||
/// </summary>
|
||||
public Guid OrderId { get; set; }
|
||||
public long OrderId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 用户 ID。
|
||||
/// </summary>
|
||||
public Guid BuyerUserId { get; set; }
|
||||
public long BuyerUserId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 订单金额。
|
||||
|
||||
@@ -11,7 +11,7 @@ public sealed class AffiliatePartner : MultiTenantEntityBase
|
||||
/// <summary>
|
||||
/// 用户 ID(如绑定平台账号)。
|
||||
/// </summary>
|
||||
public Guid? UserId { get; set; }
|
||||
public long? UserId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 昵称或渠道名称。
|
||||
|
||||
@@ -11,7 +11,7 @@ public sealed class AffiliatePayout : MultiTenantEntityBase
|
||||
/// <summary>
|
||||
/// 合作伙伴标识。
|
||||
/// </summary>
|
||||
public Guid AffiliatePartnerId { get; set; }
|
||||
public long AffiliatePartnerId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 结算周期描述。
|
||||
|
||||
@@ -10,12 +10,12 @@ public sealed class CheckInRecord : MultiTenantEntityBase
|
||||
/// <summary>
|
||||
/// 活动标识。
|
||||
/// </summary>
|
||||
public Guid CheckInCampaignId { get; set; }
|
||||
public long CheckInCampaignId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 用户标识。
|
||||
/// </summary>
|
||||
public Guid UserId { get; set; }
|
||||
public long UserId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 签到日期(本地)。
|
||||
|
||||
@@ -10,12 +10,12 @@ public sealed class CommunityComment : MultiTenantEntityBase
|
||||
/// <summary>
|
||||
/// 动态标识。
|
||||
/// </summary>
|
||||
public Guid PostId { get; set; }
|
||||
public long PostId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 评论人。
|
||||
/// </summary>
|
||||
public Guid AuthorUserId { get; set; }
|
||||
public long AuthorUserId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 评论内容。
|
||||
@@ -25,7 +25,7 @@ public sealed class CommunityComment : MultiTenantEntityBase
|
||||
/// <summary>
|
||||
/// 父级评论 ID。
|
||||
/// </summary>
|
||||
public Guid? ParentId { get; set; }
|
||||
public long? ParentId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 状态。
|
||||
|
||||
@@ -11,7 +11,7 @@ public sealed class CommunityPost : MultiTenantEntityBase
|
||||
/// <summary>
|
||||
/// 作者用户 ID。
|
||||
/// </summary>
|
||||
public Guid AuthorUserId { get; set; }
|
||||
public long AuthorUserId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 标题。
|
||||
|
||||
@@ -11,12 +11,12 @@ public sealed class CommunityReaction : MultiTenantEntityBase
|
||||
/// <summary>
|
||||
/// 动态 ID。
|
||||
/// </summary>
|
||||
public Guid PostId { get; set; }
|
||||
public long PostId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 用户 ID。
|
||||
/// </summary>
|
||||
public Guid UserId { get; set; }
|
||||
public long UserId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 反应类型。
|
||||
|
||||
@@ -11,12 +11,12 @@ public sealed class GroupOrder : MultiTenantEntityBase
|
||||
/// <summary>
|
||||
/// 门店标识。
|
||||
/// </summary>
|
||||
public Guid StoreId { get; set; }
|
||||
public long StoreId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 关联商品或套餐。
|
||||
/// </summary>
|
||||
public Guid ProductId { get; set; }
|
||||
public long ProductId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 拼单编号。
|
||||
@@ -26,7 +26,7 @@ public sealed class GroupOrder : MultiTenantEntityBase
|
||||
/// <summary>
|
||||
/// 团长用户 ID。
|
||||
/// </summary>
|
||||
public Guid LeaderUserId { get; set; }
|
||||
public long LeaderUserId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 成团需要的人数。
|
||||
|
||||
@@ -11,17 +11,17 @@ public sealed class GroupParticipant : MultiTenantEntityBase
|
||||
/// <summary>
|
||||
/// 拼单活动标识。
|
||||
/// </summary>
|
||||
public Guid GroupOrderId { get; set; }
|
||||
public long GroupOrderId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 对应订单标识。
|
||||
/// </summary>
|
||||
public Guid OrderId { get; set; }
|
||||
public long OrderId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 用户标识。
|
||||
/// </summary>
|
||||
public Guid UserId { get; set; }
|
||||
public long UserId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 参与状态。
|
||||
|
||||
@@ -25,7 +25,7 @@ public sealed class IdentityUser : MultiTenantEntityBase
|
||||
/// <summary>
|
||||
/// 所属商户(平台管理员为空)。
|
||||
/// </summary>
|
||||
public Guid? MerchantId { get; set; }
|
||||
public long? MerchantId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 角色集合。
|
||||
|
||||
@@ -18,5 +18,5 @@ public interface IIdentityUserRepository
|
||||
/// <summary>
|
||||
/// 根据 ID 获取后台用户。
|
||||
/// </summary>
|
||||
Task<IdentityUser?> FindByIdAsync(Guid userId, CancellationToken cancellationToken = default);
|
||||
Task<IdentityUser?> FindByIdAsync(long userId, CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ public interface IMiniUserRepository
|
||||
/// <param name="id">用户 ID</param>
|
||||
/// <param name="cancellationToken">取消令牌</param>
|
||||
/// <returns>小程序用户,如果不存在则返回 null</returns>
|
||||
Task<MiniUser?> FindByIdAsync(Guid id, CancellationToken cancellationToken = default);
|
||||
Task<MiniUser?> FindByIdAsync(long id, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 创建或更新小程序用户(如果 OpenId 已存在则更新,否则创建)。
|
||||
@@ -33,5 +33,5 @@ public interface IMiniUserRepository
|
||||
/// <param name="tenantId">租户 ID</param>
|
||||
/// <param name="cancellationToken">取消令牌</param>
|
||||
/// <returns>创建或更新后的小程序用户</returns>
|
||||
Task<MiniUser> CreateOrUpdateAsync(string openId, string? unionId, string? nickname, string? avatar, Guid tenantId, CancellationToken cancellationToken = default);
|
||||
Task<MiniUser> CreateOrUpdateAsync(string openId, string? unionId, string? nickname, string? avatar, long tenantId, CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ public sealed class InventoryAdjustment : MultiTenantEntityBase
|
||||
/// <summary>
|
||||
/// 对应的库存记录标识。
|
||||
/// </summary>
|
||||
public Guid InventoryItemId { get; set; }
|
||||
public long InventoryItemId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 调整类型。
|
||||
@@ -31,7 +31,7 @@ public sealed class InventoryAdjustment : MultiTenantEntityBase
|
||||
/// <summary>
|
||||
/// 操作人标识。
|
||||
/// </summary>
|
||||
public Guid? OperatorId { get; set; }
|
||||
public long? OperatorId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 发生时间。
|
||||
|
||||
@@ -10,12 +10,12 @@ public sealed class InventoryBatch : MultiTenantEntityBase
|
||||
/// <summary>
|
||||
/// 门店标识。
|
||||
/// </summary>
|
||||
public Guid StoreId { get; set; }
|
||||
public long StoreId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// SKU 标识。
|
||||
/// </summary>
|
||||
public Guid ProductSkuId { get; set; }
|
||||
public long ProductSkuId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 批次编号。
|
||||
|
||||
@@ -10,12 +10,12 @@ public sealed class InventoryItem : MultiTenantEntityBase
|
||||
/// <summary>
|
||||
/// 门店标识。
|
||||
/// </summary>
|
||||
public Guid StoreId { get; set; }
|
||||
public long StoreId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// SKU 标识。
|
||||
/// </summary>
|
||||
public Guid ProductSkuId { get; set; }
|
||||
public long ProductSkuId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 批次编号,可为空表示混批。
|
||||
|
||||
@@ -10,7 +10,7 @@ public sealed class MemberGrowthLog : MultiTenantEntityBase
|
||||
/// <summary>
|
||||
/// 会员标识。
|
||||
/// </summary>
|
||||
public Guid MemberId { get; set; }
|
||||
public long MemberId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 变动数量。
|
||||
|
||||
@@ -11,7 +11,7 @@ public sealed class MemberPointLedger : MultiTenantEntityBase
|
||||
/// <summary>
|
||||
/// 会员标识。
|
||||
/// </summary>
|
||||
public Guid MemberId { get; set; }
|
||||
public long MemberId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 变动数量,可为负值。
|
||||
@@ -31,7 +31,7 @@ public sealed class MemberPointLedger : MultiTenantEntityBase
|
||||
/// <summary>
|
||||
/// 来源 ID(订单、活动等)。
|
||||
/// </summary>
|
||||
public Guid? SourceId { get; set; }
|
||||
public long? SourceId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 发生时间。
|
||||
|
||||
@@ -11,7 +11,7 @@ public sealed class MemberProfile : MultiTenantEntityBase
|
||||
/// <summary>
|
||||
/// 用户标识。
|
||||
/// </summary>
|
||||
public Guid UserId { get; set; }
|
||||
public long UserId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 手机号。
|
||||
@@ -31,7 +31,7 @@ public sealed class MemberProfile : MultiTenantEntityBase
|
||||
/// <summary>
|
||||
/// 当前会员等级 ID。
|
||||
/// </summary>
|
||||
public Guid? MemberTierId { get; set; }
|
||||
public long? MemberTierId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 会员状态。
|
||||
|
||||
@@ -11,7 +11,7 @@ public sealed class MerchantContract : MultiTenantEntityBase
|
||||
/// <summary>
|
||||
/// 所属商户标识。
|
||||
/// </summary>
|
||||
public Guid MerchantId { get; set; }
|
||||
public long MerchantId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 合同编号。
|
||||
|
||||
@@ -11,7 +11,7 @@ public sealed class MerchantDocument : MultiTenantEntityBase
|
||||
/// <summary>
|
||||
/// 所属商户标识。
|
||||
/// </summary>
|
||||
public Guid MerchantId { get; set; }
|
||||
public long MerchantId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 证照类型。
|
||||
|
||||
@@ -11,12 +11,12 @@ public sealed class MerchantStaff : MultiTenantEntityBase
|
||||
/// <summary>
|
||||
/// 所属商户标识。
|
||||
/// </summary>
|
||||
public Guid MerchantId { get; set; }
|
||||
public long MerchantId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 可选的关联门店 ID。
|
||||
/// </summary>
|
||||
public Guid? StoreId { get; set; }
|
||||
public long? StoreId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 员工姓名。
|
||||
@@ -36,7 +36,7 @@ public sealed class MerchantStaff : MultiTenantEntityBase
|
||||
/// <summary>
|
||||
/// 登录账号 ID(指向统一身份体系)。
|
||||
/// </summary>
|
||||
public Guid? IdentityUserId { get; set; }
|
||||
public long? IdentityUserId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 员工角色类型。
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using TakeoutSaaS.Domain.Merchants.Entities;
|
||||
using TakeoutSaaS.Domain.Merchants.Enums;
|
||||
|
||||
namespace TakeoutSaaS.Domain.Merchants.Repositories;
|
||||
|
||||
/// <summary>
|
||||
/// 商户聚合仓储契约,提供基础 CRUD 与查询能力。
|
||||
/// </summary>
|
||||
public interface IMerchantRepository
|
||||
{
|
||||
/// <summary>
|
||||
/// 依据标识获取商户。
|
||||
/// </summary>
|
||||
Task<Merchant?> FindByIdAsync(long merchantId, long tenantId, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 按状态筛选商户列表。
|
||||
/// </summary>
|
||||
Task<IReadOnlyList<Merchant>> SearchAsync(long tenantId, MerchantStatus? status, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定商户的员工列表。
|
||||
/// </summary>
|
||||
Task<IReadOnlyList<MerchantStaff>> GetStaffAsync(long merchantId, long tenantId, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定商户的合同列表。
|
||||
/// </summary>
|
||||
Task<IReadOnlyList<MerchantContract>> GetContractsAsync(long merchantId, long tenantId, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定商户的资质文件列表。
|
||||
/// </summary>
|
||||
Task<IReadOnlyList<MerchantDocument>> GetDocumentsAsync(long merchantId, long tenantId, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 新增商户主体。
|
||||
/// </summary>
|
||||
Task AddMerchantAsync(Merchant merchant, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 新增商户员工。
|
||||
/// </summary>
|
||||
Task AddStaffAsync(MerchantStaff staff, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 新增商户合同。
|
||||
/// </summary>
|
||||
Task AddContractAsync(MerchantContract contract, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 新增商户资质文件。
|
||||
/// </summary>
|
||||
Task AddDocumentAsync(MerchantDocument document, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 持久化变更。
|
||||
/// </summary>
|
||||
Task SaveChangesAsync(CancellationToken cancellationToken = default);
|
||||
}
|
||||
@@ -10,7 +10,7 @@ public sealed class MapLocation : MultiTenantEntityBase
|
||||
/// <summary>
|
||||
/// 关联门店 ID,可空表示独立 POI。
|
||||
/// </summary>
|
||||
public Guid? StoreId { get; set; }
|
||||
public long? StoreId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 名称。
|
||||
|
||||
@@ -11,12 +11,12 @@ public sealed class NavigationRequest : MultiTenantEntityBase
|
||||
/// <summary>
|
||||
/// 用户 ID。
|
||||
/// </summary>
|
||||
public Guid UserId { get; set; }
|
||||
public long UserId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 门店 ID。
|
||||
/// </summary>
|
||||
public Guid StoreId { get; set; }
|
||||
public long StoreId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 来源通道(小程序、H5 等)。
|
||||
|
||||
@@ -11,17 +11,17 @@ public sealed class CartItem : MultiTenantEntityBase
|
||||
/// <summary>
|
||||
/// 所属购物车标识。
|
||||
/// </summary>
|
||||
public Guid ShoppingCartId { get; set; }
|
||||
public long ShoppingCartId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 商品或 SKU 标识。
|
||||
/// </summary>
|
||||
public Guid ProductId { get; set; }
|
||||
public long ProductId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// SKU 标识。
|
||||
/// </summary>
|
||||
public Guid? ProductSkuId { get; set; }
|
||||
public long? ProductSkuId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 商品名称快照。
|
||||
|
||||
@@ -10,7 +10,7 @@ public sealed class CartItemAddon : MultiTenantEntityBase
|
||||
/// <summary>
|
||||
/// 所属购物车条目。
|
||||
/// </summary>
|
||||
public Guid CartItemId { get; set; }
|
||||
public long CartItemId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 选项名称。
|
||||
@@ -25,5 +25,5 @@ public sealed class CartItemAddon : MultiTenantEntityBase
|
||||
/// <summary>
|
||||
/// 选项 ID(可对应 ProductAddonOption)。
|
||||
/// </summary>
|
||||
public Guid? OptionId { get; set; }
|
||||
public long? OptionId { get; set; }
|
||||
}
|
||||
|
||||
@@ -11,12 +11,12 @@ public sealed class CheckoutSession : MultiTenantEntityBase
|
||||
/// <summary>
|
||||
/// 用户标识。
|
||||
/// </summary>
|
||||
public Guid UserId { get; set; }
|
||||
public long UserId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 门店标识。
|
||||
/// </summary>
|
||||
public Guid StoreId { get; set; }
|
||||
public long StoreId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 会话 Token。
|
||||
|
||||
@@ -11,12 +11,12 @@ public sealed class ShoppingCart : MultiTenantEntityBase
|
||||
/// <summary>
|
||||
/// 用户标识。
|
||||
/// </summary>
|
||||
public Guid UserId { get; set; }
|
||||
public long UserId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 门店标识。
|
||||
/// </summary>
|
||||
public Guid StoreId { get; set; }
|
||||
public long StoreId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 购物车状态,包含正常/锁定。
|
||||
|
||||
@@ -17,7 +17,7 @@ public sealed class Order : MultiTenantEntityBase
|
||||
/// <summary>
|
||||
/// 门店。
|
||||
/// </summary>
|
||||
public Guid StoreId { get; set; }
|
||||
public long StoreId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 下单渠道。
|
||||
@@ -62,7 +62,7 @@ public sealed class Order : MultiTenantEntityBase
|
||||
/// <summary>
|
||||
/// 预约 ID。
|
||||
/// </summary>
|
||||
public Guid? ReservationId { get; set; }
|
||||
public long? ReservationId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 商品总额。
|
||||
|
||||
@@ -10,12 +10,12 @@ public sealed class OrderItem : MultiTenantEntityBase
|
||||
/// <summary>
|
||||
/// 订单 ID。
|
||||
/// </summary>
|
||||
public Guid OrderId { get; set; }
|
||||
public long OrderId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 商品 ID。
|
||||
/// </summary>
|
||||
public Guid ProductId { get; set; }
|
||||
public long ProductId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 商品名称。
|
||||
|
||||
@@ -11,7 +11,7 @@ public sealed class OrderStatusHistory : MultiTenantEntityBase
|
||||
/// <summary>
|
||||
/// 订单标识。
|
||||
/// </summary>
|
||||
public Guid OrderId { get; set; }
|
||||
public long OrderId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 变更后的状态。
|
||||
@@ -21,7 +21,7 @@ public sealed class OrderStatusHistory : MultiTenantEntityBase
|
||||
/// <summary>
|
||||
/// 操作人标识(可为空表示系统)。
|
||||
/// </summary>
|
||||
public Guid? OperatorId { get; set; }
|
||||
public long? OperatorId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 备注信息。
|
||||
|
||||
@@ -11,7 +11,7 @@ public sealed class RefundRequest : MultiTenantEntityBase
|
||||
/// <summary>
|
||||
/// 关联订单标识。
|
||||
/// </summary>
|
||||
public Guid OrderId { get; set; }
|
||||
public long OrderId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 退款单号。
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using TakeoutSaaS.Domain.Orders.Entities;
|
||||
using TakeoutSaaS.Domain.Orders.Enums;
|
||||
using TakeoutSaaS.Domain.Payments.Enums;
|
||||
|
||||
namespace TakeoutSaaS.Domain.Orders.Repositories;
|
||||
|
||||
/// <summary>
|
||||
/// 订单聚合仓储契约。
|
||||
/// </summary>
|
||||
public interface IOrderRepository
|
||||
{
|
||||
/// <summary>
|
||||
/// 依据标识获取订单。
|
||||
/// </summary>
|
||||
Task<Order?> FindByIdAsync(long orderId, long tenantId, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 依据订单号获取订单。
|
||||
/// </summary>
|
||||
Task<Order?> FindByOrderNoAsync(string orderNo, long tenantId, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 按状态筛选订单列表。
|
||||
/// </summary>
|
||||
Task<IReadOnlyList<Order>> SearchAsync(long tenantId, OrderStatus? status, PaymentStatus? paymentStatus, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 获取订单明细行。
|
||||
/// </summary>
|
||||
Task<IReadOnlyList<OrderItem>> GetItemsAsync(long orderId, long tenantId, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 获取订单状态流转记录。
|
||||
/// </summary>
|
||||
Task<IReadOnlyList<OrderStatusHistory>> GetStatusHistoryAsync(long orderId, long tenantId, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 获取订单退款申请。
|
||||
/// </summary>
|
||||
Task<IReadOnlyList<RefundRequest>> GetRefundsAsync(long orderId, long tenantId, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 新增订单。
|
||||
/// </summary>
|
||||
Task AddOrderAsync(Order order, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 新增订单明细。
|
||||
/// </summary>
|
||||
Task AddItemsAsync(IEnumerable<OrderItem> items, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 新增订单状态记录。
|
||||
/// </summary>
|
||||
Task AddStatusHistoryAsync(OrderStatusHistory history, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 新增退款申请。
|
||||
/// </summary>
|
||||
Task AddRefundAsync(RefundRequest refund, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 持久化变更。
|
||||
/// </summary>
|
||||
Task SaveChangesAsync(CancellationToken cancellationToken = default);
|
||||
}
|
||||
@@ -11,7 +11,7 @@ public sealed class PaymentRecord : MultiTenantEntityBase
|
||||
/// <summary>
|
||||
/// 关联订单。
|
||||
/// </summary>
|
||||
public Guid OrderId { get; set; }
|
||||
public long OrderId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 支付方式。
|
||||
|
||||
@@ -11,12 +11,12 @@ public sealed class PaymentRefundRecord : MultiTenantEntityBase
|
||||
/// <summary>
|
||||
/// 原支付记录标识。
|
||||
/// </summary>
|
||||
public Guid PaymentRecordId { get; set; }
|
||||
public long PaymentRecordId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 关联订单标识。
|
||||
/// </summary>
|
||||
public Guid OrderId { get; set; }
|
||||
public long OrderId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 退款金额。
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using TakeoutSaaS.Domain.Payments.Entities;
|
||||
|
||||
namespace TakeoutSaaS.Domain.Payments.Repositories;
|
||||
|
||||
/// <summary>
|
||||
/// 支付记录仓储契约。
|
||||
/// </summary>
|
||||
public interface IPaymentRepository
|
||||
{
|
||||
/// <summary>
|
||||
/// 依据标识获取支付记录。
|
||||
/// </summary>
|
||||
Task<PaymentRecord?> FindByIdAsync(long paymentId, long tenantId, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 依据订单标识获取支付记录。
|
||||
/// </summary>
|
||||
Task<PaymentRecord?> FindByOrderIdAsync(long orderId, long tenantId, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 获取支付对应的退款记录。
|
||||
/// </summary>
|
||||
Task<IReadOnlyList<PaymentRefundRecord>> GetRefundsAsync(long paymentId, long tenantId, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 新增支付记录。
|
||||
/// </summary>
|
||||
Task AddPaymentAsync(PaymentRecord payment, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 新增退款记录。
|
||||
/// </summary>
|
||||
Task AddRefundAsync(PaymentRefundRecord refund, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 持久化变更。
|
||||
/// </summary>
|
||||
Task SaveChangesAsync(CancellationToken cancellationToken = default);
|
||||
}
|
||||
@@ -11,12 +11,12 @@ public sealed class Product : MultiTenantEntityBase
|
||||
/// <summary>
|
||||
/// 所属门店。
|
||||
/// </summary>
|
||||
public Guid StoreId { get; set; }
|
||||
public long StoreId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 所属分类。
|
||||
/// </summary>
|
||||
public Guid CategoryId { get; set; }
|
||||
public long CategoryId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 商品编码。
|
||||
|
||||
@@ -11,7 +11,7 @@ public sealed class ProductAddonGroup : MultiTenantEntityBase
|
||||
/// <summary>
|
||||
/// 所属商品。
|
||||
/// </summary>
|
||||
public Guid ProductId { get; set; }
|
||||
public long ProductId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 分组名称。
|
||||
|
||||
@@ -10,7 +10,7 @@ public sealed class ProductAddonOption : MultiTenantEntityBase
|
||||
/// <summary>
|
||||
/// 所属加料分组。
|
||||
/// </summary>
|
||||
public Guid AddonGroupId { get; set; }
|
||||
public long AddonGroupId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 选项名称。
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user