chore: add documentation comments and stylecop rules
This commit is contained in:
6
.editorconfig
Normal file
6
.editorconfig
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
# EditorConfig
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*.cs]
|
||||||
|
dotnet_diagnostic.SA1600.severity = error
|
||||||
|
dotnet_diagnostic.SA1601.severity = error
|
||||||
@@ -6,5 +6,8 @@
|
|||||||
<LangVersion>latest</LangVersion>
|
<LangVersion>latest</LangVersion>
|
||||||
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
|
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.507" PrivateAssets="all" />
|
||||||
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|
||||||
|
|||||||
@@ -13,21 +13,16 @@ using TakeoutSaaS.Shared.Web.Api;
|
|||||||
using TakeoutSaaS.Shared.Web.Security;
|
using TakeoutSaaS.Shared.Web.Security;
|
||||||
|
|
||||||
namespace TakeoutSaaS.AdminApi.Controllers;
|
namespace TakeoutSaaS.AdminApi.Controllers;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 管理后台认证接口
|
/// 管理后台认证接口
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>提供登录、刷新 Token 以及用户权限查询能力。</remarks>
|
||||||
///
|
/// <param name="authService">认证服务</param>
|
||||||
/// </remarks>
|
|
||||||
/// <param name="authService"></param>
|
|
||||||
[ApiVersion("1.0")]
|
[ApiVersion("1.0")]
|
||||||
[Authorize]
|
[Authorize]
|
||||||
[Route("api/admin/v{version:apiVersion}/auth")]
|
[Route("api/admin/v{version:apiVersion}/auth")]
|
||||||
public sealed class AuthController(IAdminAuthService authService) : BaseApiController
|
public sealed class AuthController(IAdminAuthService authService) : BaseApiController
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 登录获取 Token
|
/// 登录获取 Token
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -84,12 +79,14 @@ public sealed class AuthController(IAdminAuthService authService) : BaseApiContr
|
|||||||
[ProducesResponseType(typeof(ApiResponse<CurrentUserProfile>), StatusCodes.Status401Unauthorized)]
|
[ProducesResponseType(typeof(ApiResponse<CurrentUserProfile>), StatusCodes.Status401Unauthorized)]
|
||||||
public async Task<ApiResponse<CurrentUserProfile>> GetProfile(CancellationToken cancellationToken)
|
public async Task<ApiResponse<CurrentUserProfile>> GetProfile(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 从 JWT 中获取当前用户标识
|
||||||
var userId = User.GetUserId();
|
var userId = User.GetUserId();
|
||||||
if (userId == 0)
|
if (userId == 0)
|
||||||
{
|
{
|
||||||
return ApiResponse<CurrentUserProfile>.Error(ErrorCodes.Unauthorized, "Token 缺少有效的用户标识");
|
return ApiResponse<CurrentUserProfile>.Error(ErrorCodes.Unauthorized, "Token 缺少有效的用户标识");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 2. 读取用户档案并返回
|
||||||
var profile = await authService.GetProfileAsync(userId, cancellationToken);
|
var profile = await authService.GetProfileAsync(userId, cancellationToken);
|
||||||
return ApiResponse<CurrentUserProfile>.Ok(profile);
|
return ApiResponse<CurrentUserProfile>.Ok(profile);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,16 +16,11 @@ namespace TakeoutSaaS.AdminApi.Controllers;
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 配送单管理。
|
/// 配送单管理。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
|
||||||
/// 初始化控制器。
|
|
||||||
/// </remarks>
|
|
||||||
[ApiVersion("1.0")]
|
[ApiVersion("1.0")]
|
||||||
[Authorize]
|
[Authorize]
|
||||||
[Route("api/admin/v{version:apiVersion}/deliveries")]
|
[Route("api/admin/v{version:apiVersion}/deliveries")]
|
||||||
public sealed class DeliveriesController(IMediator mediator) : BaseApiController
|
public sealed class DeliveriesController(IMediator mediator) : BaseApiController
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 创建配送单。
|
/// 创建配送单。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -34,7 +29,10 @@ public sealed class DeliveriesController(IMediator mediator) : BaseApiController
|
|||||||
[ProducesResponseType(typeof(ApiResponse<DeliveryOrderDto>), StatusCodes.Status200OK)]
|
[ProducesResponseType(typeof(ApiResponse<DeliveryOrderDto>), StatusCodes.Status200OK)]
|
||||||
public async Task<ApiResponse<DeliveryOrderDto>> Create([FromBody] CreateDeliveryOrderCommand command, CancellationToken cancellationToken)
|
public async Task<ApiResponse<DeliveryOrderDto>> Create([FromBody] CreateDeliveryOrderCommand command, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 创建配送单
|
||||||
var result = await mediator.Send(command, cancellationToken);
|
var result = await mediator.Send(command, cancellationToken);
|
||||||
|
|
||||||
|
// 2. 返回创建结果
|
||||||
return ApiResponse<DeliveryOrderDto>.Ok(result);
|
return ApiResponse<DeliveryOrderDto>.Ok(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,6 +51,7 @@ public sealed class DeliveriesController(IMediator mediator) : BaseApiController
|
|||||||
[FromQuery] bool sortDesc = true,
|
[FromQuery] bool sortDesc = true,
|
||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
|
// 1. 组装查询参数
|
||||||
var result = await mediator.Send(new SearchDeliveryOrdersQuery
|
var result = await mediator.Send(new SearchDeliveryOrdersQuery
|
||||||
{
|
{
|
||||||
OrderId = orderId,
|
OrderId = orderId,
|
||||||
@@ -63,6 +62,7 @@ public sealed class DeliveriesController(IMediator mediator) : BaseApiController
|
|||||||
SortDescending = sortDesc
|
SortDescending = sortDesc
|
||||||
}, cancellationToken);
|
}, cancellationToken);
|
||||||
|
|
||||||
|
// 2. 返回分页结果
|
||||||
return ApiResponse<PagedResult<DeliveryOrderDto>>.Ok(result);
|
return ApiResponse<PagedResult<DeliveryOrderDto>>.Ok(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,7 +75,10 @@ public sealed class DeliveriesController(IMediator mediator) : BaseApiController
|
|||||||
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status404NotFound)]
|
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status404NotFound)]
|
||||||
public async Task<ApiResponse<DeliveryOrderDto>> Detail(long deliveryOrderId, CancellationToken cancellationToken)
|
public async Task<ApiResponse<DeliveryOrderDto>> Detail(long deliveryOrderId, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 查询配送单详情
|
||||||
var result = await mediator.Send(new GetDeliveryOrderByIdQuery { DeliveryOrderId = deliveryOrderId }, cancellationToken);
|
var result = await mediator.Send(new GetDeliveryOrderByIdQuery { DeliveryOrderId = deliveryOrderId }, cancellationToken);
|
||||||
|
|
||||||
|
// 2. 返回详情或 404
|
||||||
return result == null
|
return result == null
|
||||||
? ApiResponse<DeliveryOrderDto>.Error(ErrorCodes.NotFound, "配送单不存在")
|
? ApiResponse<DeliveryOrderDto>.Error(ErrorCodes.NotFound, "配送单不存在")
|
||||||
: ApiResponse<DeliveryOrderDto>.Ok(result);
|
: ApiResponse<DeliveryOrderDto>.Ok(result);
|
||||||
@@ -90,11 +93,16 @@ public sealed class DeliveriesController(IMediator mediator) : BaseApiController
|
|||||||
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status404NotFound)]
|
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status404NotFound)]
|
||||||
public async Task<ApiResponse<DeliveryOrderDto>> Update(long deliveryOrderId, [FromBody] UpdateDeliveryOrderCommand command, CancellationToken cancellationToken)
|
public async Task<ApiResponse<DeliveryOrderDto>> Update(long deliveryOrderId, [FromBody] UpdateDeliveryOrderCommand command, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 确保命令携带配送单标识
|
||||||
if (command.DeliveryOrderId == 0)
|
if (command.DeliveryOrderId == 0)
|
||||||
{
|
{
|
||||||
command = command with { DeliveryOrderId = deliveryOrderId };
|
command = command with { DeliveryOrderId = deliveryOrderId };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 2. 执行更新
|
||||||
var result = await mediator.Send(command, cancellationToken);
|
var result = await mediator.Send(command, cancellationToken);
|
||||||
|
|
||||||
|
// 3. 返回更新结果或 404
|
||||||
return result == null
|
return result == null
|
||||||
? ApiResponse<DeliveryOrderDto>.Error(ErrorCodes.NotFound, "配送单不存在")
|
? ApiResponse<DeliveryOrderDto>.Error(ErrorCodes.NotFound, "配送单不存在")
|
||||||
: ApiResponse<DeliveryOrderDto>.Ok(result);
|
: ApiResponse<DeliveryOrderDto>.Ok(result);
|
||||||
@@ -109,7 +117,10 @@ public sealed class DeliveriesController(IMediator mediator) : BaseApiController
|
|||||||
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status404NotFound)]
|
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status404NotFound)]
|
||||||
public async Task<ApiResponse<object>> Delete(long deliveryOrderId, CancellationToken cancellationToken)
|
public async Task<ApiResponse<object>> Delete(long deliveryOrderId, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 执行删除
|
||||||
var success = await mediator.Send(new DeleteDeliveryOrderCommand { DeliveryOrderId = deliveryOrderId }, cancellationToken);
|
var success = await mediator.Send(new DeleteDeliveryOrderCommand { DeliveryOrderId = deliveryOrderId }, cancellationToken);
|
||||||
|
|
||||||
|
// 2. 返回结果或 404
|
||||||
return success
|
return success
|
||||||
? ApiResponse<object>.Ok(null)
|
? ApiResponse<object>.Ok(null)
|
||||||
: ApiResponse<object>.Error(ErrorCodes.NotFound, "配送单不存在");
|
: ApiResponse<object>.Error(ErrorCodes.NotFound, "配送单不存在");
|
||||||
|
|||||||
@@ -12,17 +12,12 @@ namespace TakeoutSaaS.AdminApi.Controllers;
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 参数字典管理。
|
/// 参数字典管理。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
|
||||||
/// 初始化字典控制器。
|
|
||||||
/// </remarks>
|
|
||||||
/// <param name="dictionaryAppService">字典服务</param>
|
/// <param name="dictionaryAppService">字典服务</param>
|
||||||
[ApiVersion("1.0")]
|
[ApiVersion("1.0")]
|
||||||
[Authorize]
|
[Authorize]
|
||||||
[Route("api/admin/v{version:apiVersion}/dictionaries")]
|
[Route("api/admin/v{version:apiVersion}/dictionaries")]
|
||||||
public sealed class DictionaryController(IDictionaryAppService dictionaryAppService) : BaseApiController
|
public sealed class DictionaryController(IDictionaryAppService dictionaryAppService) : BaseApiController
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查询字典分组。
|
/// 查询字典分组。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -31,7 +26,10 @@ public sealed class DictionaryController(IDictionaryAppService dictionaryAppServ
|
|||||||
[ProducesResponseType(typeof(ApiResponse<IReadOnlyList<DictionaryGroupDto>>), StatusCodes.Status200OK)]
|
[ProducesResponseType(typeof(ApiResponse<IReadOnlyList<DictionaryGroupDto>>), StatusCodes.Status200OK)]
|
||||||
public async Task<ApiResponse<IReadOnlyList<DictionaryGroupDto>>> GetGroups([FromQuery] DictionaryGroupQuery query, CancellationToken cancellationToken)
|
public async Task<ApiResponse<IReadOnlyList<DictionaryGroupDto>>> GetGroups([FromQuery] DictionaryGroupQuery query, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 查询字典分组
|
||||||
var groups = await dictionaryAppService.SearchGroupsAsync(query, cancellationToken);
|
var groups = await dictionaryAppService.SearchGroupsAsync(query, cancellationToken);
|
||||||
|
|
||||||
|
// 2. 返回分组列表
|
||||||
return ApiResponse<IReadOnlyList<DictionaryGroupDto>>.Ok(groups);
|
return ApiResponse<IReadOnlyList<DictionaryGroupDto>>.Ok(groups);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,7 +41,10 @@ public sealed class DictionaryController(IDictionaryAppService dictionaryAppServ
|
|||||||
[ProducesResponseType(typeof(ApiResponse<DictionaryGroupDto>), StatusCodes.Status200OK)]
|
[ProducesResponseType(typeof(ApiResponse<DictionaryGroupDto>), StatusCodes.Status200OK)]
|
||||||
public async Task<ApiResponse<DictionaryGroupDto>> CreateGroup([FromBody] CreateDictionaryGroupRequest request, CancellationToken cancellationToken)
|
public async Task<ApiResponse<DictionaryGroupDto>> CreateGroup([FromBody] CreateDictionaryGroupRequest request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 创建字典分组
|
||||||
var group = await dictionaryAppService.CreateGroupAsync(request, cancellationToken);
|
var group = await dictionaryAppService.CreateGroupAsync(request, cancellationToken);
|
||||||
|
|
||||||
|
// 2. 返回创建结果
|
||||||
return ApiResponse<DictionaryGroupDto>.Ok(group);
|
return ApiResponse<DictionaryGroupDto>.Ok(group);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,7 +56,10 @@ public sealed class DictionaryController(IDictionaryAppService dictionaryAppServ
|
|||||||
[ProducesResponseType(typeof(ApiResponse<DictionaryGroupDto>), StatusCodes.Status200OK)]
|
[ProducesResponseType(typeof(ApiResponse<DictionaryGroupDto>), StatusCodes.Status200OK)]
|
||||||
public async Task<ApiResponse<DictionaryGroupDto>> UpdateGroup(long groupId, [FromBody] UpdateDictionaryGroupRequest request, CancellationToken cancellationToken)
|
public async Task<ApiResponse<DictionaryGroupDto>> UpdateGroup(long groupId, [FromBody] UpdateDictionaryGroupRequest request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 更新字典分组
|
||||||
var group = await dictionaryAppService.UpdateGroupAsync(groupId, request, cancellationToken);
|
var group = await dictionaryAppService.UpdateGroupAsync(groupId, request, cancellationToken);
|
||||||
|
|
||||||
|
// 2. 返回更新结果
|
||||||
return ApiResponse<DictionaryGroupDto>.Ok(group);
|
return ApiResponse<DictionaryGroupDto>.Ok(group);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,7 +71,10 @@ public sealed class DictionaryController(IDictionaryAppService dictionaryAppServ
|
|||||||
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status200OK)]
|
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status200OK)]
|
||||||
public async Task<ApiResponse<object>> DeleteGroup(long groupId, CancellationToken cancellationToken)
|
public async Task<ApiResponse<object>> DeleteGroup(long groupId, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 删除字典分组
|
||||||
await dictionaryAppService.DeleteGroupAsync(groupId, cancellationToken);
|
await dictionaryAppService.DeleteGroupAsync(groupId, cancellationToken);
|
||||||
|
|
||||||
|
// 2. 返回成功响应
|
||||||
return ApiResponse.Success();
|
return ApiResponse.Success();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,7 +86,10 @@ public sealed class DictionaryController(IDictionaryAppService dictionaryAppServ
|
|||||||
[ProducesResponseType(typeof(ApiResponse<DictionaryItemDto>), StatusCodes.Status200OK)]
|
[ProducesResponseType(typeof(ApiResponse<DictionaryItemDto>), StatusCodes.Status200OK)]
|
||||||
public async Task<ApiResponse<DictionaryItemDto>> CreateItem(long groupId, [FromBody] CreateDictionaryItemRequest request, CancellationToken cancellationToken)
|
public async Task<ApiResponse<DictionaryItemDto>> CreateItem(long groupId, [FromBody] CreateDictionaryItemRequest request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 绑定分组标识
|
||||||
request.GroupId = groupId;
|
request.GroupId = groupId;
|
||||||
|
|
||||||
|
// 2. 创建字典项
|
||||||
var item = await dictionaryAppService.CreateItemAsync(request, cancellationToken);
|
var item = await dictionaryAppService.CreateItemAsync(request, cancellationToken);
|
||||||
return ApiResponse<DictionaryItemDto>.Ok(item);
|
return ApiResponse<DictionaryItemDto>.Ok(item);
|
||||||
}
|
}
|
||||||
@@ -92,7 +102,10 @@ public sealed class DictionaryController(IDictionaryAppService dictionaryAppServ
|
|||||||
[ProducesResponseType(typeof(ApiResponse<DictionaryItemDto>), StatusCodes.Status200OK)]
|
[ProducesResponseType(typeof(ApiResponse<DictionaryItemDto>), StatusCodes.Status200OK)]
|
||||||
public async Task<ApiResponse<DictionaryItemDto>> UpdateItem(long itemId, [FromBody] UpdateDictionaryItemRequest request, CancellationToken cancellationToken)
|
public async Task<ApiResponse<DictionaryItemDto>> UpdateItem(long itemId, [FromBody] UpdateDictionaryItemRequest request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 更新字典项
|
||||||
var item = await dictionaryAppService.UpdateItemAsync(itemId, request, cancellationToken);
|
var item = await dictionaryAppService.UpdateItemAsync(itemId, request, cancellationToken);
|
||||||
|
|
||||||
|
// 2. 返回更新结果
|
||||||
return ApiResponse<DictionaryItemDto>.Ok(item);
|
return ApiResponse<DictionaryItemDto>.Ok(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,7 +117,10 @@ public sealed class DictionaryController(IDictionaryAppService dictionaryAppServ
|
|||||||
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status200OK)]
|
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status200OK)]
|
||||||
public async Task<ApiResponse<object>> DeleteItem(long itemId, CancellationToken cancellationToken)
|
public async Task<ApiResponse<object>> DeleteItem(long itemId, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 删除字典项
|
||||||
await dictionaryAppService.DeleteItemAsync(itemId, cancellationToken);
|
await dictionaryAppService.DeleteItemAsync(itemId, cancellationToken);
|
||||||
|
|
||||||
|
// 2. 返回成功响应
|
||||||
return ApiResponse.Success();
|
return ApiResponse.Success();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,7 +131,10 @@ public sealed class DictionaryController(IDictionaryAppService dictionaryAppServ
|
|||||||
[ProducesResponseType(typeof(ApiResponse<IReadOnlyDictionary<string, IReadOnlyList<DictionaryItemDto>>>), StatusCodes.Status200OK)]
|
[ProducesResponseType(typeof(ApiResponse<IReadOnlyDictionary<string, IReadOnlyList<DictionaryItemDto>>>), StatusCodes.Status200OK)]
|
||||||
public async Task<ApiResponse<IReadOnlyDictionary<string, IReadOnlyList<DictionaryItemDto>>>> BatchGet([FromBody] DictionaryBatchQueryRequest request, CancellationToken cancellationToken)
|
public async Task<ApiResponse<IReadOnlyDictionary<string, IReadOnlyList<DictionaryItemDto>>>> BatchGet([FromBody] DictionaryBatchQueryRequest request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 批量读取并命中缓存
|
||||||
var dictionaries = await dictionaryAppService.GetCachedItemsAsync(request, cancellationToken);
|
var dictionaries = await dictionaryAppService.GetCachedItemsAsync(request, cancellationToken);
|
||||||
|
|
||||||
|
// 2. 返回批量结果
|
||||||
return ApiResponse<IReadOnlyDictionary<string, IReadOnlyList<DictionaryItemDto>>>.Ok(dictionaries);
|
return ApiResponse<IReadOnlyDictionary<string, IReadOnlyList<DictionaryItemDto>>>.Ok(dictionaries);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,8 +19,6 @@ namespace TakeoutSaaS.AdminApi.Controllers;
|
|||||||
[Route("api/admin/v{version:apiVersion}/files")]
|
[Route("api/admin/v{version:apiVersion}/files")]
|
||||||
public sealed class FilesController(IFileStorageService fileStorageService) : BaseApiController
|
public sealed class FilesController(IFileStorageService fileStorageService) : BaseApiController
|
||||||
{
|
{
|
||||||
private readonly IFileStorageService _fileStorageService = fileStorageService;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 上传图片或文件。
|
/// 上传图片或文件。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -30,23 +28,28 @@ public sealed class FilesController(IFileStorageService fileStorageService) : Ba
|
|||||||
[ProducesResponseType(typeof(ApiResponse<FileUploadResponse>), StatusCodes.Status400BadRequest)]
|
[ProducesResponseType(typeof(ApiResponse<FileUploadResponse>), StatusCodes.Status400BadRequest)]
|
||||||
public async Task<ApiResponse<FileUploadResponse>> Upload([FromForm] IFormFile? file, [FromForm] string? type, CancellationToken cancellationToken)
|
public async Task<ApiResponse<FileUploadResponse>> Upload([FromForm] IFormFile? file, [FromForm] string? type, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 校验文件有效性
|
||||||
if (file == null || file.Length == 0)
|
if (file == null || file.Length == 0)
|
||||||
{
|
{
|
||||||
return ApiResponse<FileUploadResponse>.Error(ErrorCodes.BadRequest, "文件不能为空");
|
return ApiResponse<FileUploadResponse>.Error(ErrorCodes.BadRequest, "文件不能为空");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 2. 解析上传类型
|
||||||
if (!UploadFileTypeParser.TryParse(type, out var uploadType))
|
if (!UploadFileTypeParser.TryParse(type, out var uploadType))
|
||||||
{
|
{
|
||||||
return ApiResponse<FileUploadResponse>.Error(ErrorCodes.BadRequest, "上传类型不合法");
|
return ApiResponse<FileUploadResponse>.Error(ErrorCodes.BadRequest, "上传类型不合法");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 3. 提取请求来源
|
||||||
var origin = Request.Headers["Origin"].FirstOrDefault() ?? Request.Headers["Referer"].FirstOrDefault();
|
var origin = Request.Headers["Origin"].FirstOrDefault() ?? Request.Headers["Referer"].FirstOrDefault();
|
||||||
await using var stream = file.OpenReadStream();
|
await using var stream = file.OpenReadStream();
|
||||||
|
|
||||||
var result = await _fileStorageService.UploadAsync(
|
// 4. 调用存储服务执行上传
|
||||||
|
var result = await fileStorageService.UploadAsync(
|
||||||
new UploadFileRequest(uploadType, stream, file.FileName, file.ContentType ?? string.Empty, file.Length, origin),
|
new UploadFileRequest(uploadType, stream, file.FileName, file.ContentType ?? string.Empty, file.Length, origin),
|
||||||
cancellationToken);
|
cancellationToken);
|
||||||
|
|
||||||
|
// 5. 返回上传结果
|
||||||
return ApiResponse<FileUploadResponse>.Ok(result);
|
return ApiResponse<FileUploadResponse>.Ok(result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,10 @@ public class HealthController : BaseApiController
|
|||||||
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status200OK)]
|
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status200OK)]
|
||||||
public ApiResponse<object> Get()
|
public ApiResponse<object> Get()
|
||||||
{
|
{
|
||||||
|
// 1. 构造健康状态
|
||||||
var payload = new { status = "OK", service = "AdminApi", time = DateTime.UtcNow };
|
var payload = new { status = "OK", service = "AdminApi", time = DateTime.UtcNow };
|
||||||
|
|
||||||
|
// 2. 返回健康响应
|
||||||
return ApiResponse<object>.Ok(payload);
|
return ApiResponse<object>.Ok(payload);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,10 @@ public sealed class MerchantCategoriesController(IMediator mediator) : BaseApiCo
|
|||||||
[ProducesResponseType(typeof(ApiResponse<IReadOnlyList<MerchantCategoryDto>>), StatusCodes.Status200OK)]
|
[ProducesResponseType(typeof(ApiResponse<IReadOnlyList<MerchantCategoryDto>>), StatusCodes.Status200OK)]
|
||||||
public async Task<ApiResponse<IReadOnlyList<MerchantCategoryDto>>> List(CancellationToken cancellationToken)
|
public async Task<ApiResponse<IReadOnlyList<MerchantCategoryDto>>> List(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 查询所有类目
|
||||||
var result = await mediator.Send(new ListMerchantCategoriesQuery(), cancellationToken);
|
var result = await mediator.Send(new ListMerchantCategoriesQuery(), cancellationToken);
|
||||||
|
|
||||||
|
// 2. 返回类目列表
|
||||||
return ApiResponse<IReadOnlyList<MerchantCategoryDto>>.Ok(result);
|
return ApiResponse<IReadOnlyList<MerchantCategoryDto>>.Ok(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,7 +44,10 @@ public sealed class MerchantCategoriesController(IMediator mediator) : BaseApiCo
|
|||||||
[ProducesResponseType(typeof(ApiResponse<MerchantCategoryDto>), StatusCodes.Status200OK)]
|
[ProducesResponseType(typeof(ApiResponse<MerchantCategoryDto>), StatusCodes.Status200OK)]
|
||||||
public async Task<ApiResponse<MerchantCategoryDto>> Create([FromBody] CreateMerchantCategoryCommand command, CancellationToken cancellationToken)
|
public async Task<ApiResponse<MerchantCategoryDto>> Create([FromBody] CreateMerchantCategoryCommand command, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 创建类目
|
||||||
var result = await mediator.Send(command, cancellationToken);
|
var result = await mediator.Send(command, cancellationToken);
|
||||||
|
|
||||||
|
// 2. 返回创建结果
|
||||||
return ApiResponse<MerchantCategoryDto>.Ok(result);
|
return ApiResponse<MerchantCategoryDto>.Ok(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,7 +60,10 @@ public sealed class MerchantCategoriesController(IMediator mediator) : BaseApiCo
|
|||||||
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status404NotFound)]
|
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status404NotFound)]
|
||||||
public async Task<ApiResponse<object>> Delete(long categoryId, CancellationToken cancellationToken)
|
public async Task<ApiResponse<object>> Delete(long categoryId, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 执行删除
|
||||||
var success = await mediator.Send(new DeleteMerchantCategoryCommand(categoryId), cancellationToken);
|
var success = await mediator.Send(new DeleteMerchantCategoryCommand(categoryId), cancellationToken);
|
||||||
|
|
||||||
|
// 2. 返回删除结果或 404
|
||||||
return success
|
return success
|
||||||
? ApiResponse<object>.Ok(null)
|
? ApiResponse<object>.Ok(null)
|
||||||
: ApiResponse<object>.Error(ErrorCodes.NotFound, "类目不存在");
|
: ApiResponse<object>.Error(ErrorCodes.NotFound, "类目不存在");
|
||||||
@@ -68,7 +77,10 @@ public sealed class MerchantCategoriesController(IMediator mediator) : BaseApiCo
|
|||||||
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status200OK)]
|
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status200OK)]
|
||||||
public async Task<ApiResponse<object>> Reorder([FromBody] ReorderMerchantCategoriesCommand command, CancellationToken cancellationToken)
|
public async Task<ApiResponse<object>> Reorder([FromBody] ReorderMerchantCategoriesCommand command, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 执行排序调整
|
||||||
await mediator.Send(command, cancellationToken);
|
await mediator.Send(command, cancellationToken);
|
||||||
|
|
||||||
|
// 2. 返回成功结果
|
||||||
return ApiResponse<object>.Ok(null);
|
return ApiResponse<object>.Ok(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,16 +16,11 @@ namespace TakeoutSaaS.AdminApi.Controllers;
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 商户管理。
|
/// 商户管理。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
|
||||||
/// 初始化控制器。
|
|
||||||
/// </remarks>
|
|
||||||
[ApiVersion("1.0")]
|
[ApiVersion("1.0")]
|
||||||
[Authorize]
|
[Authorize]
|
||||||
[Route("api/admin/v{version:apiVersion}/merchants")]
|
[Route("api/admin/v{version:apiVersion}/merchants")]
|
||||||
public sealed class MerchantsController(IMediator mediator) : BaseApiController
|
public sealed class MerchantsController(IMediator mediator) : BaseApiController
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 创建商户。
|
/// 创建商户。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -34,7 +29,10 @@ public sealed class MerchantsController(IMediator mediator) : BaseApiController
|
|||||||
[ProducesResponseType(typeof(ApiResponse<MerchantDto>), StatusCodes.Status200OK)]
|
[ProducesResponseType(typeof(ApiResponse<MerchantDto>), StatusCodes.Status200OK)]
|
||||||
public async Task<ApiResponse<MerchantDto>> Create([FromBody] CreateMerchantCommand command, CancellationToken cancellationToken)
|
public async Task<ApiResponse<MerchantDto>> Create([FromBody] CreateMerchantCommand command, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 创建商户
|
||||||
var result = await mediator.Send(command, cancellationToken);
|
var result = await mediator.Send(command, cancellationToken);
|
||||||
|
|
||||||
|
// 2. 返回创建结果
|
||||||
return ApiResponse<MerchantDto>.Ok(result);
|
return ApiResponse<MerchantDto>.Ok(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,6 +50,7 @@ public sealed class MerchantsController(IMediator mediator) : BaseApiController
|
|||||||
[FromQuery] bool sortDesc = true,
|
[FromQuery] bool sortDesc = true,
|
||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
|
// 1. 组装查询参数并执行查询
|
||||||
var result = await mediator.Send(new SearchMerchantsQuery
|
var result = await mediator.Send(new SearchMerchantsQuery
|
||||||
{
|
{
|
||||||
Status = status,
|
Status = status,
|
||||||
@@ -60,6 +59,8 @@ public sealed class MerchantsController(IMediator mediator) : BaseApiController
|
|||||||
SortBy = sortBy,
|
SortBy = sortBy,
|
||||||
SortDescending = sortDesc
|
SortDescending = sortDesc
|
||||||
}, cancellationToken);
|
}, cancellationToken);
|
||||||
|
|
||||||
|
// 2. 返回分页结果
|
||||||
return ApiResponse<PagedResult<MerchantDto>>.Ok(result);
|
return ApiResponse<PagedResult<MerchantDto>>.Ok(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,12 +73,16 @@ public sealed class MerchantsController(IMediator mediator) : BaseApiController
|
|||||||
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status404NotFound)]
|
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status404NotFound)]
|
||||||
public async Task<ApiResponse<MerchantDto>> Update(long merchantId, [FromBody] UpdateMerchantCommand command, CancellationToken cancellationToken)
|
public async Task<ApiResponse<MerchantDto>> Update(long merchantId, [FromBody] UpdateMerchantCommand command, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 绑定商户标识
|
||||||
if (command.MerchantId == 0)
|
if (command.MerchantId == 0)
|
||||||
{
|
{
|
||||||
command = command with { MerchantId = merchantId };
|
command = command with { MerchantId = merchantId };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 2. 执行更新
|
||||||
var result = await mediator.Send(command, cancellationToken);
|
var result = await mediator.Send(command, cancellationToken);
|
||||||
|
|
||||||
|
// 3. 返回更新结果或 404
|
||||||
return result == null
|
return result == null
|
||||||
? ApiResponse<MerchantDto>.Error(ErrorCodes.NotFound, "商户不存在")
|
? ApiResponse<MerchantDto>.Error(ErrorCodes.NotFound, "商户不存在")
|
||||||
: ApiResponse<MerchantDto>.Ok(result);
|
: ApiResponse<MerchantDto>.Ok(result);
|
||||||
@@ -92,7 +97,10 @@ public sealed class MerchantsController(IMediator mediator) : BaseApiController
|
|||||||
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status404NotFound)]
|
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status404NotFound)]
|
||||||
public async Task<ApiResponse<object>> Delete(long merchantId, CancellationToken cancellationToken)
|
public async Task<ApiResponse<object>> Delete(long merchantId, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 执行删除
|
||||||
var success = await mediator.Send(new DeleteMerchantCommand { MerchantId = merchantId }, cancellationToken);
|
var success = await mediator.Send(new DeleteMerchantCommand { MerchantId = merchantId }, cancellationToken);
|
||||||
|
|
||||||
|
// 2. 返回删除结果或 404
|
||||||
return success
|
return success
|
||||||
? ApiResponse<object>.Ok(null)
|
? ApiResponse<object>.Ok(null)
|
||||||
: ApiResponse<object>.Error(ErrorCodes.NotFound, "商户不存在");
|
: ApiResponse<object>.Error(ErrorCodes.NotFound, "商户不存在");
|
||||||
@@ -107,7 +115,10 @@ public sealed class MerchantsController(IMediator mediator) : BaseApiController
|
|||||||
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status404NotFound)]
|
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status404NotFound)]
|
||||||
public async Task<ApiResponse<MerchantDto>> Detail(long merchantId, CancellationToken cancellationToken)
|
public async Task<ApiResponse<MerchantDto>> Detail(long merchantId, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 查询商户概览
|
||||||
var result = await mediator.Send(new GetMerchantByIdQuery { MerchantId = merchantId }, cancellationToken);
|
var result = await mediator.Send(new GetMerchantByIdQuery { MerchantId = merchantId }, cancellationToken);
|
||||||
|
|
||||||
|
// 2. 返回结果或 404
|
||||||
return result == null
|
return result == null
|
||||||
? ApiResponse<MerchantDto>.Error(ErrorCodes.NotFound, "商户不存在")
|
? ApiResponse<MerchantDto>.Error(ErrorCodes.NotFound, "商户不存在")
|
||||||
: ApiResponse<MerchantDto>.Ok(result);
|
: ApiResponse<MerchantDto>.Ok(result);
|
||||||
@@ -121,7 +132,10 @@ public sealed class MerchantsController(IMediator mediator) : BaseApiController
|
|||||||
[ProducesResponseType(typeof(ApiResponse<MerchantDetailDto>), StatusCodes.Status200OK)]
|
[ProducesResponseType(typeof(ApiResponse<MerchantDetailDto>), StatusCodes.Status200OK)]
|
||||||
public async Task<ApiResponse<MerchantDetailDto>> FullDetail(long merchantId, CancellationToken cancellationToken)
|
public async Task<ApiResponse<MerchantDetailDto>> FullDetail(long merchantId, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 查询商户详细资料
|
||||||
var result = await mediator.Send(new GetMerchantDetailQuery(merchantId), cancellationToken);
|
var result = await mediator.Send(new GetMerchantDetailQuery(merchantId), cancellationToken);
|
||||||
|
|
||||||
|
// 2. 返回详情
|
||||||
return ApiResponse<MerchantDetailDto>.Ok(result);
|
return ApiResponse<MerchantDetailDto>.Ok(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,7 +150,10 @@ public sealed class MerchantsController(IMediator mediator) : BaseApiController
|
|||||||
[FromBody] AddMerchantDocumentCommand body,
|
[FromBody] AddMerchantDocumentCommand body,
|
||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 绑定商户标识
|
||||||
var command = body with { MerchantId = merchantId };
|
var command = body with { MerchantId = merchantId };
|
||||||
|
|
||||||
|
// 2. 创建证照记录
|
||||||
var result = await mediator.Send(command, cancellationToken);
|
var result = await mediator.Send(command, cancellationToken);
|
||||||
return ApiResponse<MerchantDocumentDto>.Ok(result);
|
return ApiResponse<MerchantDocumentDto>.Ok(result);
|
||||||
}
|
}
|
||||||
@@ -149,7 +166,10 @@ public sealed class MerchantsController(IMediator mediator) : BaseApiController
|
|||||||
[ProducesResponseType(typeof(ApiResponse<IReadOnlyList<MerchantDocumentDto>>), StatusCodes.Status200OK)]
|
[ProducesResponseType(typeof(ApiResponse<IReadOnlyList<MerchantDocumentDto>>), StatusCodes.Status200OK)]
|
||||||
public async Task<ApiResponse<IReadOnlyList<MerchantDocumentDto>>> Documents(long merchantId, CancellationToken cancellationToken)
|
public async Task<ApiResponse<IReadOnlyList<MerchantDocumentDto>>> Documents(long merchantId, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 查询证照列表
|
||||||
var result = await mediator.Send(new GetMerchantDocumentsQuery(merchantId), cancellationToken);
|
var result = await mediator.Send(new GetMerchantDocumentsQuery(merchantId), cancellationToken);
|
||||||
|
|
||||||
|
// 2. 返回证照集合
|
||||||
return ApiResponse<IReadOnlyList<MerchantDocumentDto>>.Ok(result);
|
return ApiResponse<IReadOnlyList<MerchantDocumentDto>>.Ok(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -165,7 +185,10 @@ public sealed class MerchantsController(IMediator mediator) : BaseApiController
|
|||||||
[FromBody] ReviewMerchantDocumentCommand body,
|
[FromBody] ReviewMerchantDocumentCommand body,
|
||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 绑定商户与证照标识
|
||||||
var command = body with { MerchantId = merchantId, DocumentId = documentId };
|
var command = body with { MerchantId = merchantId, DocumentId = documentId };
|
||||||
|
|
||||||
|
// 2. 执行审核
|
||||||
var result = await mediator.Send(command, cancellationToken);
|
var result = await mediator.Send(command, cancellationToken);
|
||||||
return ApiResponse<MerchantDocumentDto>.Ok(result);
|
return ApiResponse<MerchantDocumentDto>.Ok(result);
|
||||||
}
|
}
|
||||||
@@ -181,7 +204,10 @@ public sealed class MerchantsController(IMediator mediator) : BaseApiController
|
|||||||
[FromBody] CreateMerchantContractCommand body,
|
[FromBody] CreateMerchantContractCommand body,
|
||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 绑定商户标识
|
||||||
var command = body with { MerchantId = merchantId };
|
var command = body with { MerchantId = merchantId };
|
||||||
|
|
||||||
|
// 2. 创建合同
|
||||||
var result = await mediator.Send(command, cancellationToken);
|
var result = await mediator.Send(command, cancellationToken);
|
||||||
return ApiResponse<MerchantContractDto>.Ok(result);
|
return ApiResponse<MerchantContractDto>.Ok(result);
|
||||||
}
|
}
|
||||||
@@ -194,7 +220,10 @@ public sealed class MerchantsController(IMediator mediator) : BaseApiController
|
|||||||
[ProducesResponseType(typeof(ApiResponse<IReadOnlyList<MerchantContractDto>>), StatusCodes.Status200OK)]
|
[ProducesResponseType(typeof(ApiResponse<IReadOnlyList<MerchantContractDto>>), StatusCodes.Status200OK)]
|
||||||
public async Task<ApiResponse<IReadOnlyList<MerchantContractDto>>> Contracts(long merchantId, CancellationToken cancellationToken)
|
public async Task<ApiResponse<IReadOnlyList<MerchantContractDto>>> Contracts(long merchantId, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 查询合同列表
|
||||||
var result = await mediator.Send(new GetMerchantContractsQuery(merchantId), cancellationToken);
|
var result = await mediator.Send(new GetMerchantContractsQuery(merchantId), cancellationToken);
|
||||||
|
|
||||||
|
// 2. 返回合同集合
|
||||||
return ApiResponse<IReadOnlyList<MerchantContractDto>>.Ok(result);
|
return ApiResponse<IReadOnlyList<MerchantContractDto>>.Ok(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -210,7 +239,10 @@ public sealed class MerchantsController(IMediator mediator) : BaseApiController
|
|||||||
[FromBody] UpdateMerchantContractStatusCommand body,
|
[FromBody] UpdateMerchantContractStatusCommand body,
|
||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 绑定商户与合同标识
|
||||||
var command = body with { MerchantId = merchantId, ContractId = contractId };
|
var command = body with { MerchantId = merchantId, ContractId = contractId };
|
||||||
|
|
||||||
|
// 2. 更新合同状态
|
||||||
var result = await mediator.Send(command, cancellationToken);
|
var result = await mediator.Send(command, cancellationToken);
|
||||||
return ApiResponse<MerchantContractDto>.Ok(result);
|
return ApiResponse<MerchantContractDto>.Ok(result);
|
||||||
}
|
}
|
||||||
@@ -223,7 +255,10 @@ public sealed class MerchantsController(IMediator mediator) : BaseApiController
|
|||||||
[ProducesResponseType(typeof(ApiResponse<MerchantDto>), StatusCodes.Status200OK)]
|
[ProducesResponseType(typeof(ApiResponse<MerchantDto>), StatusCodes.Status200OK)]
|
||||||
public async Task<ApiResponse<MerchantDto>> Review(long merchantId, [FromBody] ReviewMerchantCommand body, CancellationToken cancellationToken)
|
public async Task<ApiResponse<MerchantDto>> Review(long merchantId, [FromBody] ReviewMerchantCommand body, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 绑定商户标识
|
||||||
var command = body with { MerchantId = merchantId };
|
var command = body with { MerchantId = merchantId };
|
||||||
|
|
||||||
|
// 2. 执行审核
|
||||||
var result = await mediator.Send(command, cancellationToken);
|
var result = await mediator.Send(command, cancellationToken);
|
||||||
return ApiResponse<MerchantDto>.Ok(result);
|
return ApiResponse<MerchantDto>.Ok(result);
|
||||||
}
|
}
|
||||||
@@ -240,7 +275,10 @@ public sealed class MerchantsController(IMediator mediator) : BaseApiController
|
|||||||
[FromQuery] int pageSize = 20,
|
[FromQuery] int pageSize = 20,
|
||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
|
// 1. 查询审核日志
|
||||||
var result = await mediator.Send(new GetMerchantAuditLogsQuery(merchantId, page, pageSize), cancellationToken);
|
var result = await mediator.Send(new GetMerchantAuditLogsQuery(merchantId, page, pageSize), cancellationToken);
|
||||||
|
|
||||||
|
// 2. 返回日志分页
|
||||||
return ApiResponse<PagedResult<MerchantAuditLogDto>>.Ok(result);
|
return ApiResponse<PagedResult<MerchantAuditLogDto>>.Ok(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -252,7 +290,10 @@ public sealed class MerchantsController(IMediator mediator) : BaseApiController
|
|||||||
[ProducesResponseType(typeof(ApiResponse<IReadOnlyList<string>>), StatusCodes.Status200OK)]
|
[ProducesResponseType(typeof(ApiResponse<IReadOnlyList<string>>), StatusCodes.Status200OK)]
|
||||||
public async Task<ApiResponse<IReadOnlyList<string>>> Categories(CancellationToken cancellationToken)
|
public async Task<ApiResponse<IReadOnlyList<string>>> Categories(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 查询可选类目
|
||||||
var result = await mediator.Send(new GetMerchantCategoriesQuery(), cancellationToken);
|
var result = await mediator.Send(new GetMerchantCategoriesQuery(), cancellationToken);
|
||||||
|
|
||||||
|
// 2. 返回类目列表
|
||||||
return ApiResponse<IReadOnlyList<string>>.Ok(result);
|
return ApiResponse<IReadOnlyList<string>>.Ok(result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,16 +17,11 @@ namespace TakeoutSaaS.AdminApi.Controllers;
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 订单管理。
|
/// 订单管理。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
|
||||||
/// 初始化控制器。
|
|
||||||
/// </remarks>
|
|
||||||
[ApiVersion("1.0")]
|
[ApiVersion("1.0")]
|
||||||
[Authorize]
|
[Authorize]
|
||||||
[Route("api/admin/v{version:apiVersion}/orders")]
|
[Route("api/admin/v{version:apiVersion}/orders")]
|
||||||
public sealed class OrdersController(IMediator mediator) : BaseApiController
|
public sealed class OrdersController(IMediator mediator) : BaseApiController
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 创建订单。
|
/// 创建订单。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -35,7 +30,10 @@ public sealed class OrdersController(IMediator mediator) : BaseApiController
|
|||||||
[ProducesResponseType(typeof(ApiResponse<OrderDto>), StatusCodes.Status200OK)]
|
[ProducesResponseType(typeof(ApiResponse<OrderDto>), StatusCodes.Status200OK)]
|
||||||
public async Task<ApiResponse<OrderDto>> Create([FromBody] CreateOrderCommand command, CancellationToken cancellationToken)
|
public async Task<ApiResponse<OrderDto>> Create([FromBody] CreateOrderCommand command, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 创建订单
|
||||||
var result = await mediator.Send(command, cancellationToken);
|
var result = await mediator.Send(command, cancellationToken);
|
||||||
|
|
||||||
|
// 2. 返回创建结果
|
||||||
return ApiResponse<OrderDto>.Ok(result);
|
return ApiResponse<OrderDto>.Ok(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,6 +54,7 @@ public sealed class OrdersController(IMediator mediator) : BaseApiController
|
|||||||
[FromQuery] bool sortDesc = true,
|
[FromQuery] bool sortDesc = true,
|
||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
|
// 1. 组装查询参数并执行查询
|
||||||
var result = await mediator.Send(new SearchOrdersQuery
|
var result = await mediator.Send(new SearchOrdersQuery
|
||||||
{
|
{
|
||||||
StoreId = storeId,
|
StoreId = storeId,
|
||||||
@@ -68,6 +67,7 @@ public sealed class OrdersController(IMediator mediator) : BaseApiController
|
|||||||
SortDescending = sortDesc
|
SortDescending = sortDesc
|
||||||
}, cancellationToken);
|
}, cancellationToken);
|
||||||
|
|
||||||
|
// 2. 返回分页结果
|
||||||
return ApiResponse<PagedResult<OrderDto>>.Ok(result);
|
return ApiResponse<PagedResult<OrderDto>>.Ok(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,7 +80,10 @@ public sealed class OrdersController(IMediator mediator) : BaseApiController
|
|||||||
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status404NotFound)]
|
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status404NotFound)]
|
||||||
public async Task<ApiResponse<OrderDto>> Detail(long orderId, CancellationToken cancellationToken)
|
public async Task<ApiResponse<OrderDto>> Detail(long orderId, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 查询订单详情
|
||||||
var result = await mediator.Send(new GetOrderByIdQuery { OrderId = orderId }, cancellationToken);
|
var result = await mediator.Send(new GetOrderByIdQuery { OrderId = orderId }, cancellationToken);
|
||||||
|
|
||||||
|
// 2. 返回详情或 404
|
||||||
return result == null
|
return result == null
|
||||||
? ApiResponse<OrderDto>.Error(ErrorCodes.NotFound, "订单不存在")
|
? ApiResponse<OrderDto>.Error(ErrorCodes.NotFound, "订单不存在")
|
||||||
: ApiResponse<OrderDto>.Ok(result);
|
: ApiResponse<OrderDto>.Ok(result);
|
||||||
@@ -95,11 +98,16 @@ public sealed class OrdersController(IMediator mediator) : BaseApiController
|
|||||||
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status404NotFound)]
|
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status404NotFound)]
|
||||||
public async Task<ApiResponse<OrderDto>> Update(long orderId, [FromBody] UpdateOrderCommand command, CancellationToken cancellationToken)
|
public async Task<ApiResponse<OrderDto>> Update(long orderId, [FromBody] UpdateOrderCommand command, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 确保命令包含订单标识
|
||||||
if (command.OrderId == 0)
|
if (command.OrderId == 0)
|
||||||
{
|
{
|
||||||
command = command with { OrderId = orderId };
|
command = command with { OrderId = orderId };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 2. 执行更新
|
||||||
var result = await mediator.Send(command, cancellationToken);
|
var result = await mediator.Send(command, cancellationToken);
|
||||||
|
|
||||||
|
// 3. 返回更新结果或 404
|
||||||
return result == null
|
return result == null
|
||||||
? ApiResponse<OrderDto>.Error(ErrorCodes.NotFound, "订单不存在")
|
? ApiResponse<OrderDto>.Error(ErrorCodes.NotFound, "订单不存在")
|
||||||
: ApiResponse<OrderDto>.Ok(result);
|
: ApiResponse<OrderDto>.Ok(result);
|
||||||
@@ -114,7 +122,10 @@ public sealed class OrdersController(IMediator mediator) : BaseApiController
|
|||||||
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status404NotFound)]
|
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status404NotFound)]
|
||||||
public async Task<ApiResponse<object>> Delete(long orderId, CancellationToken cancellationToken)
|
public async Task<ApiResponse<object>> Delete(long orderId, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 执行删除
|
||||||
var success = await mediator.Send(new DeleteOrderCommand { OrderId = orderId }, cancellationToken);
|
var success = await mediator.Send(new DeleteOrderCommand { OrderId = orderId }, cancellationToken);
|
||||||
|
|
||||||
|
// 2. 返回结果或 404
|
||||||
return success
|
return success
|
||||||
? ApiResponse<object>.Ok(null)
|
? ApiResponse<object>.Ok(null)
|
||||||
: ApiResponse<object>.Error(ErrorCodes.NotFound, "订单不存在");
|
: ApiResponse<object>.Error(ErrorCodes.NotFound, "订单不存在");
|
||||||
|
|||||||
@@ -16,16 +16,11 @@ namespace TakeoutSaaS.AdminApi.Controllers;
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 支付记录管理。
|
/// 支付记录管理。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
|
||||||
/// 初始化控制器。
|
|
||||||
/// </remarks>
|
|
||||||
[ApiVersion("1.0")]
|
[ApiVersion("1.0")]
|
||||||
[Authorize]
|
[Authorize]
|
||||||
[Route("api/admin/v{version:apiVersion}/payments")]
|
[Route("api/admin/v{version:apiVersion}/payments")]
|
||||||
public sealed class PaymentsController(IMediator mediator) : BaseApiController
|
public sealed class PaymentsController(IMediator mediator) : BaseApiController
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 创建支付记录。
|
/// 创建支付记录。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -34,7 +29,10 @@ public sealed class PaymentsController(IMediator mediator) : BaseApiController
|
|||||||
[ProducesResponseType(typeof(ApiResponse<PaymentDto>), StatusCodes.Status200OK)]
|
[ProducesResponseType(typeof(ApiResponse<PaymentDto>), StatusCodes.Status200OK)]
|
||||||
public async Task<ApiResponse<PaymentDto>> Create([FromBody] CreatePaymentCommand command, CancellationToken cancellationToken)
|
public async Task<ApiResponse<PaymentDto>> Create([FromBody] CreatePaymentCommand command, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 创建支付记录
|
||||||
var result = await mediator.Send(command, cancellationToken);
|
var result = await mediator.Send(command, cancellationToken);
|
||||||
|
|
||||||
|
// 2. 返回创建结果
|
||||||
return ApiResponse<PaymentDto>.Ok(result);
|
return ApiResponse<PaymentDto>.Ok(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,6 +51,7 @@ public sealed class PaymentsController(IMediator mediator) : BaseApiController
|
|||||||
[FromQuery] bool sortDesc = true,
|
[FromQuery] bool sortDesc = true,
|
||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
|
// 1. 组装查询参数并执行查询
|
||||||
var result = await mediator.Send(new SearchPaymentsQuery
|
var result = await mediator.Send(new SearchPaymentsQuery
|
||||||
{
|
{
|
||||||
OrderId = orderId,
|
OrderId = orderId,
|
||||||
@@ -63,6 +62,7 @@ public sealed class PaymentsController(IMediator mediator) : BaseApiController
|
|||||||
SortDescending = sortDesc
|
SortDescending = sortDesc
|
||||||
}, cancellationToken);
|
}, cancellationToken);
|
||||||
|
|
||||||
|
// 2. 返回分页结果
|
||||||
return ApiResponse<PagedResult<PaymentDto>>.Ok(result);
|
return ApiResponse<PagedResult<PaymentDto>>.Ok(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,7 +75,10 @@ public sealed class PaymentsController(IMediator mediator) : BaseApiController
|
|||||||
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status404NotFound)]
|
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status404NotFound)]
|
||||||
public async Task<ApiResponse<PaymentDto>> Detail(long paymentId, CancellationToken cancellationToken)
|
public async Task<ApiResponse<PaymentDto>> Detail(long paymentId, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 查询支付记录详情
|
||||||
var result = await mediator.Send(new GetPaymentByIdQuery { PaymentId = paymentId }, cancellationToken);
|
var result = await mediator.Send(new GetPaymentByIdQuery { PaymentId = paymentId }, cancellationToken);
|
||||||
|
|
||||||
|
// 2. 返回详情或 404
|
||||||
return result == null
|
return result == null
|
||||||
? ApiResponse<PaymentDto>.Error(ErrorCodes.NotFound, "支付记录不存在")
|
? ApiResponse<PaymentDto>.Error(ErrorCodes.NotFound, "支付记录不存在")
|
||||||
: ApiResponse<PaymentDto>.Ok(result);
|
: ApiResponse<PaymentDto>.Ok(result);
|
||||||
@@ -90,11 +93,16 @@ public sealed class PaymentsController(IMediator mediator) : BaseApiController
|
|||||||
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status404NotFound)]
|
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status404NotFound)]
|
||||||
public async Task<ApiResponse<PaymentDto>> Update(long paymentId, [FromBody] UpdatePaymentCommand command, CancellationToken cancellationToken)
|
public async Task<ApiResponse<PaymentDto>> Update(long paymentId, [FromBody] UpdatePaymentCommand command, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 确保命令包含支付记录标识
|
||||||
if (command.PaymentId == 0)
|
if (command.PaymentId == 0)
|
||||||
{
|
{
|
||||||
command = command with { PaymentId = paymentId };
|
command = command with { PaymentId = paymentId };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 2. 执行更新
|
||||||
var result = await mediator.Send(command, cancellationToken);
|
var result = await mediator.Send(command, cancellationToken);
|
||||||
|
|
||||||
|
// 3. 返回更新结果或 404
|
||||||
return result == null
|
return result == null
|
||||||
? ApiResponse<PaymentDto>.Error(ErrorCodes.NotFound, "支付记录不存在")
|
? ApiResponse<PaymentDto>.Error(ErrorCodes.NotFound, "支付记录不存在")
|
||||||
: ApiResponse<PaymentDto>.Ok(result);
|
: ApiResponse<PaymentDto>.Ok(result);
|
||||||
@@ -109,7 +117,10 @@ public sealed class PaymentsController(IMediator mediator) : BaseApiController
|
|||||||
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status404NotFound)]
|
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status404NotFound)]
|
||||||
public async Task<ApiResponse<object>> Delete(long paymentId, CancellationToken cancellationToken)
|
public async Task<ApiResponse<object>> Delete(long paymentId, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 执行删除
|
||||||
var success = await mediator.Send(new DeletePaymentCommand { PaymentId = paymentId }, cancellationToken);
|
var success = await mediator.Send(new DeletePaymentCommand { PaymentId = paymentId }, cancellationToken);
|
||||||
|
|
||||||
|
// 2. 返回结果或 404
|
||||||
return success
|
return success
|
||||||
? ApiResponse<object>.Ok(null)
|
? ApiResponse<object>.Ok(null)
|
||||||
: ApiResponse<object>.Error(ErrorCodes.NotFound, "支付记录不存在");
|
: ApiResponse<object>.Error(ErrorCodes.NotFound, "支付记录不存在");
|
||||||
|
|||||||
@@ -31,7 +31,10 @@ public sealed class PermissionsController(IMediator mediator) : BaseApiControlle
|
|||||||
[ProducesResponseType(typeof(ApiResponse<PagedResult<PermissionDto>>), StatusCodes.Status200OK)]
|
[ProducesResponseType(typeof(ApiResponse<PagedResult<PermissionDto>>), StatusCodes.Status200OK)]
|
||||||
public async Task<ApiResponse<PagedResult<PermissionDto>>> Search([FromQuery] SearchPermissionsQuery query, CancellationToken cancellationToken)
|
public async Task<ApiResponse<PagedResult<PermissionDto>>> Search([FromQuery] SearchPermissionsQuery query, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 查询权限分页
|
||||||
var result = await mediator.Send(query, cancellationToken);
|
var result = await mediator.Send(query, cancellationToken);
|
||||||
|
|
||||||
|
// 2. 返回分页数据
|
||||||
return ApiResponse<PagedResult<PermissionDto>>.Ok(result);
|
return ApiResponse<PagedResult<PermissionDto>>.Ok(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,7 +46,10 @@ public sealed class PermissionsController(IMediator mediator) : BaseApiControlle
|
|||||||
[ProducesResponseType(typeof(ApiResponse<PermissionDto>), StatusCodes.Status200OK)]
|
[ProducesResponseType(typeof(ApiResponse<PermissionDto>), StatusCodes.Status200OK)]
|
||||||
public async Task<ApiResponse<PermissionDto>> Create([FromBody, Required] CreatePermissionCommand command, CancellationToken cancellationToken)
|
public async Task<ApiResponse<PermissionDto>> Create([FromBody, Required] CreatePermissionCommand command, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 创建权限
|
||||||
var result = await mediator.Send(command, cancellationToken);
|
var result = await mediator.Send(command, cancellationToken);
|
||||||
|
|
||||||
|
// 2. 返回创建结果
|
||||||
return ApiResponse<PermissionDto>.Ok(result);
|
return ApiResponse<PermissionDto>.Ok(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,8 +62,13 @@ public sealed class PermissionsController(IMediator mediator) : BaseApiControlle
|
|||||||
[ProducesResponseType(typeof(ApiResponse<PermissionDto>), StatusCodes.Status404NotFound)]
|
[ProducesResponseType(typeof(ApiResponse<PermissionDto>), StatusCodes.Status404NotFound)]
|
||||||
public async Task<ApiResponse<PermissionDto>> Update(long permissionId, [FromBody, Required] UpdatePermissionCommand command, CancellationToken cancellationToken)
|
public async Task<ApiResponse<PermissionDto>> Update(long permissionId, [FromBody, Required] UpdatePermissionCommand command, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 绑定权限标识
|
||||||
command = command with { PermissionId = permissionId };
|
command = command with { PermissionId = permissionId };
|
||||||
|
|
||||||
|
// 2. 执行更新
|
||||||
var result = await mediator.Send(command, cancellationToken);
|
var result = await mediator.Send(command, cancellationToken);
|
||||||
|
|
||||||
|
// 3. 返回更新结果或 404
|
||||||
return result is null
|
return result is null
|
||||||
? ApiResponse<PermissionDto>.Error(StatusCodes.Status404NotFound, "权限不存在")
|
? ApiResponse<PermissionDto>.Error(StatusCodes.Status404NotFound, "权限不存在")
|
||||||
: ApiResponse<PermissionDto>.Ok(result);
|
: ApiResponse<PermissionDto>.Ok(result);
|
||||||
@@ -71,7 +82,10 @@ public sealed class PermissionsController(IMediator mediator) : BaseApiControlle
|
|||||||
[ProducesResponseType(typeof(ApiResponse<bool>), StatusCodes.Status200OK)]
|
[ProducesResponseType(typeof(ApiResponse<bool>), StatusCodes.Status200OK)]
|
||||||
public async Task<ApiResponse<bool>> Delete(long permissionId, CancellationToken cancellationToken)
|
public async Task<ApiResponse<bool>> Delete(long permissionId, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 构建删除命令
|
||||||
var command = new DeletePermissionCommand { PermissionId = permissionId };
|
var command = new DeletePermissionCommand { PermissionId = permissionId };
|
||||||
|
|
||||||
|
// 2. 执行删除
|
||||||
var result = await mediator.Send(command, cancellationToken);
|
var result = await mediator.Send(command, cancellationToken);
|
||||||
return ApiResponse<bool>.Ok(result);
|
return ApiResponse<bool>.Ok(result);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,16 +16,11 @@ namespace TakeoutSaaS.AdminApi.Controllers;
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 商品管理。
|
/// 商品管理。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
|
||||||
/// 初始化控制器。
|
|
||||||
/// </remarks>
|
|
||||||
[ApiVersion("1.0")]
|
[ApiVersion("1.0")]
|
||||||
[Authorize]
|
[Authorize]
|
||||||
[Route("api/admin/v{version:apiVersion}/products")]
|
[Route("api/admin/v{version:apiVersion}/products")]
|
||||||
public sealed class ProductsController(IMediator mediator) : BaseApiController
|
public sealed class ProductsController(IMediator mediator) : BaseApiController
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 创建商品。
|
/// 创建商品。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -34,7 +29,10 @@ public sealed class ProductsController(IMediator mediator) : BaseApiController
|
|||||||
[ProducesResponseType(typeof(ApiResponse<ProductDto>), StatusCodes.Status200OK)]
|
[ProducesResponseType(typeof(ApiResponse<ProductDto>), StatusCodes.Status200OK)]
|
||||||
public async Task<ApiResponse<ProductDto>> Create([FromBody] CreateProductCommand command, CancellationToken cancellationToken)
|
public async Task<ApiResponse<ProductDto>> Create([FromBody] CreateProductCommand command, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 创建商品
|
||||||
var result = await mediator.Send(command, cancellationToken);
|
var result = await mediator.Send(command, cancellationToken);
|
||||||
|
|
||||||
|
// 2. 返回创建结果
|
||||||
return ApiResponse<ProductDto>.Ok(result);
|
return ApiResponse<ProductDto>.Ok(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,6 +52,7 @@ public sealed class ProductsController(IMediator mediator) : BaseApiController
|
|||||||
[FromQuery] bool sortDesc = true,
|
[FromQuery] bool sortDesc = true,
|
||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
|
// 1. 组装查询参数并执行查询
|
||||||
var result = await mediator.Send(new SearchProductsQuery
|
var result = await mediator.Send(new SearchProductsQuery
|
||||||
{
|
{
|
||||||
StoreId = storeId,
|
StoreId = storeId,
|
||||||
@@ -65,6 +64,7 @@ public sealed class ProductsController(IMediator mediator) : BaseApiController
|
|||||||
SortDescending = sortDesc
|
SortDescending = sortDesc
|
||||||
}, cancellationToken);
|
}, cancellationToken);
|
||||||
|
|
||||||
|
// 2. 返回分页结果
|
||||||
return ApiResponse<PagedResult<ProductDto>>.Ok(result);
|
return ApiResponse<PagedResult<ProductDto>>.Ok(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,7 +77,10 @@ public sealed class ProductsController(IMediator mediator) : BaseApiController
|
|||||||
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status404NotFound)]
|
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status404NotFound)]
|
||||||
public async Task<ApiResponse<ProductDto>> Detail(long productId, CancellationToken cancellationToken)
|
public async Task<ApiResponse<ProductDto>> Detail(long productId, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 查询商品详情
|
||||||
var result = await mediator.Send(new GetProductByIdQuery { ProductId = productId }, cancellationToken);
|
var result = await mediator.Send(new GetProductByIdQuery { ProductId = productId }, cancellationToken);
|
||||||
|
|
||||||
|
// 2. 返回详情或 404
|
||||||
return result == null
|
return result == null
|
||||||
? ApiResponse<ProductDto>.Error(ErrorCodes.NotFound, "商品不存在")
|
? ApiResponse<ProductDto>.Error(ErrorCodes.NotFound, "商品不存在")
|
||||||
: ApiResponse<ProductDto>.Ok(result);
|
: ApiResponse<ProductDto>.Ok(result);
|
||||||
@@ -92,11 +95,16 @@ public sealed class ProductsController(IMediator mediator) : BaseApiController
|
|||||||
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status404NotFound)]
|
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status404NotFound)]
|
||||||
public async Task<ApiResponse<ProductDto>> Update(long productId, [FromBody] UpdateProductCommand command, CancellationToken cancellationToken)
|
public async Task<ApiResponse<ProductDto>> Update(long productId, [FromBody] UpdateProductCommand command, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 确保命令包含商品标识
|
||||||
if (command.ProductId == 0)
|
if (command.ProductId == 0)
|
||||||
{
|
{
|
||||||
command = command with { ProductId = productId };
|
command = command with { ProductId = productId };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 2. 执行更新
|
||||||
var result = await mediator.Send(command, cancellationToken);
|
var result = await mediator.Send(command, cancellationToken);
|
||||||
|
|
||||||
|
// 3. 返回更新结果或 404
|
||||||
return result == null
|
return result == null
|
||||||
? ApiResponse<ProductDto>.Error(ErrorCodes.NotFound, "商品不存在")
|
? ApiResponse<ProductDto>.Error(ErrorCodes.NotFound, "商品不存在")
|
||||||
: ApiResponse<ProductDto>.Ok(result);
|
: ApiResponse<ProductDto>.Ok(result);
|
||||||
@@ -111,7 +119,10 @@ public sealed class ProductsController(IMediator mediator) : BaseApiController
|
|||||||
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status404NotFound)]
|
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status404NotFound)]
|
||||||
public async Task<ApiResponse<object>> Delete(long productId, CancellationToken cancellationToken)
|
public async Task<ApiResponse<object>> Delete(long productId, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 执行删除
|
||||||
var success = await mediator.Send(new DeleteProductCommand { ProductId = productId }, cancellationToken);
|
var success = await mediator.Send(new DeleteProductCommand { ProductId = productId }, cancellationToken);
|
||||||
|
|
||||||
|
// 2. 返回结果或 404
|
||||||
return success
|
return success
|
||||||
? ApiResponse<object>.Ok(null)
|
? ApiResponse<object>.Ok(null)
|
||||||
: ApiResponse<object>.Error(ErrorCodes.NotFound, "商品不存在");
|
: ApiResponse<object>.Error(ErrorCodes.NotFound, "商品不存在");
|
||||||
|
|||||||
@@ -32,7 +32,10 @@ public sealed class RolesController(IMediator mediator) : BaseApiController
|
|||||||
[ProducesResponseType(typeof(ApiResponse<IReadOnlyList<RoleTemplateDto>>), StatusCodes.Status200OK)]
|
[ProducesResponseType(typeof(ApiResponse<IReadOnlyList<RoleTemplateDto>>), StatusCodes.Status200OK)]
|
||||||
public async Task<ApiResponse<IReadOnlyList<RoleTemplateDto>>> ListTemplates([FromQuery] bool? isActive, CancellationToken cancellationToken)
|
public async Task<ApiResponse<IReadOnlyList<RoleTemplateDto>>> ListTemplates([FromQuery] bool? isActive, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 查询模板列表
|
||||||
var result = await mediator.Send(new ListRoleTemplatesQuery { IsActive = isActive }, cancellationToken);
|
var result = await mediator.Send(new ListRoleTemplatesQuery { IsActive = isActive }, cancellationToken);
|
||||||
|
|
||||||
|
// 2. 返回模板集合
|
||||||
return ApiResponse<IReadOnlyList<RoleTemplateDto>>.Ok(result);
|
return ApiResponse<IReadOnlyList<RoleTemplateDto>>.Ok(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,7 +51,10 @@ public sealed class RolesController(IMediator mediator) : BaseApiController
|
|||||||
[ProducesResponseType(typeof(ApiResponse<RoleTemplateDto>), StatusCodes.Status404NotFound)]
|
[ProducesResponseType(typeof(ApiResponse<RoleTemplateDto>), StatusCodes.Status404NotFound)]
|
||||||
public async Task<ApiResponse<RoleTemplateDto>> GetTemplate(string templateCode, CancellationToken cancellationToken)
|
public async Task<ApiResponse<RoleTemplateDto>> GetTemplate(string templateCode, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 查询指定模板
|
||||||
var result = await mediator.Send(new GetRoleTemplateQuery { TemplateCode = templateCode }, cancellationToken);
|
var result = await mediator.Send(new GetRoleTemplateQuery { TemplateCode = templateCode }, cancellationToken);
|
||||||
|
|
||||||
|
// 2. 返回模板或 404
|
||||||
return result is null
|
return result is null
|
||||||
? ApiResponse<RoleTemplateDto>.Error(StatusCodes.Status404NotFound, "角色模板不存在")
|
? ApiResponse<RoleTemplateDto>.Error(StatusCodes.Status404NotFound, "角色模板不存在")
|
||||||
: ApiResponse<RoleTemplateDto>.Ok(result);
|
: ApiResponse<RoleTemplateDto>.Ok(result);
|
||||||
@@ -62,7 +68,10 @@ public sealed class RolesController(IMediator mediator) : BaseApiController
|
|||||||
[ProducesResponseType(typeof(ApiResponse<RoleTemplateDto>), StatusCodes.Status200OK)]
|
[ProducesResponseType(typeof(ApiResponse<RoleTemplateDto>), StatusCodes.Status200OK)]
|
||||||
public async Task<ApiResponse<RoleTemplateDto>> CreateTemplate([FromBody, Required] CreateRoleTemplateCommand command, CancellationToken cancellationToken)
|
public async Task<ApiResponse<RoleTemplateDto>> CreateTemplate([FromBody, Required] CreateRoleTemplateCommand command, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 创建模板
|
||||||
var result = await mediator.Send(command, cancellationToken);
|
var result = await mediator.Send(command, cancellationToken);
|
||||||
|
|
||||||
|
// 2. 返回创建结果
|
||||||
return ApiResponse<RoleTemplateDto>.Ok(result);
|
return ApiResponse<RoleTemplateDto>.Ok(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,8 +87,13 @@ public sealed class RolesController(IMediator mediator) : BaseApiController
|
|||||||
[FromBody, Required] UpdateRoleTemplateCommand command,
|
[FromBody, Required] UpdateRoleTemplateCommand command,
|
||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 绑定模板编码
|
||||||
command = command with { TemplateCode = templateCode };
|
command = command with { TemplateCode = templateCode };
|
||||||
|
|
||||||
|
// 2. 执行更新
|
||||||
var result = await mediator.Send(command, cancellationToken);
|
var result = await mediator.Send(command, cancellationToken);
|
||||||
|
|
||||||
|
// 3. 返回更新结果或 404
|
||||||
return result is null
|
return result is null
|
||||||
? ApiResponse<RoleTemplateDto>.Error(StatusCodes.Status404NotFound, "角色模板不存在")
|
? ApiResponse<RoleTemplateDto>.Error(StatusCodes.Status404NotFound, "角色模板不存在")
|
||||||
: ApiResponse<RoleTemplateDto>.Ok(result);
|
: ApiResponse<RoleTemplateDto>.Ok(result);
|
||||||
@@ -93,7 +107,10 @@ public sealed class RolesController(IMediator mediator) : BaseApiController
|
|||||||
[ProducesResponseType(typeof(ApiResponse<bool>), StatusCodes.Status200OK)]
|
[ProducesResponseType(typeof(ApiResponse<bool>), StatusCodes.Status200OK)]
|
||||||
public async Task<ApiResponse<bool>> DeleteTemplate(string templateCode, CancellationToken cancellationToken)
|
public async Task<ApiResponse<bool>> DeleteTemplate(string templateCode, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 删除模板
|
||||||
var result = await mediator.Send(new DeleteRoleTemplateCommand { TemplateCode = templateCode }, cancellationToken);
|
var result = await mediator.Send(new DeleteRoleTemplateCommand { TemplateCode = templateCode }, cancellationToken);
|
||||||
|
|
||||||
|
// 2. 返回执行结果
|
||||||
return ApiResponse<bool>.Ok(result);
|
return ApiResponse<bool>.Ok(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,7 +129,10 @@ public sealed class RolesController(IMediator mediator) : BaseApiController
|
|||||||
[FromBody, Required] CopyRoleTemplateCommand command,
|
[FromBody, Required] CopyRoleTemplateCommand command,
|
||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 绑定模板编码
|
||||||
command = command with { TemplateCode = templateCode };
|
command = command with { TemplateCode = templateCode };
|
||||||
|
|
||||||
|
// 2. 复制模板并返回角色
|
||||||
var result = await mediator.Send(command, cancellationToken);
|
var result = await mediator.Send(command, cancellationToken);
|
||||||
return ApiResponse<RoleDto>.Ok(result);
|
return ApiResponse<RoleDto>.Ok(result);
|
||||||
}
|
}
|
||||||
@@ -131,7 +151,10 @@ public sealed class RolesController(IMediator mediator) : BaseApiController
|
|||||||
[FromBody] InitializeRoleTemplatesCommand? command,
|
[FromBody] InitializeRoleTemplatesCommand? command,
|
||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 确保命令实例存在
|
||||||
command ??= new InitializeRoleTemplatesCommand();
|
command ??= new InitializeRoleTemplatesCommand();
|
||||||
|
|
||||||
|
// 2. 执行初始化
|
||||||
var result = await mediator.Send(command, cancellationToken);
|
var result = await mediator.Send(command, cancellationToken);
|
||||||
return ApiResponse<IReadOnlyList<RoleDto>>.Ok(result);
|
return ApiResponse<IReadOnlyList<RoleDto>>.Ok(result);
|
||||||
}
|
}
|
||||||
@@ -149,7 +172,10 @@ public sealed class RolesController(IMediator mediator) : BaseApiController
|
|||||||
[ProducesResponseType(typeof(ApiResponse<PagedResult<RoleDto>>), StatusCodes.Status200OK)]
|
[ProducesResponseType(typeof(ApiResponse<PagedResult<RoleDto>>), StatusCodes.Status200OK)]
|
||||||
public async Task<ApiResponse<PagedResult<RoleDto>>> Search([FromQuery] SearchRolesQuery query, CancellationToken cancellationToken)
|
public async Task<ApiResponse<PagedResult<RoleDto>>> Search([FromQuery] SearchRolesQuery query, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 查询角色分页
|
||||||
var result = await mediator.Send(query, cancellationToken);
|
var result = await mediator.Send(query, cancellationToken);
|
||||||
|
|
||||||
|
// 2. 返回分页数据
|
||||||
return ApiResponse<PagedResult<RoleDto>>.Ok(result);
|
return ApiResponse<PagedResult<RoleDto>>.Ok(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -161,7 +187,10 @@ public sealed class RolesController(IMediator mediator) : BaseApiController
|
|||||||
[ProducesResponseType(typeof(ApiResponse<RoleDto>), StatusCodes.Status200OK)]
|
[ProducesResponseType(typeof(ApiResponse<RoleDto>), StatusCodes.Status200OK)]
|
||||||
public async Task<ApiResponse<RoleDto>> Create([FromBody, Required] CreateRoleCommand command, CancellationToken cancellationToken)
|
public async Task<ApiResponse<RoleDto>> Create([FromBody, Required] CreateRoleCommand command, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 创建角色
|
||||||
var result = await mediator.Send(command, cancellationToken);
|
var result = await mediator.Send(command, cancellationToken);
|
||||||
|
|
||||||
|
// 2. 返回创建结果
|
||||||
return ApiResponse<RoleDto>.Ok(result);
|
return ApiResponse<RoleDto>.Ok(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -174,8 +203,13 @@ public sealed class RolesController(IMediator mediator) : BaseApiController
|
|||||||
[ProducesResponseType(typeof(ApiResponse<RoleDto>), StatusCodes.Status404NotFound)]
|
[ProducesResponseType(typeof(ApiResponse<RoleDto>), StatusCodes.Status404NotFound)]
|
||||||
public async Task<ApiResponse<RoleDto>> Update(long roleId, [FromBody, Required] UpdateRoleCommand command, CancellationToken cancellationToken)
|
public async Task<ApiResponse<RoleDto>> Update(long roleId, [FromBody, Required] UpdateRoleCommand command, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 绑定角色标识
|
||||||
command = command with { RoleId = roleId };
|
command = command with { RoleId = roleId };
|
||||||
|
|
||||||
|
// 2. 执行更新
|
||||||
var result = await mediator.Send(command, cancellationToken);
|
var result = await mediator.Send(command, cancellationToken);
|
||||||
|
|
||||||
|
// 3. 返回更新结果或 404
|
||||||
return result is null
|
return result is null
|
||||||
? ApiResponse<RoleDto>.Error(StatusCodes.Status404NotFound, "角色不存在")
|
? ApiResponse<RoleDto>.Error(StatusCodes.Status404NotFound, "角色不存在")
|
||||||
: ApiResponse<RoleDto>.Ok(result);
|
: ApiResponse<RoleDto>.Ok(result);
|
||||||
@@ -189,7 +223,10 @@ public sealed class RolesController(IMediator mediator) : BaseApiController
|
|||||||
[ProducesResponseType(typeof(ApiResponse<bool>), StatusCodes.Status200OK)]
|
[ProducesResponseType(typeof(ApiResponse<bool>), StatusCodes.Status200OK)]
|
||||||
public async Task<ApiResponse<bool>> Delete(long roleId, CancellationToken cancellationToken)
|
public async Task<ApiResponse<bool>> Delete(long roleId, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 构建删除命令
|
||||||
var command = new DeleteRoleCommand { RoleId = roleId };
|
var command = new DeleteRoleCommand { RoleId = roleId };
|
||||||
|
|
||||||
|
// 2. 执行删除
|
||||||
var result = await mediator.Send(command, cancellationToken);
|
var result = await mediator.Send(command, cancellationToken);
|
||||||
return ApiResponse<bool>.Ok(result);
|
return ApiResponse<bool>.Ok(result);
|
||||||
}
|
}
|
||||||
@@ -202,7 +239,10 @@ public sealed class RolesController(IMediator mediator) : BaseApiController
|
|||||||
[ProducesResponseType(typeof(ApiResponse<bool>), StatusCodes.Status200OK)]
|
[ProducesResponseType(typeof(ApiResponse<bool>), StatusCodes.Status200OK)]
|
||||||
public async Task<ApiResponse<bool>> BindPermissions(long roleId, [FromBody, Required] BindRolePermissionsCommand command, CancellationToken cancellationToken)
|
public async Task<ApiResponse<bool>> BindPermissions(long roleId, [FromBody, Required] BindRolePermissionsCommand command, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 绑定角色标识
|
||||||
command = command with { RoleId = roleId };
|
command = command with { RoleId = roleId };
|
||||||
|
|
||||||
|
// 2. 执行覆盖式授权
|
||||||
var result = await mediator.Send(command, cancellationToken);
|
var result = await mediator.Send(command, cancellationToken);
|
||||||
return ApiResponse<bool>.Ok(result);
|
return ApiResponse<bool>.Ok(result);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,16 +16,11 @@ namespace TakeoutSaaS.AdminApi.Controllers;
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 门店管理。
|
/// 门店管理。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
|
||||||
/// 初始化控制器。
|
|
||||||
/// </remarks>
|
|
||||||
[ApiVersion("1.0")]
|
[ApiVersion("1.0")]
|
||||||
[Authorize]
|
[Authorize]
|
||||||
[Route("api/admin/v{version:apiVersion}/stores")]
|
[Route("api/admin/v{version:apiVersion}/stores")]
|
||||||
public sealed class StoresController(IMediator mediator) : BaseApiController
|
public sealed class StoresController(IMediator mediator) : BaseApiController
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 创建门店。
|
/// 创建门店。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -34,7 +29,10 @@ public sealed class StoresController(IMediator mediator) : BaseApiController
|
|||||||
[ProducesResponseType(typeof(ApiResponse<StoreDto>), StatusCodes.Status200OK)]
|
[ProducesResponseType(typeof(ApiResponse<StoreDto>), StatusCodes.Status200OK)]
|
||||||
public async Task<ApiResponse<StoreDto>> Create([FromBody] CreateStoreCommand command, CancellationToken cancellationToken)
|
public async Task<ApiResponse<StoreDto>> Create([FromBody] CreateStoreCommand command, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 创建门店
|
||||||
var result = await mediator.Send(command, cancellationToken);
|
var result = await mediator.Send(command, cancellationToken);
|
||||||
|
|
||||||
|
// 2. 返回创建结果
|
||||||
return ApiResponse<StoreDto>.Ok(result);
|
return ApiResponse<StoreDto>.Ok(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,6 +51,7 @@ public sealed class StoresController(IMediator mediator) : BaseApiController
|
|||||||
[FromQuery] bool sortDesc = true,
|
[FromQuery] bool sortDesc = true,
|
||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
|
// 1. 组装查询参数并执行查询
|
||||||
var result = await mediator.Send(new SearchStoresQuery
|
var result = await mediator.Send(new SearchStoresQuery
|
||||||
{
|
{
|
||||||
MerchantId = merchantId,
|
MerchantId = merchantId,
|
||||||
@@ -63,6 +62,7 @@ public sealed class StoresController(IMediator mediator) : BaseApiController
|
|||||||
SortDescending = sortDesc
|
SortDescending = sortDesc
|
||||||
}, cancellationToken);
|
}, cancellationToken);
|
||||||
|
|
||||||
|
// 2. 返回分页结果
|
||||||
return ApiResponse<PagedResult<StoreDto>>.Ok(result);
|
return ApiResponse<PagedResult<StoreDto>>.Ok(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,7 +75,10 @@ public sealed class StoresController(IMediator mediator) : BaseApiController
|
|||||||
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status404NotFound)]
|
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status404NotFound)]
|
||||||
public async Task<ApiResponse<StoreDto>> Detail(long storeId, CancellationToken cancellationToken)
|
public async Task<ApiResponse<StoreDto>> Detail(long storeId, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 查询门店详情
|
||||||
var result = await mediator.Send(new GetStoreByIdQuery { StoreId = storeId }, cancellationToken);
|
var result = await mediator.Send(new GetStoreByIdQuery { StoreId = storeId }, cancellationToken);
|
||||||
|
|
||||||
|
// 2. 返回详情或 404
|
||||||
return result == null
|
return result == null
|
||||||
? ApiResponse<StoreDto>.Error(ErrorCodes.NotFound, "门店不存在")
|
? ApiResponse<StoreDto>.Error(ErrorCodes.NotFound, "门店不存在")
|
||||||
: ApiResponse<StoreDto>.Ok(result);
|
: ApiResponse<StoreDto>.Ok(result);
|
||||||
@@ -90,11 +93,16 @@ public sealed class StoresController(IMediator mediator) : BaseApiController
|
|||||||
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status404NotFound)]
|
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status404NotFound)]
|
||||||
public async Task<ApiResponse<StoreDto>> Update(long storeId, [FromBody] UpdateStoreCommand command, CancellationToken cancellationToken)
|
public async Task<ApiResponse<StoreDto>> Update(long storeId, [FromBody] UpdateStoreCommand command, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 确保命令包含门店标识
|
||||||
if (command.StoreId == 0)
|
if (command.StoreId == 0)
|
||||||
{
|
{
|
||||||
command = command with { StoreId = storeId };
|
command = command with { StoreId = storeId };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 2. 执行更新
|
||||||
var result = await mediator.Send(command, cancellationToken);
|
var result = await mediator.Send(command, cancellationToken);
|
||||||
|
|
||||||
|
// 3. 返回更新结果或 404
|
||||||
return result == null
|
return result == null
|
||||||
? ApiResponse<StoreDto>.Error(ErrorCodes.NotFound, "门店不存在")
|
? ApiResponse<StoreDto>.Error(ErrorCodes.NotFound, "门店不存在")
|
||||||
: ApiResponse<StoreDto>.Ok(result);
|
: ApiResponse<StoreDto>.Ok(result);
|
||||||
@@ -109,7 +117,10 @@ public sealed class StoresController(IMediator mediator) : BaseApiController
|
|||||||
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status404NotFound)]
|
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status404NotFound)]
|
||||||
public async Task<ApiResponse<object>> Delete(long storeId, CancellationToken cancellationToken)
|
public async Task<ApiResponse<object>> Delete(long storeId, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 执行删除
|
||||||
var success = await mediator.Send(new DeleteStoreCommand { StoreId = storeId }, cancellationToken);
|
var success = await mediator.Send(new DeleteStoreCommand { StoreId = storeId }, cancellationToken);
|
||||||
|
|
||||||
|
// 2. 返回结果或 404
|
||||||
return success
|
return success
|
||||||
? ApiResponse<object>.Ok(null)
|
? ApiResponse<object>.Ok(null)
|
||||||
: ApiResponse<object>.Error(ErrorCodes.NotFound, "门店不存在");
|
: ApiResponse<object>.Error(ErrorCodes.NotFound, "门店不存在");
|
||||||
|
|||||||
@@ -31,7 +31,10 @@ public sealed class SystemParametersController(IMediator mediator) : BaseApiCont
|
|||||||
[ProducesResponseType(typeof(ApiResponse<SystemParameterDto>), StatusCodes.Status200OK)]
|
[ProducesResponseType(typeof(ApiResponse<SystemParameterDto>), StatusCodes.Status200OK)]
|
||||||
public async Task<ApiResponse<SystemParameterDto>> Create([FromBody] CreateSystemParameterCommand command, CancellationToken cancellationToken)
|
public async Task<ApiResponse<SystemParameterDto>> Create([FromBody] CreateSystemParameterCommand command, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 创建系统参数
|
||||||
var result = await mediator.Send(command, cancellationToken);
|
var result = await mediator.Send(command, cancellationToken);
|
||||||
|
|
||||||
|
// 2. 返回创建结果
|
||||||
return ApiResponse<SystemParameterDto>.Ok(result);
|
return ApiResponse<SystemParameterDto>.Ok(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,6 +53,7 @@ public sealed class SystemParametersController(IMediator mediator) : BaseApiCont
|
|||||||
[FromQuery] bool sortDesc = true,
|
[FromQuery] bool sortDesc = true,
|
||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
|
// 1. 组合查询参数
|
||||||
var result = await mediator.Send(new SearchSystemParametersQuery
|
var result = await mediator.Send(new SearchSystemParametersQuery
|
||||||
{
|
{
|
||||||
Keyword = keyword,
|
Keyword = keyword,
|
||||||
@@ -60,6 +64,7 @@ public sealed class SystemParametersController(IMediator mediator) : BaseApiCont
|
|||||||
SortDescending = sortDesc
|
SortDescending = sortDesc
|
||||||
}, cancellationToken);
|
}, cancellationToken);
|
||||||
|
|
||||||
|
// 2. 返回分页结果
|
||||||
return ApiResponse<PagedResult<SystemParameterDto>>.Ok(result);
|
return ApiResponse<PagedResult<SystemParameterDto>>.Ok(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,7 +77,10 @@ public sealed class SystemParametersController(IMediator mediator) : BaseApiCont
|
|||||||
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status404NotFound)]
|
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status404NotFound)]
|
||||||
public async Task<ApiResponse<SystemParameterDto>> Detail(long parameterId, CancellationToken cancellationToken)
|
public async Task<ApiResponse<SystemParameterDto>> Detail(long parameterId, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 查询参数详情
|
||||||
var result = await mediator.Send(new GetSystemParameterByIdQuery(parameterId), cancellationToken);
|
var result = await mediator.Send(new GetSystemParameterByIdQuery(parameterId), cancellationToken);
|
||||||
|
|
||||||
|
// 2. 返回详情或 404
|
||||||
return result == null
|
return result == null
|
||||||
? ApiResponse<SystemParameterDto>.Error(ErrorCodes.NotFound, "系统参数不存在")
|
? ApiResponse<SystemParameterDto>.Error(ErrorCodes.NotFound, "系统参数不存在")
|
||||||
: ApiResponse<SystemParameterDto>.Ok(result);
|
: ApiResponse<SystemParameterDto>.Ok(result);
|
||||||
@@ -87,12 +95,16 @@ public sealed class SystemParametersController(IMediator mediator) : BaseApiCont
|
|||||||
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status404NotFound)]
|
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status404NotFound)]
|
||||||
public async Task<ApiResponse<SystemParameterDto>> Update(long parameterId, [FromBody] UpdateSystemParameterCommand command, CancellationToken cancellationToken)
|
public async Task<ApiResponse<SystemParameterDto>> Update(long parameterId, [FromBody] UpdateSystemParameterCommand command, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 确保命令包含参数标识
|
||||||
if (command.ParameterId == 0)
|
if (command.ParameterId == 0)
|
||||||
{
|
{
|
||||||
command = command with { ParameterId = parameterId };
|
command = command with { ParameterId = parameterId };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 2. 执行更新
|
||||||
var result = await mediator.Send(command, cancellationToken);
|
var result = await mediator.Send(command, cancellationToken);
|
||||||
|
|
||||||
|
// 3. 返回结果或 404
|
||||||
return result == null
|
return result == null
|
||||||
? ApiResponse<SystemParameterDto>.Error(ErrorCodes.NotFound, "系统参数不存在")
|
? ApiResponse<SystemParameterDto>.Error(ErrorCodes.NotFound, "系统参数不存在")
|
||||||
: ApiResponse<SystemParameterDto>.Ok(result);
|
: ApiResponse<SystemParameterDto>.Ok(result);
|
||||||
@@ -107,7 +119,10 @@ public sealed class SystemParametersController(IMediator mediator) : BaseApiCont
|
|||||||
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status404NotFound)]
|
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status404NotFound)]
|
||||||
public async Task<ApiResponse<object>> Delete(long parameterId, CancellationToken cancellationToken)
|
public async Task<ApiResponse<object>> Delete(long parameterId, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 执行删除
|
||||||
var success = await mediator.Send(new DeleteSystemParameterCommand { ParameterId = parameterId }, cancellationToken);
|
var success = await mediator.Send(new DeleteSystemParameterCommand { ParameterId = parameterId }, cancellationToken);
|
||||||
|
|
||||||
|
// 2. 返回成功或 404
|
||||||
return success
|
return success
|
||||||
? ApiResponse.Success()
|
? ApiResponse.Success()
|
||||||
: ApiResponse<object>.Error(ErrorCodes.NotFound, "系统参数不存在");
|
: ApiResponse<object>.Error(ErrorCodes.NotFound, "系统参数不存在");
|
||||||
|
|||||||
@@ -28,8 +28,13 @@ public sealed class TenantAnnouncementsController(IMediator mediator) : BaseApiC
|
|||||||
[ProducesResponseType(typeof(ApiResponse<PagedResult<TenantAnnouncementDto>>), StatusCodes.Status200OK)]
|
[ProducesResponseType(typeof(ApiResponse<PagedResult<TenantAnnouncementDto>>), StatusCodes.Status200OK)]
|
||||||
public async Task<ApiResponse<PagedResult<TenantAnnouncementDto>>> Search(long tenantId, [FromQuery] SearchTenantAnnouncementsQuery query, CancellationToken cancellationToken)
|
public async Task<ApiResponse<PagedResult<TenantAnnouncementDto>>> Search(long tenantId, [FromQuery] SearchTenantAnnouncementsQuery query, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 绑定租户标识
|
||||||
query = query with { TenantId = tenantId };
|
query = query with { TenantId = tenantId };
|
||||||
|
|
||||||
|
// 2. 查询公告列表
|
||||||
var result = await mediator.Send(query, cancellationToken);
|
var result = await mediator.Send(query, cancellationToken);
|
||||||
|
|
||||||
|
// 3. 返回分页结果
|
||||||
return ApiResponse<PagedResult<TenantAnnouncementDto>>.Ok(result);
|
return ApiResponse<PagedResult<TenantAnnouncementDto>>.Ok(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,7 +47,10 @@ public sealed class TenantAnnouncementsController(IMediator mediator) : BaseApiC
|
|||||||
[ProducesResponseType(typeof(ApiResponse<TenantAnnouncementDto>), StatusCodes.Status404NotFound)]
|
[ProducesResponseType(typeof(ApiResponse<TenantAnnouncementDto>), StatusCodes.Status404NotFound)]
|
||||||
public async Task<ApiResponse<TenantAnnouncementDto>> Detail(long tenantId, long announcementId, CancellationToken cancellationToken)
|
public async Task<ApiResponse<TenantAnnouncementDto>> Detail(long tenantId, long announcementId, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 查询指定公告
|
||||||
var result = await mediator.Send(new GetTenantAnnouncementQuery { TenantId = tenantId, AnnouncementId = announcementId }, cancellationToken);
|
var result = await mediator.Send(new GetTenantAnnouncementQuery { TenantId = tenantId, AnnouncementId = announcementId }, cancellationToken);
|
||||||
|
|
||||||
|
// 2. 返回详情或 404
|
||||||
return result is null
|
return result is null
|
||||||
? ApiResponse<TenantAnnouncementDto>.Error(StatusCodes.Status404NotFound, "公告不存在")
|
? ApiResponse<TenantAnnouncementDto>.Error(StatusCodes.Status404NotFound, "公告不存在")
|
||||||
: ApiResponse<TenantAnnouncementDto>.Ok(result);
|
: ApiResponse<TenantAnnouncementDto>.Ok(result);
|
||||||
@@ -56,7 +64,10 @@ public sealed class TenantAnnouncementsController(IMediator mediator) : BaseApiC
|
|||||||
[ProducesResponseType(typeof(ApiResponse<TenantAnnouncementDto>), StatusCodes.Status200OK)]
|
[ProducesResponseType(typeof(ApiResponse<TenantAnnouncementDto>), StatusCodes.Status200OK)]
|
||||||
public async Task<ApiResponse<TenantAnnouncementDto>> Create(long tenantId, [FromBody, Required] CreateTenantAnnouncementCommand command, CancellationToken cancellationToken)
|
public async Task<ApiResponse<TenantAnnouncementDto>> Create(long tenantId, [FromBody, Required] CreateTenantAnnouncementCommand command, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 绑定租户标识
|
||||||
command = command with { TenantId = tenantId };
|
command = command with { TenantId = tenantId };
|
||||||
|
|
||||||
|
// 2. 创建公告并返回
|
||||||
var result = await mediator.Send(command, cancellationToken);
|
var result = await mediator.Send(command, cancellationToken);
|
||||||
return ApiResponse<TenantAnnouncementDto>.Ok(result);
|
return ApiResponse<TenantAnnouncementDto>.Ok(result);
|
||||||
}
|
}
|
||||||
@@ -70,8 +81,13 @@ public sealed class TenantAnnouncementsController(IMediator mediator) : BaseApiC
|
|||||||
[ProducesResponseType(typeof(ApiResponse<TenantAnnouncementDto>), StatusCodes.Status404NotFound)]
|
[ProducesResponseType(typeof(ApiResponse<TenantAnnouncementDto>), StatusCodes.Status404NotFound)]
|
||||||
public async Task<ApiResponse<TenantAnnouncementDto>> Update(long tenantId, long announcementId, [FromBody, Required] UpdateTenantAnnouncementCommand command, CancellationToken cancellationToken)
|
public async Task<ApiResponse<TenantAnnouncementDto>> Update(long tenantId, long announcementId, [FromBody, Required] UpdateTenantAnnouncementCommand command, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 绑定租户与公告标识
|
||||||
command = command with { TenantId = tenantId, AnnouncementId = announcementId };
|
command = command with { TenantId = tenantId, AnnouncementId = announcementId };
|
||||||
|
|
||||||
|
// 2. 执行更新
|
||||||
var result = await mediator.Send(command, cancellationToken);
|
var result = await mediator.Send(command, cancellationToken);
|
||||||
|
|
||||||
|
// 3. 返回更新结果或 404
|
||||||
return result is null
|
return result is null
|
||||||
? ApiResponse<TenantAnnouncementDto>.Error(StatusCodes.Status404NotFound, "公告不存在")
|
? ApiResponse<TenantAnnouncementDto>.Error(StatusCodes.Status404NotFound, "公告不存在")
|
||||||
: ApiResponse<TenantAnnouncementDto>.Ok(result);
|
: ApiResponse<TenantAnnouncementDto>.Ok(result);
|
||||||
@@ -85,7 +101,10 @@ public sealed class TenantAnnouncementsController(IMediator mediator) : BaseApiC
|
|||||||
[ProducesResponseType(typeof(ApiResponse<bool>), StatusCodes.Status200OK)]
|
[ProducesResponseType(typeof(ApiResponse<bool>), StatusCodes.Status200OK)]
|
||||||
public async Task<ApiResponse<bool>> Delete(long tenantId, long announcementId, CancellationToken cancellationToken)
|
public async Task<ApiResponse<bool>> Delete(long tenantId, long announcementId, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 删除公告
|
||||||
var result = await mediator.Send(new DeleteTenantAnnouncementCommand { TenantId = tenantId, AnnouncementId = announcementId }, cancellationToken);
|
var result = await mediator.Send(new DeleteTenantAnnouncementCommand { TenantId = tenantId, AnnouncementId = announcementId }, cancellationToken);
|
||||||
|
|
||||||
|
// 2. 返回执行结果
|
||||||
return ApiResponse<bool>.Ok(result);
|
return ApiResponse<bool>.Ok(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,7 +117,10 @@ public sealed class TenantAnnouncementsController(IMediator mediator) : BaseApiC
|
|||||||
[ProducesResponseType(typeof(ApiResponse<TenantAnnouncementDto>), StatusCodes.Status404NotFound)]
|
[ProducesResponseType(typeof(ApiResponse<TenantAnnouncementDto>), StatusCodes.Status404NotFound)]
|
||||||
public async Task<ApiResponse<TenantAnnouncementDto>> MarkRead(long tenantId, long announcementId, CancellationToken cancellationToken)
|
public async Task<ApiResponse<TenantAnnouncementDto>> MarkRead(long tenantId, long announcementId, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 标记公告已读
|
||||||
var result = await mediator.Send(new MarkTenantAnnouncementReadCommand { TenantId = tenantId, AnnouncementId = announcementId }, cancellationToken);
|
var result = await mediator.Send(new MarkTenantAnnouncementReadCommand { TenantId = tenantId, AnnouncementId = announcementId }, cancellationToken);
|
||||||
|
|
||||||
|
// 2. 返回结果或 404
|
||||||
return result is null
|
return result is null
|
||||||
? ApiResponse<TenantAnnouncementDto>.Error(StatusCodes.Status404NotFound, "公告不存在")
|
? ApiResponse<TenantAnnouncementDto>.Error(StatusCodes.Status404NotFound, "公告不存在")
|
||||||
: ApiResponse<TenantAnnouncementDto>.Ok(result);
|
: ApiResponse<TenantAnnouncementDto>.Ok(result);
|
||||||
|
|||||||
@@ -28,8 +28,13 @@ public sealed class TenantBillingsController(IMediator mediator) : BaseApiContro
|
|||||||
[ProducesResponseType(typeof(ApiResponse<PagedResult<TenantBillingDto>>), StatusCodes.Status200OK)]
|
[ProducesResponseType(typeof(ApiResponse<PagedResult<TenantBillingDto>>), StatusCodes.Status200OK)]
|
||||||
public async Task<ApiResponse<PagedResult<TenantBillingDto>>> Search(long tenantId, [FromQuery] SearchTenantBillsQuery query, CancellationToken cancellationToken)
|
public async Task<ApiResponse<PagedResult<TenantBillingDto>>> Search(long tenantId, [FromQuery] SearchTenantBillsQuery query, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 绑定租户标识
|
||||||
query = query with { TenantId = tenantId };
|
query = query with { TenantId = tenantId };
|
||||||
|
|
||||||
|
// 2. 查询账单列表
|
||||||
var result = await mediator.Send(query, cancellationToken);
|
var result = await mediator.Send(query, cancellationToken);
|
||||||
|
|
||||||
|
// 3. 返回分页结果
|
||||||
return ApiResponse<PagedResult<TenantBillingDto>>.Ok(result);
|
return ApiResponse<PagedResult<TenantBillingDto>>.Ok(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,7 +47,10 @@ public sealed class TenantBillingsController(IMediator mediator) : BaseApiContro
|
|||||||
[ProducesResponseType(typeof(ApiResponse<TenantBillingDto>), StatusCodes.Status404NotFound)]
|
[ProducesResponseType(typeof(ApiResponse<TenantBillingDto>), StatusCodes.Status404NotFound)]
|
||||||
public async Task<ApiResponse<TenantBillingDto>> Detail(long tenantId, long billingId, CancellationToken cancellationToken)
|
public async Task<ApiResponse<TenantBillingDto>> Detail(long tenantId, long billingId, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 查询账单详情
|
||||||
var result = await mediator.Send(new GetTenantBillQuery { TenantId = tenantId, BillingId = billingId }, cancellationToken);
|
var result = await mediator.Send(new GetTenantBillQuery { TenantId = tenantId, BillingId = billingId }, cancellationToken);
|
||||||
|
|
||||||
|
// 2. 返回详情或 404
|
||||||
return result is null
|
return result is null
|
||||||
? ApiResponse<TenantBillingDto>.Error(StatusCodes.Status404NotFound, "账单不存在")
|
? ApiResponse<TenantBillingDto>.Error(StatusCodes.Status404NotFound, "账单不存在")
|
||||||
: ApiResponse<TenantBillingDto>.Ok(result);
|
: ApiResponse<TenantBillingDto>.Ok(result);
|
||||||
@@ -56,7 +64,10 @@ public sealed class TenantBillingsController(IMediator mediator) : BaseApiContro
|
|||||||
[ProducesResponseType(typeof(ApiResponse<TenantBillingDto>), StatusCodes.Status200OK)]
|
[ProducesResponseType(typeof(ApiResponse<TenantBillingDto>), StatusCodes.Status200OK)]
|
||||||
public async Task<ApiResponse<TenantBillingDto>> Create(long tenantId, [FromBody, Required] CreateTenantBillingCommand command, CancellationToken cancellationToken)
|
public async Task<ApiResponse<TenantBillingDto>> Create(long tenantId, [FromBody, Required] CreateTenantBillingCommand command, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 绑定租户标识
|
||||||
command = command with { TenantId = tenantId };
|
command = command with { TenantId = tenantId };
|
||||||
|
|
||||||
|
// 2. 创建账单
|
||||||
var result = await mediator.Send(command, cancellationToken);
|
var result = await mediator.Send(command, cancellationToken);
|
||||||
return ApiResponse<TenantBillingDto>.Ok(result);
|
return ApiResponse<TenantBillingDto>.Ok(result);
|
||||||
}
|
}
|
||||||
@@ -70,8 +81,13 @@ public sealed class TenantBillingsController(IMediator mediator) : BaseApiContro
|
|||||||
[ProducesResponseType(typeof(ApiResponse<TenantBillingDto>), StatusCodes.Status404NotFound)]
|
[ProducesResponseType(typeof(ApiResponse<TenantBillingDto>), StatusCodes.Status404NotFound)]
|
||||||
public async Task<ApiResponse<TenantBillingDto>> MarkPaid(long tenantId, long billingId, [FromBody, Required] MarkTenantBillingPaidCommand command, CancellationToken cancellationToken)
|
public async Task<ApiResponse<TenantBillingDto>> MarkPaid(long tenantId, long billingId, [FromBody, Required] MarkTenantBillingPaidCommand command, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 绑定租户与账单标识
|
||||||
command = command with { TenantId = tenantId, BillingId = billingId };
|
command = command with { TenantId = tenantId, BillingId = billingId };
|
||||||
|
|
||||||
|
// 2. 标记支付状态
|
||||||
var result = await mediator.Send(command, cancellationToken);
|
var result = await mediator.Send(command, cancellationToken);
|
||||||
|
|
||||||
|
// 3. 返回结果或 404
|
||||||
return result is null
|
return result is null
|
||||||
? ApiResponse<TenantBillingDto>.Error(StatusCodes.Status404NotFound, "账单不存在")
|
? ApiResponse<TenantBillingDto>.Error(StatusCodes.Status404NotFound, "账单不存在")
|
||||||
: ApiResponse<TenantBillingDto>.Ok(result);
|
: ApiResponse<TenantBillingDto>.Ok(result);
|
||||||
|
|||||||
@@ -27,8 +27,13 @@ public sealed class TenantNotificationsController(IMediator mediator) : BaseApiC
|
|||||||
[ProducesResponseType(typeof(ApiResponse<PagedResult<TenantNotificationDto>>), StatusCodes.Status200OK)]
|
[ProducesResponseType(typeof(ApiResponse<PagedResult<TenantNotificationDto>>), StatusCodes.Status200OK)]
|
||||||
public async Task<ApiResponse<PagedResult<TenantNotificationDto>>> Search(long tenantId, [FromQuery] SearchTenantNotificationsQuery query, CancellationToken cancellationToken)
|
public async Task<ApiResponse<PagedResult<TenantNotificationDto>>> Search(long tenantId, [FromQuery] SearchTenantNotificationsQuery query, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 绑定租户标识
|
||||||
query = query with { TenantId = tenantId };
|
query = query with { TenantId = tenantId };
|
||||||
|
|
||||||
|
// 2. 查询通知列表
|
||||||
var result = await mediator.Send(query, cancellationToken);
|
var result = await mediator.Send(query, cancellationToken);
|
||||||
|
|
||||||
|
// 3. 返回分页结果
|
||||||
return ApiResponse<PagedResult<TenantNotificationDto>>.Ok(result);
|
return ApiResponse<PagedResult<TenantNotificationDto>>.Ok(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,7 +46,10 @@ public sealed class TenantNotificationsController(IMediator mediator) : BaseApiC
|
|||||||
[ProducesResponseType(typeof(ApiResponse<TenantNotificationDto>), StatusCodes.Status404NotFound)]
|
[ProducesResponseType(typeof(ApiResponse<TenantNotificationDto>), StatusCodes.Status404NotFound)]
|
||||||
public async Task<ApiResponse<TenantNotificationDto>> MarkRead(long tenantId, long notificationId, CancellationToken cancellationToken)
|
public async Task<ApiResponse<TenantNotificationDto>> MarkRead(long tenantId, long notificationId, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 标记通知为已读
|
||||||
var result = await mediator.Send(new MarkTenantNotificationReadCommand { TenantId = tenantId, NotificationId = notificationId }, cancellationToken);
|
var result = await mediator.Send(new MarkTenantNotificationReadCommand { TenantId = tenantId, NotificationId = notificationId }, cancellationToken);
|
||||||
|
|
||||||
|
// 2. 返回结果或 404
|
||||||
return result is null
|
return result is null
|
||||||
? ApiResponse<TenantNotificationDto>.Error(StatusCodes.Status404NotFound, "通知不存在")
|
? ApiResponse<TenantNotificationDto>.Error(StatusCodes.Status404NotFound, "通知不存在")
|
||||||
: ApiResponse<TenantNotificationDto>.Ok(result);
|
: ApiResponse<TenantNotificationDto>.Ok(result);
|
||||||
|
|||||||
@@ -28,7 +28,10 @@ public sealed class TenantPackagesController(IMediator mediator) : BaseApiContro
|
|||||||
[ProducesResponseType(typeof(ApiResponse<PagedResult<TenantPackageDto>>), StatusCodes.Status200OK)]
|
[ProducesResponseType(typeof(ApiResponse<PagedResult<TenantPackageDto>>), StatusCodes.Status200OK)]
|
||||||
public async Task<ApiResponse<PagedResult<TenantPackageDto>>> Search([FromQuery] SearchTenantPackagesQuery query, CancellationToken cancellationToken)
|
public async Task<ApiResponse<PagedResult<TenantPackageDto>>> Search([FromQuery] SearchTenantPackagesQuery query, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 查询租户套餐分页
|
||||||
var result = await mediator.Send(query, cancellationToken);
|
var result = await mediator.Send(query, cancellationToken);
|
||||||
|
|
||||||
|
// 2. 返回结果
|
||||||
return ApiResponse<PagedResult<TenantPackageDto>>.Ok(result);
|
return ApiResponse<PagedResult<TenantPackageDto>>.Ok(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,7 +44,10 @@ public sealed class TenantPackagesController(IMediator mediator) : BaseApiContro
|
|||||||
[ProducesResponseType(typeof(ApiResponse<TenantPackageDto>), StatusCodes.Status404NotFound)]
|
[ProducesResponseType(typeof(ApiResponse<TenantPackageDto>), StatusCodes.Status404NotFound)]
|
||||||
public async Task<ApiResponse<TenantPackageDto>> Detail(long tenantPackageId, CancellationToken cancellationToken)
|
public async Task<ApiResponse<TenantPackageDto>> Detail(long tenantPackageId, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 查询套餐详情
|
||||||
var result = await mediator.Send(new GetTenantPackageByIdQuery { TenantPackageId = tenantPackageId }, cancellationToken);
|
var result = await mediator.Send(new GetTenantPackageByIdQuery { TenantPackageId = tenantPackageId }, cancellationToken);
|
||||||
|
|
||||||
|
// 2. 返回查询结果或 404
|
||||||
return result is null
|
return result is null
|
||||||
? ApiResponse<TenantPackageDto>.Error(StatusCodes.Status404NotFound, "套餐不存在")
|
? ApiResponse<TenantPackageDto>.Error(StatusCodes.Status404NotFound, "套餐不存在")
|
||||||
: ApiResponse<TenantPackageDto>.Ok(result);
|
: ApiResponse<TenantPackageDto>.Ok(result);
|
||||||
@@ -55,7 +61,10 @@ public sealed class TenantPackagesController(IMediator mediator) : BaseApiContro
|
|||||||
[ProducesResponseType(typeof(ApiResponse<TenantPackageDto>), StatusCodes.Status200OK)]
|
[ProducesResponseType(typeof(ApiResponse<TenantPackageDto>), StatusCodes.Status200OK)]
|
||||||
public async Task<ApiResponse<TenantPackageDto>> Create([FromBody, Required] CreateTenantPackageCommand command, CancellationToken cancellationToken)
|
public async Task<ApiResponse<TenantPackageDto>> Create([FromBody, Required] CreateTenantPackageCommand command, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 执行创建
|
||||||
var result = await mediator.Send(command, cancellationToken);
|
var result = await mediator.Send(command, cancellationToken);
|
||||||
|
|
||||||
|
// 2. 返回创建结果
|
||||||
return ApiResponse<TenantPackageDto>.Ok(result);
|
return ApiResponse<TenantPackageDto>.Ok(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,8 +77,13 @@ public sealed class TenantPackagesController(IMediator mediator) : BaseApiContro
|
|||||||
[ProducesResponseType(typeof(ApiResponse<TenantPackageDto>), StatusCodes.Status404NotFound)]
|
[ProducesResponseType(typeof(ApiResponse<TenantPackageDto>), StatusCodes.Status404NotFound)]
|
||||||
public async Task<ApiResponse<TenantPackageDto>> Update(long tenantPackageId, [FromBody, Required] UpdateTenantPackageCommand command, CancellationToken cancellationToken)
|
public async Task<ApiResponse<TenantPackageDto>> Update(long tenantPackageId, [FromBody, Required] UpdateTenantPackageCommand command, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 绑定路由 ID
|
||||||
command = command with { TenantPackageId = tenantPackageId };
|
command = command with { TenantPackageId = tenantPackageId };
|
||||||
|
|
||||||
|
// 2. 执行更新
|
||||||
var result = await mediator.Send(command, cancellationToken);
|
var result = await mediator.Send(command, cancellationToken);
|
||||||
|
|
||||||
|
// 3. 返回更新结果或 404
|
||||||
return result is null
|
return result is null
|
||||||
? ApiResponse<TenantPackageDto>.Error(StatusCodes.Status404NotFound, "套餐不存在")
|
? ApiResponse<TenantPackageDto>.Error(StatusCodes.Status404NotFound, "套餐不存在")
|
||||||
: ApiResponse<TenantPackageDto>.Ok(result);
|
: ApiResponse<TenantPackageDto>.Ok(result);
|
||||||
@@ -83,7 +97,10 @@ public sealed class TenantPackagesController(IMediator mediator) : BaseApiContro
|
|||||||
[ProducesResponseType(typeof(ApiResponse<bool>), StatusCodes.Status200OK)]
|
[ProducesResponseType(typeof(ApiResponse<bool>), StatusCodes.Status200OK)]
|
||||||
public async Task<ApiResponse<bool>> Delete(long tenantPackageId, CancellationToken cancellationToken)
|
public async Task<ApiResponse<bool>> Delete(long tenantPackageId, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 构建删除命令
|
||||||
var command = new DeleteTenantPackageCommand { TenantPackageId = tenantPackageId };
|
var command = new DeleteTenantPackageCommand { TenantPackageId = tenantPackageId };
|
||||||
|
|
||||||
|
// 2. 执行删除并返回
|
||||||
var result = await mediator.Send(command, cancellationToken);
|
var result = await mediator.Send(command, cancellationToken);
|
||||||
return ApiResponse<bool>.Ok(result);
|
return ApiResponse<bool>.Ok(result);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,10 @@ public sealed class TenantsController(IMediator mediator) : BaseApiController
|
|||||||
[ProducesResponseType(typeof(ApiResponse<TenantDto>), StatusCodes.Status200OK)]
|
[ProducesResponseType(typeof(ApiResponse<TenantDto>), StatusCodes.Status200OK)]
|
||||||
public async Task<ApiResponse<TenantDto>> Register([FromBody] RegisterTenantCommand command, CancellationToken cancellationToken)
|
public async Task<ApiResponse<TenantDto>> Register([FromBody] RegisterTenantCommand command, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 注册租户并初始化套餐
|
||||||
var result = await mediator.Send(command, cancellationToken);
|
var result = await mediator.Send(command, cancellationToken);
|
||||||
|
|
||||||
|
// 2. 返回注册结果
|
||||||
return ApiResponse<TenantDto>.Ok(result);
|
return ApiResponse<TenantDto>.Ok(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,7 +44,10 @@ public sealed class TenantsController(IMediator mediator) : BaseApiController
|
|||||||
[ProducesResponseType(typeof(ApiResponse<PagedResult<TenantDto>>), StatusCodes.Status200OK)]
|
[ProducesResponseType(typeof(ApiResponse<PagedResult<TenantDto>>), StatusCodes.Status200OK)]
|
||||||
public async Task<ApiResponse<PagedResult<TenantDto>>> Search([FromQuery] SearchTenantsQuery query, CancellationToken cancellationToken)
|
public async Task<ApiResponse<PagedResult<TenantDto>>> Search([FromQuery] SearchTenantsQuery query, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 查询租户分页
|
||||||
var result = await mediator.Send(query, cancellationToken);
|
var result = await mediator.Send(query, cancellationToken);
|
||||||
|
|
||||||
|
// 2. 返回分页数据
|
||||||
return ApiResponse<PagedResult<TenantDto>>.Ok(result);
|
return ApiResponse<PagedResult<TenantDto>>.Ok(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,7 +59,10 @@ public sealed class TenantsController(IMediator mediator) : BaseApiController
|
|||||||
[ProducesResponseType(typeof(ApiResponse<TenantDetailDto>), StatusCodes.Status200OK)]
|
[ProducesResponseType(typeof(ApiResponse<TenantDetailDto>), StatusCodes.Status200OK)]
|
||||||
public async Task<ApiResponse<TenantDetailDto>> Detail(long tenantId, CancellationToken cancellationToken)
|
public async Task<ApiResponse<TenantDetailDto>> Detail(long tenantId, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 查询租户详情
|
||||||
var result = await mediator.Send(new GetTenantByIdQuery(tenantId), cancellationToken);
|
var result = await mediator.Send(new GetTenantByIdQuery(tenantId), cancellationToken);
|
||||||
|
|
||||||
|
// 2. 返回租户信息
|
||||||
return ApiResponse<TenantDetailDto>.Ok(result);
|
return ApiResponse<TenantDetailDto>.Ok(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,8 +77,13 @@ public sealed class TenantsController(IMediator mediator) : BaseApiController
|
|||||||
[FromBody] SubmitTenantVerificationCommand body,
|
[FromBody] SubmitTenantVerificationCommand body,
|
||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 合并路由中的租户标识
|
||||||
var command = body with { TenantId = tenantId };
|
var command = body with { TenantId = tenantId };
|
||||||
|
|
||||||
|
// 2. 提交或更新认证资料
|
||||||
var result = await mediator.Send(command, cancellationToken);
|
var result = await mediator.Send(command, cancellationToken);
|
||||||
|
|
||||||
|
// 3. 返回认证结果
|
||||||
return ApiResponse<TenantVerificationDto>.Ok(result);
|
return ApiResponse<TenantVerificationDto>.Ok(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,8 +95,13 @@ public sealed class TenantsController(IMediator mediator) : BaseApiController
|
|||||||
[ProducesResponseType(typeof(ApiResponse<TenantDto>), StatusCodes.Status200OK)]
|
[ProducesResponseType(typeof(ApiResponse<TenantDto>), StatusCodes.Status200OK)]
|
||||||
public async Task<ApiResponse<TenantDto>> Review(long tenantId, [FromBody] ReviewTenantCommand body, CancellationToken cancellationToken)
|
public async Task<ApiResponse<TenantDto>> Review(long tenantId, [FromBody] ReviewTenantCommand body, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 绑定租户标识
|
||||||
var command = body with { TenantId = tenantId };
|
var command = body with { TenantId = tenantId };
|
||||||
|
|
||||||
|
// 2. 执行审核
|
||||||
var result = await mediator.Send(command, cancellationToken);
|
var result = await mediator.Send(command, cancellationToken);
|
||||||
|
|
||||||
|
// 3. 返回审核结果
|
||||||
return ApiResponse<TenantDto>.Ok(result);
|
return ApiResponse<TenantDto>.Ok(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,7 +116,10 @@ public sealed class TenantsController(IMediator mediator) : BaseApiController
|
|||||||
[FromBody] CreateTenantSubscriptionCommand body,
|
[FromBody] CreateTenantSubscriptionCommand body,
|
||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 绑定租户并创建或续费订阅
|
||||||
var command = body with { TenantId = tenantId };
|
var command = body with { TenantId = tenantId };
|
||||||
|
|
||||||
|
// 2. 返回订阅结果
|
||||||
var result = await mediator.Send(command, cancellationToken);
|
var result = await mediator.Send(command, cancellationToken);
|
||||||
return ApiResponse<TenantSubscriptionDto>.Ok(result);
|
return ApiResponse<TenantSubscriptionDto>.Ok(result);
|
||||||
}
|
}
|
||||||
@@ -114,8 +136,13 @@ public sealed class TenantsController(IMediator mediator) : BaseApiController
|
|||||||
[FromBody] ChangeTenantSubscriptionPlanCommand body,
|
[FromBody] ChangeTenantSubscriptionPlanCommand body,
|
||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 绑定租户与订阅标识
|
||||||
var command = body with { TenantId = tenantId, TenantSubscriptionId = subscriptionId };
|
var command = body with { TenantId = tenantId, TenantSubscriptionId = subscriptionId };
|
||||||
|
|
||||||
|
// 2. 执行升降配
|
||||||
var result = await mediator.Send(command, cancellationToken);
|
var result = await mediator.Send(command, cancellationToken);
|
||||||
|
|
||||||
|
// 3. 返回调整后的订阅
|
||||||
return ApiResponse<TenantSubscriptionDto>.Ok(result);
|
return ApiResponse<TenantSubscriptionDto>.Ok(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,7 +158,10 @@ public sealed class TenantsController(IMediator mediator) : BaseApiController
|
|||||||
[FromQuery] int pageSize = 20,
|
[FromQuery] int pageSize = 20,
|
||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
|
// 1. 构造审核日志查询
|
||||||
var query = new GetTenantAuditLogsQuery(tenantId, page, pageSize);
|
var query = new GetTenantAuditLogsQuery(tenantId, page, pageSize);
|
||||||
|
|
||||||
|
// 2. 查询并返回分页结果
|
||||||
var result = await mediator.Send(query, cancellationToken);
|
var result = await mediator.Send(query, cancellationToken);
|
||||||
return ApiResponse<PagedResult<TenantAuditLogDto>>.Ok(result);
|
return ApiResponse<PagedResult<TenantAuditLogDto>>.Ok(result);
|
||||||
}
|
}
|
||||||
@@ -148,7 +178,10 @@ public sealed class TenantsController(IMediator mediator) : BaseApiController
|
|||||||
[FromBody, Required] CheckTenantQuotaCommand body,
|
[FromBody, Required] CheckTenantQuotaCommand body,
|
||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 绑定租户标识
|
||||||
var command = body with { TenantId = tenantId };
|
var command = body with { TenantId = tenantId };
|
||||||
|
|
||||||
|
// 2. 校验并占用配额
|
||||||
var result = await mediator.Send(command, cancellationToken);
|
var result = await mediator.Send(command, cancellationToken);
|
||||||
return ApiResponse<QuotaCheckResultDto>.Ok(result);
|
return ApiResponse<QuotaCheckResultDto>.Ok(result);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ public sealed class UserPermissionsController(IAdminAuthService authService) : B
|
|||||||
[FromQuery] SearchUserPermissionsQuery query,
|
[FromQuery] SearchUserPermissionsQuery query,
|
||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 查询当前租户的用户权限概览
|
||||||
var result = await authService.SearchUserPermissionsAsync(
|
var result = await authService.SearchUserPermissionsAsync(
|
||||||
query.Keyword,
|
query.Keyword,
|
||||||
query.Page,
|
query.Page,
|
||||||
@@ -66,6 +67,7 @@ public sealed class UserPermissionsController(IAdminAuthService authService) : B
|
|||||||
query.SortDescending,
|
query.SortDescending,
|
||||||
cancellationToken);
|
cancellationToken);
|
||||||
|
|
||||||
|
// 2. 返回分页结果
|
||||||
return ApiResponse<PagedResult<UserPermissionDto>>.Ok(result);
|
return ApiResponse<PagedResult<UserPermissionDto>>.Ok(result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,13 +26,16 @@ using TakeoutSaaS.Module.Tenancy.Extensions;
|
|||||||
using TakeoutSaaS.Shared.Web.Extensions;
|
using TakeoutSaaS.Shared.Web.Extensions;
|
||||||
using TakeoutSaaS.Shared.Web.Swagger;
|
using TakeoutSaaS.Shared.Web.Swagger;
|
||||||
|
|
||||||
|
// 1. 创建构建器与日志模板
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
const string logTemplate = "[{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} {Level:u3}] [TraceId:{TraceId}] [SpanId:{SpanId}] [Service:{Service}] {SourceContext} {Message:lj}{NewLine}{Exception}";
|
const string logTemplate = "[{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} {Level:u3}] [TraceId:{TraceId}] [SpanId:{SpanId}] [Service:{Service}] {SourceContext} {Message:lj}{NewLine}{Exception}";
|
||||||
|
|
||||||
|
// 2. 加载种子配置文件
|
||||||
builder.Configuration
|
builder.Configuration
|
||||||
.AddJsonFile("appsettings.Seed.json", optional: true, reloadOnChange: true)
|
.AddJsonFile("appsettings.Seed.json", optional: true, reloadOnChange: true)
|
||||||
.AddJsonFile($"appsettings.Seed.{builder.Environment.EnvironmentName}.json", optional: true, reloadOnChange: true);
|
.AddJsonFile($"appsettings.Seed.{builder.Environment.EnvironmentName}.json", optional: true, reloadOnChange: true);
|
||||||
|
|
||||||
|
// 3. 配置 Serilog 输出
|
||||||
builder.Host.UseSerilog((context, _, configuration) =>
|
builder.Host.UseSerilog((context, _, configuration) =>
|
||||||
{
|
{
|
||||||
configuration
|
configuration
|
||||||
@@ -47,6 +50,7 @@ builder.Host.UseSerilog((context, _, configuration) =>
|
|||||||
outputTemplate: logTemplate);
|
outputTemplate: logTemplate);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 4. 注册通用 Web 能力与 Swagger
|
||||||
builder.Services.AddSharedWebCore();
|
builder.Services.AddSharedWebCore();
|
||||||
builder.Services.AddSharedSwagger(options =>
|
builder.Services.AddSharedSwagger(options =>
|
||||||
{
|
{
|
||||||
@@ -54,6 +58,8 @@ builder.Services.AddSharedSwagger(options =>
|
|||||||
options.Description = "管理后台 API 文档";
|
options.Description = "管理后台 API 文档";
|
||||||
options.EnableAuthorization = true;
|
options.EnableAuthorization = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 5. 注册领域与基础设施模块
|
||||||
builder.Services.AddIdentityApplication();
|
builder.Services.AddIdentityApplication();
|
||||||
builder.Services.AddIdentityInfrastructure(builder.Configuration, enableAdminSeed: true);
|
builder.Services.AddIdentityInfrastructure(builder.Configuration, enableAdminSeed: true);
|
||||||
builder.Services.AddAppInfrastructure(builder.Configuration);
|
builder.Services.AddAppInfrastructure(builder.Configuration);
|
||||||
@@ -71,6 +77,8 @@ builder.Services.AddMessagingModule(builder.Configuration);
|
|||||||
builder.Services.AddMessagingApplication();
|
builder.Services.AddMessagingApplication();
|
||||||
builder.Services.AddSchedulerModule(builder.Configuration);
|
builder.Services.AddSchedulerModule(builder.Configuration);
|
||||||
builder.Services.AddHealthChecks();
|
builder.Services.AddHealthChecks();
|
||||||
|
|
||||||
|
// 6. 配置 OpenTelemetry 采集
|
||||||
var otelSection = builder.Configuration.GetSection("Otel");
|
var otelSection = builder.Configuration.GetSection("Otel");
|
||||||
var otelEndpoint = otelSection.GetValue<string>("Endpoint");
|
var otelEndpoint = otelSection.GetValue<string>("Endpoint");
|
||||||
var useConsoleExporter = otelSection.GetValue<bool?>("UseConsoleExporter") ?? builder.Environment.IsDevelopment();
|
var useConsoleExporter = otelSection.GetValue<bool?>("UseConsoleExporter") ?? builder.Environment.IsDevelopment();
|
||||||
@@ -86,7 +94,6 @@ builder.Services.AddOpenTelemetry()
|
|||||||
.AddAspNetCoreInstrumentation()
|
.AddAspNetCoreInstrumentation()
|
||||||
.AddHttpClientInstrumentation()
|
.AddHttpClientInstrumentation()
|
||||||
.AddEntityFrameworkCoreInstrumentation();
|
.AddEntityFrameworkCoreInstrumentation();
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(otelEndpoint))
|
if (!string.IsNullOrWhiteSpace(otelEndpoint))
|
||||||
{
|
{
|
||||||
tracing.AddOtlpExporter(exporter =>
|
tracing.AddOtlpExporter(exporter =>
|
||||||
@@ -94,7 +101,6 @@ builder.Services.AddOpenTelemetry()
|
|||||||
exporter.Endpoint = new Uri(otelEndpoint);
|
exporter.Endpoint = new Uri(otelEndpoint);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (useConsoleExporter)
|
if (useConsoleExporter)
|
||||||
{
|
{
|
||||||
tracing.AddConsoleExporter();
|
tracing.AddConsoleExporter();
|
||||||
@@ -107,7 +113,6 @@ builder.Services.AddOpenTelemetry()
|
|||||||
.AddHttpClientInstrumentation()
|
.AddHttpClientInstrumentation()
|
||||||
.AddRuntimeInstrumentation()
|
.AddRuntimeInstrumentation()
|
||||||
.AddPrometheusExporter();
|
.AddPrometheusExporter();
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(otelEndpoint))
|
if (!string.IsNullOrWhiteSpace(otelEndpoint))
|
||||||
{
|
{
|
||||||
metrics.AddOtlpExporter(exporter =>
|
metrics.AddOtlpExporter(exporter =>
|
||||||
@@ -115,13 +120,13 @@ builder.Services.AddOpenTelemetry()
|
|||||||
exporter.Endpoint = new Uri(otelEndpoint);
|
exporter.Endpoint = new Uri(otelEndpoint);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (useConsoleExporter)
|
if (useConsoleExporter)
|
||||||
{
|
{
|
||||||
metrics.AddConsoleExporter();
|
metrics.AddConsoleExporter();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 7. 解析并配置 CORS
|
||||||
var adminOrigins = ResolveCorsOrigins(builder.Configuration, "Cors:Admin");
|
var adminOrigins = ResolveCorsOrigins(builder.Configuration, "Cors:Admin");
|
||||||
builder.Services.AddCors(options =>
|
builder.Services.AddCors(options =>
|
||||||
{
|
{
|
||||||
@@ -131,8 +136,8 @@ builder.Services.AddCors(options =>
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 8. 构建应用并配置中间件管道
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
|
|
||||||
app.UseCors("AdminApiCors");
|
app.UseCors("AdminApiCors");
|
||||||
app.UseTenantResolution();
|
app.UseTenantResolution();
|
||||||
app.UseSharedWebCore();
|
app.UseSharedWebCore();
|
||||||
@@ -140,12 +145,12 @@ app.UseAuthentication();
|
|||||||
app.UseAuthorization();
|
app.UseAuthorization();
|
||||||
app.UseSharedSwagger();
|
app.UseSharedSwagger();
|
||||||
app.UseSchedulerDashboard(builder.Configuration);
|
app.UseSchedulerDashboard(builder.Configuration);
|
||||||
|
|
||||||
app.MapHealthChecks("/healthz");
|
app.MapHealthChecks("/healthz");
|
||||||
app.MapPrometheusScrapingEndpoint();
|
app.MapPrometheusScrapingEndpoint();
|
||||||
app.MapControllers();
|
app.MapControllers();
|
||||||
app.Run();
|
app.Run();
|
||||||
|
|
||||||
|
// 9. 解析配置中的 CORS 域名
|
||||||
static string[] ResolveCorsOrigins(IConfiguration configuration, string sectionKey)
|
static string[] ResolveCorsOrigins(IConfiguration configuration, string sectionKey)
|
||||||
{
|
{
|
||||||
var origins = configuration.GetSection(sectionKey).Get<string[]>();
|
var origins = configuration.GetSection(sectionKey).Get<string[]>();
|
||||||
@@ -155,6 +160,7 @@ static string[] ResolveCorsOrigins(IConfiguration configuration, string sectionK
|
|||||||
.ToArray() ?? [];
|
.ToArray() ?? [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 10. 构建 CORS 策略
|
||||||
static void ConfigureCorsPolicy(CorsPolicyBuilder policy, string[] origins)
|
static void ConfigureCorsPolicy(CorsPolicyBuilder policy, string[] origins)
|
||||||
{
|
{
|
||||||
if (origins.Length == 0)
|
if (origins.Length == 0)
|
||||||
@@ -166,7 +172,6 @@ static void ConfigureCorsPolicy(CorsPolicyBuilder policy, string[] origins)
|
|||||||
policy.WithOrigins(origins)
|
policy.WithOrigins(origins)
|
||||||
.AllowCredentials();
|
.AllowCredentials();
|
||||||
}
|
}
|
||||||
|
|
||||||
policy
|
policy
|
||||||
.AllowAnyHeader()
|
.AllowAnyHeader()
|
||||||
.AllowAnyMethod();
|
.AllowAnyMethod();
|
||||||
|
|||||||
@@ -10,17 +10,13 @@ namespace TakeoutSaaS.MiniApi.Controllers;
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 小程序登录认证
|
/// 小程序登录认证
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>提供小程序端的微信登录与 Token 刷新能力。</remarks>
|
||||||
/// 小程序登录认证
|
/// <param name="authService">小程序认证服务</param>
|
||||||
/// </remarks>
|
|
||||||
/// <param name="authService"></param>
|
|
||||||
[ApiVersion("1.0")]
|
[ApiVersion("1.0")]
|
||||||
[Authorize]
|
[Authorize]
|
||||||
[Route("api/mini/v{version:apiVersion}/auth")]
|
[Route("api/mini/v{version:apiVersion}/auth")]
|
||||||
public sealed class AuthController(IMiniAuthService authService) : BaseApiController
|
public sealed class AuthController(IMiniAuthService authService) : BaseApiController
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 微信登录
|
/// 微信登录
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -29,7 +25,10 @@ public sealed class AuthController(IMiniAuthService authService) : BaseApiContro
|
|||||||
[ProducesResponseType(typeof(ApiResponse<TokenResponse>), StatusCodes.Status200OK)]
|
[ProducesResponseType(typeof(ApiResponse<TokenResponse>), StatusCodes.Status200OK)]
|
||||||
public async Task<ApiResponse<TokenResponse>> LoginWithWeChat([FromBody] WeChatLoginRequest request, CancellationToken cancellationToken)
|
public async Task<ApiResponse<TokenResponse>> LoginWithWeChat([FromBody] WeChatLoginRequest request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 调用认证服务完成微信登录
|
||||||
var response = await authService.LoginWithWeChatAsync(request, cancellationToken);
|
var response = await authService.LoginWithWeChatAsync(request, cancellationToken);
|
||||||
|
|
||||||
|
// 2. 返回访问与刷新令牌
|
||||||
return ApiResponse<TokenResponse>.Ok(response);
|
return ApiResponse<TokenResponse>.Ok(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,7 +40,10 @@ public sealed class AuthController(IMiniAuthService authService) : BaseApiContro
|
|||||||
[ProducesResponseType(typeof(ApiResponse<TokenResponse>), StatusCodes.Status200OK)]
|
[ProducesResponseType(typeof(ApiResponse<TokenResponse>), StatusCodes.Status200OK)]
|
||||||
public async Task<ApiResponse<TokenResponse>> RefreshToken([FromBody] RefreshTokenRequest request, CancellationToken cancellationToken)
|
public async Task<ApiResponse<TokenResponse>> RefreshToken([FromBody] RefreshTokenRequest request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 调用认证服务刷新 Token
|
||||||
var response = await authService.RefreshTokenAsync(request, cancellationToken);
|
var response = await authService.RefreshTokenAsync(request, cancellationToken);
|
||||||
|
|
||||||
|
// 2. 返回新的令牌
|
||||||
return ApiResponse<TokenResponse>.Ok(response);
|
return ApiResponse<TokenResponse>.Ok(response);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,8 +19,6 @@ namespace TakeoutSaaS.MiniApi.Controllers;
|
|||||||
[Route("api/mini/v{version:apiVersion}/files")]
|
[Route("api/mini/v{version:apiVersion}/files")]
|
||||||
public sealed class FilesController(IFileStorageService fileStorageService) : BaseApiController
|
public sealed class FilesController(IFileStorageService fileStorageService) : BaseApiController
|
||||||
{
|
{
|
||||||
private readonly IFileStorageService _fileStorageService = fileStorageService;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 上传图片或文件。
|
/// 上传图片或文件。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -30,23 +28,28 @@ public sealed class FilesController(IFileStorageService fileStorageService) : Ba
|
|||||||
[ProducesResponseType(typeof(ApiResponse<FileUploadResponse>), StatusCodes.Status400BadRequest)]
|
[ProducesResponseType(typeof(ApiResponse<FileUploadResponse>), StatusCodes.Status400BadRequest)]
|
||||||
public async Task<ApiResponse<FileUploadResponse>> Upload([FromForm] IFormFile? file, [FromForm] string? type, CancellationToken cancellationToken)
|
public async Task<ApiResponse<FileUploadResponse>> Upload([FromForm] IFormFile? file, [FromForm] string? type, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 校验文件有效性
|
||||||
if (file == null || file.Length == 0)
|
if (file == null || file.Length == 0)
|
||||||
{
|
{
|
||||||
return ApiResponse<FileUploadResponse>.Error(ErrorCodes.BadRequest, "文件不能为空");
|
return ApiResponse<FileUploadResponse>.Error(ErrorCodes.BadRequest, "文件不能为空");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 2. 解析上传类型
|
||||||
if (!UploadFileTypeParser.TryParse(type, out var uploadType))
|
if (!UploadFileTypeParser.TryParse(type, out var uploadType))
|
||||||
{
|
{
|
||||||
return ApiResponse<FileUploadResponse>.Error(ErrorCodes.BadRequest, "上传类型不合法");
|
return ApiResponse<FileUploadResponse>.Error(ErrorCodes.BadRequest, "上传类型不合法");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 3. 提取请求来源
|
||||||
var origin = Request.Headers["Origin"].FirstOrDefault() ?? Request.Headers["Referer"].FirstOrDefault();
|
var origin = Request.Headers["Origin"].FirstOrDefault() ?? Request.Headers["Referer"].FirstOrDefault();
|
||||||
await using var stream = file.OpenReadStream();
|
await using var stream = file.OpenReadStream();
|
||||||
|
|
||||||
var result = await _fileStorageService.UploadAsync(
|
// 4. 调用存储服务执行上传
|
||||||
|
var result = await fileStorageService.UploadAsync(
|
||||||
new UploadFileRequest(uploadType, stream, file.FileName, file.ContentType ?? string.Empty, file.Length, origin),
|
new UploadFileRequest(uploadType, stream, file.FileName, file.ContentType ?? string.Empty, file.Length, origin),
|
||||||
cancellationToken);
|
cancellationToken);
|
||||||
|
|
||||||
|
// 5. 返回上传结果
|
||||||
return ApiResponse<FileUploadResponse>.Ok(result);
|
return ApiResponse<FileUploadResponse>.Ok(result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,10 @@ public class HealthController : BaseApiController
|
|||||||
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status200OK)]
|
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status200OK)]
|
||||||
public ApiResponse<object> Get()
|
public ApiResponse<object> Get()
|
||||||
{
|
{
|
||||||
|
// 1. 构造健康状态
|
||||||
var payload = new { status = "OK", service = "MiniApi", time = DateTime.UtcNow };
|
var payload = new { status = "OK", service = "MiniApi", time = DateTime.UtcNow };
|
||||||
|
|
||||||
|
// 2. 返回健康响应
|
||||||
return ApiResponse<object>.Ok(payload);
|
return ApiResponse<object>.Ok(payload);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,17 +16,13 @@ namespace TakeoutSaaS.MiniApi.Controllers;
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 当前用户信息
|
/// 当前用户信息
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>提供小程序端当前用户档案查询。</remarks>
|
||||||
///
|
/// <param name="authService">小程序认证服务</param>
|
||||||
/// </remarks>
|
|
||||||
/// <param name="authService"></param>
|
|
||||||
[ApiVersion("1.0")]
|
[ApiVersion("1.0")]
|
||||||
[Authorize]
|
[Authorize]
|
||||||
[Route("api/mini/v{version:apiVersion}/me")]
|
[Route("api/mini/v{version:apiVersion}/me")]
|
||||||
public sealed class MeController(IMiniAuthService authService) : BaseApiController
|
public sealed class MeController(IMiniAuthService authService) : BaseApiController
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获取用户档案
|
/// 获取用户档案
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -35,12 +31,14 @@ public sealed class MeController(IMiniAuthService authService) : BaseApiControll
|
|||||||
[ProducesResponseType(typeof(ApiResponse<CurrentUserProfile>), StatusCodes.Status401Unauthorized)]
|
[ProducesResponseType(typeof(ApiResponse<CurrentUserProfile>), StatusCodes.Status401Unauthorized)]
|
||||||
public async Task<ApiResponse<CurrentUserProfile>> Get(CancellationToken cancellationToken)
|
public async Task<ApiResponse<CurrentUserProfile>> Get(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 从 JWT 中解析用户标识
|
||||||
var userId = User.GetUserId();
|
var userId = User.GetUserId();
|
||||||
if (userId == 0)
|
if (userId == 0)
|
||||||
{
|
{
|
||||||
return ApiResponse<CurrentUserProfile>.Error(ErrorCodes.Unauthorized, "Token 缺少有效的用户标识");
|
return ApiResponse<CurrentUserProfile>.Error(ErrorCodes.Unauthorized, "Token 缺少有效的用户标识");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 2. 查询用户档案并返回
|
||||||
var profile = await authService.GetProfileAsync(userId, cancellationToken);
|
var profile = await authService.GetProfileAsync(userId, cancellationToken);
|
||||||
return ApiResponse<CurrentUserProfile>.Ok(profile);
|
return ApiResponse<CurrentUserProfile>.Ok(profile);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,9 +18,11 @@ using TakeoutSaaS.Shared.Kernel.Ids;
|
|||||||
using TakeoutSaaS.Shared.Web.Extensions;
|
using TakeoutSaaS.Shared.Web.Extensions;
|
||||||
using TakeoutSaaS.Shared.Web.Swagger;
|
using TakeoutSaaS.Shared.Web.Swagger;
|
||||||
|
|
||||||
|
// 1. 创建构建器与日志模板
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
const string logTemplate = "[{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} {Level:u3}] [TraceId:{TraceId}] [SpanId:{SpanId}] [Service:{Service}] {SourceContext} {Message:lj}{NewLine}{Exception}";
|
const string logTemplate = "[{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} {Level:u3}] [TraceId:{TraceId}] [SpanId:{SpanId}] [Service:{Service}] {SourceContext} {Message:lj}{NewLine}{Exception}";
|
||||||
|
|
||||||
|
// 2. 注册雪花 ID 生成器与 Serilog
|
||||||
builder.Services.AddSingleton<IIdGenerator>(_ => new SnowflakeIdGenerator());
|
builder.Services.AddSingleton<IIdGenerator>(_ => new SnowflakeIdGenerator());
|
||||||
builder.Host.UseSerilog((_, _, configuration) =>
|
builder.Host.UseSerilog((_, _, configuration) =>
|
||||||
{
|
{
|
||||||
@@ -36,6 +38,7 @@ builder.Host.UseSerilog((_, _, configuration) =>
|
|||||||
outputTemplate: logTemplate);
|
outputTemplate: logTemplate);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 3. 注册通用 Web 能力与 Swagger
|
||||||
builder.Services.AddSharedWebCore();
|
builder.Services.AddSharedWebCore();
|
||||||
builder.Services.AddSharedSwagger(options =>
|
builder.Services.AddSharedSwagger(options =>
|
||||||
{
|
{
|
||||||
@@ -43,6 +46,8 @@ builder.Services.AddSharedSwagger(options =>
|
|||||||
options.Description = "小程序 API 文档";
|
options.Description = "小程序 API 文档";
|
||||||
options.EnableAuthorization = true;
|
options.EnableAuthorization = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 4. 注册多租户与业务模块
|
||||||
builder.Services.AddTenantResolution(builder.Configuration);
|
builder.Services.AddTenantResolution(builder.Configuration);
|
||||||
builder.Services.AddStorageModule(builder.Configuration);
|
builder.Services.AddStorageModule(builder.Configuration);
|
||||||
builder.Services.AddStorageApplication();
|
builder.Services.AddStorageApplication();
|
||||||
@@ -51,6 +56,8 @@ builder.Services.AddSmsApplication(builder.Configuration);
|
|||||||
builder.Services.AddMessagingModule(builder.Configuration);
|
builder.Services.AddMessagingModule(builder.Configuration);
|
||||||
builder.Services.AddMessagingApplication();
|
builder.Services.AddMessagingApplication();
|
||||||
builder.Services.AddHealthChecks();
|
builder.Services.AddHealthChecks();
|
||||||
|
|
||||||
|
// 5. 配置 OpenTelemetry 采集
|
||||||
var otelSection = builder.Configuration.GetSection("Otel");
|
var otelSection = builder.Configuration.GetSection("Otel");
|
||||||
var otelEndpoint = otelSection.GetValue<string>("Endpoint");
|
var otelEndpoint = otelSection.GetValue<string>("Endpoint");
|
||||||
var useConsoleExporter = otelSection.GetValue<bool?>("UseConsoleExporter") ?? builder.Environment.IsDevelopment();
|
var useConsoleExporter = otelSection.GetValue<bool?>("UseConsoleExporter") ?? builder.Environment.IsDevelopment();
|
||||||
@@ -102,6 +109,7 @@ builder.Services.AddOpenTelemetry()
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 6. 配置 CORS
|
||||||
var miniOrigins = ResolveCorsOrigins(builder.Configuration, "Cors:Mini");
|
var miniOrigins = ResolveCorsOrigins(builder.Configuration, "Cors:Mini");
|
||||||
builder.Services.AddCors(options =>
|
builder.Services.AddCors(options =>
|
||||||
{
|
{
|
||||||
@@ -111,6 +119,7 @@ builder.Services.AddCors(options =>
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 7. 构建应用并配置中间件管道
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
|
|
||||||
app.UseCors("MiniApiCors");
|
app.UseCors("MiniApiCors");
|
||||||
@@ -123,6 +132,7 @@ app.MapPrometheusScrapingEndpoint();
|
|||||||
app.MapControllers();
|
app.MapControllers();
|
||||||
app.Run();
|
app.Run();
|
||||||
|
|
||||||
|
// 8. 解析配置中的 CORS 域名
|
||||||
static string[] ResolveCorsOrigins(IConfiguration configuration, string sectionKey)
|
static string[] ResolveCorsOrigins(IConfiguration configuration, string sectionKey)
|
||||||
{
|
{
|
||||||
var origins = configuration.GetSection(sectionKey).Get<string[]>();
|
var origins = configuration.GetSection(sectionKey).Get<string[]>();
|
||||||
@@ -132,6 +142,7 @@ static string[] ResolveCorsOrigins(IConfiguration configuration, string sectionK
|
|||||||
.ToArray() ?? [];
|
.ToArray() ?? [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 9. 构建 CORS 策略
|
||||||
static void ConfigureCorsPolicy(CorsPolicyBuilder policy, string[] origins)
|
static void ConfigureCorsPolicy(CorsPolicyBuilder policy, string[] origins)
|
||||||
{
|
{
|
||||||
if (origins.Length == 0)
|
if (origins.Length == 0)
|
||||||
|
|||||||
@@ -23,7 +23,10 @@ public class HealthController : BaseApiController
|
|||||||
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status200OK)]
|
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status200OK)]
|
||||||
public ApiResponse<object> Get()
|
public ApiResponse<object> Get()
|
||||||
{
|
{
|
||||||
|
// 1. 构造健康状态
|
||||||
var payload = new { status = "OK", service = "UserApi", time = DateTime.UtcNow };
|
var payload = new { status = "OK", service = "UserApi", time = DateTime.UtcNow };
|
||||||
|
|
||||||
|
// 2. 返回健康响应
|
||||||
return ApiResponse<object>.Ok(payload);
|
return ApiResponse<object>.Ok(payload);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,9 +12,11 @@ using TakeoutSaaS.Shared.Kernel.Ids;
|
|||||||
using TakeoutSaaS.Shared.Web.Extensions;
|
using TakeoutSaaS.Shared.Web.Extensions;
|
||||||
using TakeoutSaaS.Shared.Web.Swagger;
|
using TakeoutSaaS.Shared.Web.Swagger;
|
||||||
|
|
||||||
|
// 1. 创建构建器与日志模板
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
const string logTemplate = "[{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} {Level:u3}] [TraceId:{TraceId}] [SpanId:{SpanId}] [Service:{Service}] {SourceContext} {Message:lj}{NewLine}{Exception}";
|
const string logTemplate = "[{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} {Level:u3}] [TraceId:{TraceId}] [SpanId:{SpanId}] [Service:{Service}] {SourceContext} {Message:lj}{NewLine}{Exception}";
|
||||||
|
|
||||||
|
// 2. 注册雪花 ID 生成器与 Serilog
|
||||||
builder.Services.AddSingleton<IIdGenerator>(_ => new SnowflakeIdGenerator());
|
builder.Services.AddSingleton<IIdGenerator>(_ => new SnowflakeIdGenerator());
|
||||||
builder.Host.UseSerilog((_, _, configuration) =>
|
builder.Host.UseSerilog((_, _, configuration) =>
|
||||||
{
|
{
|
||||||
@@ -30,6 +32,7 @@ builder.Host.UseSerilog((_, _, configuration) =>
|
|||||||
outputTemplate: logTemplate);
|
outputTemplate: logTemplate);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 3. 注册通用 Web 能力与 Swagger
|
||||||
builder.Services.AddSharedWebCore();
|
builder.Services.AddSharedWebCore();
|
||||||
builder.Services.AddSharedSwagger(options =>
|
builder.Services.AddSharedSwagger(options =>
|
||||||
{
|
{
|
||||||
@@ -37,8 +40,12 @@ builder.Services.AddSharedSwagger(options =>
|
|||||||
options.Description = "C 端用户 API 文档";
|
options.Description = "C 端用户 API 文档";
|
||||||
options.EnableAuthorization = true;
|
options.EnableAuthorization = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 4. 注册多租户与健康检查
|
||||||
builder.Services.AddTenantResolution(builder.Configuration);
|
builder.Services.AddTenantResolution(builder.Configuration);
|
||||||
builder.Services.AddHealthChecks();
|
builder.Services.AddHealthChecks();
|
||||||
|
|
||||||
|
// 5. 配置 OpenTelemetry 采集
|
||||||
var otelSection = builder.Configuration.GetSection("Otel");
|
var otelSection = builder.Configuration.GetSection("Otel");
|
||||||
var otelEndpoint = otelSection.GetValue<string>("Endpoint");
|
var otelEndpoint = otelSection.GetValue<string>("Endpoint");
|
||||||
var useConsoleExporter = otelSection.GetValue<bool?>("UseConsoleExporter") ?? builder.Environment.IsDevelopment();
|
var useConsoleExporter = otelSection.GetValue<bool?>("UseConsoleExporter") ?? builder.Environment.IsDevelopment();
|
||||||
@@ -90,6 +97,7 @@ builder.Services.AddOpenTelemetry()
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 6. 配置 CORS
|
||||||
var userOrigins = ResolveCorsOrigins(builder.Configuration, "Cors:User");
|
var userOrigins = ResolveCorsOrigins(builder.Configuration, "Cors:User");
|
||||||
builder.Services.AddCors(options =>
|
builder.Services.AddCors(options =>
|
||||||
{
|
{
|
||||||
@@ -99,6 +107,7 @@ builder.Services.AddCors(options =>
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 7. 构建应用并配置中间件管道
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
|
|
||||||
app.UseCors("UserApiCors");
|
app.UseCors("UserApiCors");
|
||||||
@@ -111,6 +120,7 @@ app.MapPrometheusScrapingEndpoint();
|
|||||||
app.MapControllers();
|
app.MapControllers();
|
||||||
app.Run();
|
app.Run();
|
||||||
|
|
||||||
|
// 8. 解析配置中的 CORS 域名
|
||||||
static string[] ResolveCorsOrigins(IConfiguration configuration, string sectionKey)
|
static string[] ResolveCorsOrigins(IConfiguration configuration, string sectionKey)
|
||||||
{
|
{
|
||||||
var origins = configuration.GetSection(sectionKey).Get<string[]>();
|
var origins = configuration.GetSection(sectionKey).Get<string[]>();
|
||||||
@@ -120,6 +130,7 @@ static string[] ResolveCorsOrigins(IConfiguration configuration, string sectionK
|
|||||||
.ToArray() ?? [];
|
.ToArray() ?? [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 9. 构建 CORS 策略
|
||||||
static void ConfigureCorsPolicy(CorsPolicyBuilder policy, string[] origins)
|
static void ConfigureCorsPolicy(CorsPolicyBuilder policy, string[] origins)
|
||||||
{
|
{
|
||||||
if (origins.Length == 0)
|
if (origins.Length == 0)
|
||||||
|
|||||||
@@ -13,12 +13,10 @@ namespace TakeoutSaaS.Application.App.Deliveries.Handlers;
|
|||||||
public sealed class CreateDeliveryOrderCommandHandler(IDeliveryRepository deliveryRepository, ILogger<CreateDeliveryOrderCommandHandler> logger)
|
public sealed class CreateDeliveryOrderCommandHandler(IDeliveryRepository deliveryRepository, ILogger<CreateDeliveryOrderCommandHandler> logger)
|
||||||
: IRequestHandler<CreateDeliveryOrderCommand, DeliveryOrderDto>
|
: IRequestHandler<CreateDeliveryOrderCommand, DeliveryOrderDto>
|
||||||
{
|
{
|
||||||
private readonly IDeliveryRepository _deliveryRepository = deliveryRepository;
|
|
||||||
private readonly ILogger<CreateDeliveryOrderCommandHandler> _logger = logger;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task<DeliveryOrderDto> Handle(CreateDeliveryOrderCommand request, CancellationToken cancellationToken)
|
public async Task<DeliveryOrderDto> Handle(CreateDeliveryOrderCommand request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 构建配送单实体
|
||||||
var deliveryOrder = new DeliveryOrder
|
var deliveryOrder = new DeliveryOrder
|
||||||
{
|
{
|
||||||
OrderId = request.OrderId,
|
OrderId = request.OrderId,
|
||||||
@@ -34,10 +32,14 @@ public sealed class CreateDeliveryOrderCommandHandler(IDeliveryRepository delive
|
|||||||
FailureReason = request.FailureReason?.Trim()
|
FailureReason = request.FailureReason?.Trim()
|
||||||
};
|
};
|
||||||
|
|
||||||
await _deliveryRepository.AddDeliveryOrderAsync(deliveryOrder, cancellationToken);
|
// 2. 持久化配送单
|
||||||
await _deliveryRepository.SaveChangesAsync(cancellationToken);
|
await deliveryRepository.AddDeliveryOrderAsync(deliveryOrder, cancellationToken);
|
||||||
_logger.LogInformation("创建配送单 {DeliveryOrderId} 对应订单 {OrderId}", deliveryOrder.Id, deliveryOrder.OrderId);
|
await deliveryRepository.SaveChangesAsync(cancellationToken);
|
||||||
|
|
||||||
|
// 3. 记录日志
|
||||||
|
logger.LogInformation("创建配送单 {DeliveryOrderId} 对应订单 {OrderId}", deliveryOrder.Id, deliveryOrder.OrderId);
|
||||||
|
|
||||||
|
// 4. 映射 DTO 返回
|
||||||
return MapToDto(deliveryOrder, []);
|
return MapToDto(deliveryOrder, []);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,23 +15,23 @@ public sealed class DeleteDeliveryOrderCommandHandler(
|
|||||||
ILogger<DeleteDeliveryOrderCommandHandler> logger)
|
ILogger<DeleteDeliveryOrderCommandHandler> logger)
|
||||||
: IRequestHandler<DeleteDeliveryOrderCommand, bool>
|
: IRequestHandler<DeleteDeliveryOrderCommand, bool>
|
||||||
{
|
{
|
||||||
private readonly IDeliveryRepository _deliveryRepository = deliveryRepository;
|
|
||||||
private readonly ITenantProvider _tenantProvider = tenantProvider;
|
|
||||||
private readonly ILogger<DeleteDeliveryOrderCommandHandler> _logger = logger;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task<bool> Handle(DeleteDeliveryOrderCommand request, CancellationToken cancellationToken)
|
public async Task<bool> Handle(DeleteDeliveryOrderCommand request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var tenantId = _tenantProvider.GetCurrentTenantId();
|
// 1. 获取租户并定位配送单
|
||||||
var existing = await _deliveryRepository.FindByIdAsync(request.DeliveryOrderId, tenantId, cancellationToken);
|
var tenantId = tenantProvider.GetCurrentTenantId();
|
||||||
|
var existing = await deliveryRepository.FindByIdAsync(request.DeliveryOrderId, tenantId, cancellationToken);
|
||||||
if (existing == null)
|
if (existing == null)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
await _deliveryRepository.DeleteDeliveryOrderAsync(request.DeliveryOrderId, tenantId, cancellationToken);
|
// 2. 删除并保存
|
||||||
await _deliveryRepository.SaveChangesAsync(cancellationToken);
|
await deliveryRepository.DeleteDeliveryOrderAsync(request.DeliveryOrderId, tenantId, cancellationToken);
|
||||||
_logger.LogInformation("删除配送单 {DeliveryOrderId}", request.DeliveryOrderId);
|
await deliveryRepository.SaveChangesAsync(cancellationToken);
|
||||||
|
|
||||||
|
// 3. 记录删除日志
|
||||||
|
logger.LogInformation("删除配送单 {DeliveryOrderId}", request.DeliveryOrderId);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,20 +15,23 @@ public sealed class GetDeliveryOrderByIdQueryHandler(
|
|||||||
ITenantProvider tenantProvider)
|
ITenantProvider tenantProvider)
|
||||||
: IRequestHandler<GetDeliveryOrderByIdQuery, DeliveryOrderDto?>
|
: IRequestHandler<GetDeliveryOrderByIdQuery, DeliveryOrderDto?>
|
||||||
{
|
{
|
||||||
private readonly IDeliveryRepository _deliveryRepository = deliveryRepository;
|
|
||||||
private readonly ITenantProvider _tenantProvider = tenantProvider;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task<DeliveryOrderDto?> Handle(GetDeliveryOrderByIdQuery request, CancellationToken cancellationToken)
|
public async Task<DeliveryOrderDto?> Handle(GetDeliveryOrderByIdQuery request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var tenantId = _tenantProvider.GetCurrentTenantId();
|
// 1. 读取当前租户标识
|
||||||
var order = await _deliveryRepository.FindByIdAsync(request.DeliveryOrderId, tenantId, cancellationToken);
|
var tenantId = tenantProvider.GetCurrentTenantId();
|
||||||
|
|
||||||
|
// 2. 查询配送单主体
|
||||||
|
var order = await deliveryRepository.FindByIdAsync(request.DeliveryOrderId, tenantId, cancellationToken);
|
||||||
if (order == null)
|
if (order == null)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var events = await _deliveryRepository.GetEventsAsync(order.Id, tenantId, cancellationToken);
|
// 3. 查询配送事件明细
|
||||||
|
var events = await deliveryRepository.GetEventsAsync(order.Id, tenantId, cancellationToken);
|
||||||
|
|
||||||
|
// 4. 映射为 DTO 返回
|
||||||
return MapToDto(order, events);
|
return MapToDto(order, events);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,21 +15,25 @@ public sealed class SearchDeliveryOrdersQueryHandler(
|
|||||||
ITenantProvider tenantProvider)
|
ITenantProvider tenantProvider)
|
||||||
: IRequestHandler<SearchDeliveryOrdersQuery, PagedResult<DeliveryOrderDto>>
|
: IRequestHandler<SearchDeliveryOrdersQuery, PagedResult<DeliveryOrderDto>>
|
||||||
{
|
{
|
||||||
private readonly IDeliveryRepository _deliveryRepository = deliveryRepository;
|
|
||||||
private readonly ITenantProvider _tenantProvider = tenantProvider;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task<PagedResult<DeliveryOrderDto>> Handle(SearchDeliveryOrdersQuery request, CancellationToken cancellationToken)
|
public async Task<PagedResult<DeliveryOrderDto>> Handle(SearchDeliveryOrdersQuery request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var tenantId = _tenantProvider.GetCurrentTenantId();
|
// 1. 获取当前租户标识
|
||||||
var orders = await _deliveryRepository.SearchAsync(tenantId, request.Status, request.OrderId, cancellationToken);
|
var tenantId = tenantProvider.GetCurrentTenantId();
|
||||||
|
|
||||||
|
// 2. 查询配送单列表(租户隔离)
|
||||||
|
var orders = await deliveryRepository.SearchAsync(tenantId, request.Status, request.OrderId, cancellationToken);
|
||||||
|
|
||||||
|
// 3. 本地排序
|
||||||
var sorted = ApplySorting(orders, request.SortBy, request.SortDescending);
|
var sorted = ApplySorting(orders, request.SortBy, request.SortDescending);
|
||||||
|
|
||||||
|
// 4. 本地分页
|
||||||
var paged = sorted
|
var paged = sorted
|
||||||
.Skip((request.Page - 1) * request.PageSize)
|
.Skip((request.Page - 1) * request.PageSize)
|
||||||
.Take(request.PageSize)
|
.Take(request.PageSize)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
|
// 5. 映射 DTO
|
||||||
var items = paged.Select(order => new DeliveryOrderDto
|
var items = paged.Select(order => new DeliveryOrderDto
|
||||||
{
|
{
|
||||||
Id = order.Id,
|
Id = order.Id,
|
||||||
@@ -48,6 +52,7 @@ public sealed class SearchDeliveryOrdersQueryHandler(
|
|||||||
CreatedAt = order.CreatedAt
|
CreatedAt = order.CreatedAt
|
||||||
}).ToList();
|
}).ToList();
|
||||||
|
|
||||||
|
// 6. 返回分页结果
|
||||||
return new PagedResult<DeliveryOrderDto>(items, request.Page, request.PageSize, orders.Count);
|
return new PagedResult<DeliveryOrderDto>(items, request.Page, request.PageSize, orders.Count);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,20 +17,20 @@ public sealed class UpdateDeliveryOrderCommandHandler(
|
|||||||
ILogger<UpdateDeliveryOrderCommandHandler> logger)
|
ILogger<UpdateDeliveryOrderCommandHandler> logger)
|
||||||
: IRequestHandler<UpdateDeliveryOrderCommand, DeliveryOrderDto?>
|
: IRequestHandler<UpdateDeliveryOrderCommand, DeliveryOrderDto?>
|
||||||
{
|
{
|
||||||
private readonly IDeliveryRepository _deliveryRepository = deliveryRepository;
|
|
||||||
private readonly ITenantProvider _tenantProvider = tenantProvider;
|
|
||||||
private readonly ILogger<UpdateDeliveryOrderCommandHandler> _logger = logger;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task<DeliveryOrderDto?> Handle(UpdateDeliveryOrderCommand request, CancellationToken cancellationToken)
|
public async Task<DeliveryOrderDto?> Handle(UpdateDeliveryOrderCommand request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var tenantId = _tenantProvider.GetCurrentTenantId();
|
// 1. 获取当前租户标识
|
||||||
var existing = await _deliveryRepository.FindByIdAsync(request.DeliveryOrderId, tenantId, cancellationToken);
|
var tenantId = tenantProvider.GetCurrentTenantId();
|
||||||
|
|
||||||
|
// 2. 查询目标配送单
|
||||||
|
var existing = await deliveryRepository.FindByIdAsync(request.DeliveryOrderId, tenantId, cancellationToken);
|
||||||
if (existing == null)
|
if (existing == null)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 3. 更新字段
|
||||||
existing.OrderId = request.OrderId;
|
existing.OrderId = request.OrderId;
|
||||||
existing.Provider = request.Provider;
|
existing.Provider = request.Provider;
|
||||||
existing.ProviderOrderId = request.ProviderOrderId?.Trim();
|
existing.ProviderOrderId = request.ProviderOrderId?.Trim();
|
||||||
@@ -43,11 +43,15 @@ public sealed class UpdateDeliveryOrderCommandHandler(
|
|||||||
existing.DeliveredAt = request.DeliveredAt;
|
existing.DeliveredAt = request.DeliveredAt;
|
||||||
existing.FailureReason = request.FailureReason?.Trim();
|
existing.FailureReason = request.FailureReason?.Trim();
|
||||||
|
|
||||||
await _deliveryRepository.UpdateDeliveryOrderAsync(existing, cancellationToken);
|
// 4. 持久化变更
|
||||||
await _deliveryRepository.SaveChangesAsync(cancellationToken);
|
await deliveryRepository.UpdateDeliveryOrderAsync(existing, cancellationToken);
|
||||||
_logger.LogInformation("更新配送单 {DeliveryOrderId}", existing.Id);
|
await deliveryRepository.SaveChangesAsync(cancellationToken);
|
||||||
|
|
||||||
var events = await _deliveryRepository.GetEventsAsync(existing.Id, tenantId, cancellationToken);
|
// 5. 记录更新日志
|
||||||
|
logger.LogInformation("更新配送单 {DeliveryOrderId}", existing.Id);
|
||||||
|
|
||||||
|
// 6. 查询事件并返回映射结果
|
||||||
|
var events = await deliveryRepository.GetEventsAsync(existing.Id, tenantId, cancellationToken);
|
||||||
return MapToDto(existing, events);
|
return MapToDto(existing, events);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,20 +22,17 @@ public sealed class AddMerchantDocumentCommandHandler(
|
|||||||
ICurrentUserAccessor currentUserAccessor)
|
ICurrentUserAccessor currentUserAccessor)
|
||||||
: IRequestHandler<AddMerchantDocumentCommand, MerchantDocumentDto>
|
: IRequestHandler<AddMerchantDocumentCommand, MerchantDocumentDto>
|
||||||
{
|
{
|
||||||
private readonly IMerchantRepository _merchantRepository = merchantRepository;
|
|
||||||
private readonly ITenantProvider _tenantProvider = tenantProvider;
|
|
||||||
private readonly IIdGenerator _idGenerator = idGenerator;
|
|
||||||
private readonly ICurrentUserAccessor _currentUserAccessor = currentUserAccessor;
|
|
||||||
|
|
||||||
public async Task<MerchantDocumentDto> Handle(AddMerchantDocumentCommand request, CancellationToken cancellationToken)
|
public async Task<MerchantDocumentDto> Handle(AddMerchantDocumentCommand request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var tenantId = _tenantProvider.GetCurrentTenantId();
|
// 1. 获取租户并查询商户
|
||||||
var merchant = await _merchantRepository.FindByIdAsync(request.MerchantId, tenantId, cancellationToken)
|
var tenantId = tenantProvider.GetCurrentTenantId();
|
||||||
|
var merchant = await merchantRepository.FindByIdAsync(request.MerchantId, tenantId, cancellationToken)
|
||||||
?? throw new BusinessException(ErrorCodes.NotFound, "商户不存在");
|
?? throw new BusinessException(ErrorCodes.NotFound, "商户不存在");
|
||||||
|
|
||||||
|
// 2. 构建证照记录
|
||||||
var document = new MerchantDocument
|
var document = new MerchantDocument
|
||||||
{
|
{
|
||||||
Id = _idGenerator.NextId(),
|
Id = idGenerator.NextId(),
|
||||||
MerchantId = merchant.Id,
|
MerchantId = merchant.Id,
|
||||||
DocumentType = request.DocumentType,
|
DocumentType = request.DocumentType,
|
||||||
Status = MerchantDocumentStatus.Pending,
|
Status = MerchantDocumentStatus.Pending,
|
||||||
@@ -45,8 +42,9 @@ public sealed class AddMerchantDocumentCommandHandler(
|
|||||||
ExpiresAt = request.ExpiresAt
|
ExpiresAt = request.ExpiresAt
|
||||||
};
|
};
|
||||||
|
|
||||||
await _merchantRepository.AddDocumentAsync(document, cancellationToken);
|
// 3. 持久化与审计
|
||||||
await _merchantRepository.AddAuditLogAsync(new MerchantAuditLog
|
await merchantRepository.AddDocumentAsync(document, cancellationToken);
|
||||||
|
await merchantRepository.AddAuditLogAsync(new MerchantAuditLog
|
||||||
{
|
{
|
||||||
TenantId = tenantId,
|
TenantId = tenantId,
|
||||||
MerchantId = merchant.Id,
|
MerchantId = merchant.Id,
|
||||||
@@ -57,20 +55,21 @@ public sealed class AddMerchantDocumentCommandHandler(
|
|||||||
OperatorName = ResolveOperatorName()
|
OperatorName = ResolveOperatorName()
|
||||||
}, cancellationToken);
|
}, cancellationToken);
|
||||||
|
|
||||||
await _merchantRepository.SaveChangesAsync(cancellationToken);
|
await merchantRepository.SaveChangesAsync(cancellationToken);
|
||||||
|
|
||||||
|
// 4. 返回 DTO
|
||||||
return MerchantMapping.ToDto(document);
|
return MerchantMapping.ToDto(document);
|
||||||
}
|
}
|
||||||
|
|
||||||
private long? ResolveOperatorId()
|
private long? ResolveOperatorId()
|
||||||
{
|
{
|
||||||
var id = _currentUserAccessor.UserId;
|
var id = currentUserAccessor.UserId;
|
||||||
return id == 0 ? null : id;
|
return id == 0 ? null : id;
|
||||||
}
|
}
|
||||||
|
|
||||||
private string ResolveOperatorName()
|
private string ResolveOperatorName()
|
||||||
{
|
{
|
||||||
var id = _currentUserAccessor.UserId;
|
var id = currentUserAccessor.UserId;
|
||||||
return id == 0 ? "system" : $"user:{id}";
|
return id == 0 ? "system" : $"user:{id}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,22 +18,23 @@ public sealed class CreateMerchantCategoryCommandHandler(
|
|||||||
ITenantProvider tenantProvider)
|
ITenantProvider tenantProvider)
|
||||||
: IRequestHandler<CreateMerchantCategoryCommand, MerchantCategoryDto>
|
: IRequestHandler<CreateMerchantCategoryCommand, MerchantCategoryDto>
|
||||||
{
|
{
|
||||||
private readonly IMerchantCategoryRepository _categoryRepository = categoryRepository;
|
|
||||||
private readonly ITenantProvider _tenantProvider = tenantProvider;
|
|
||||||
|
|
||||||
public async Task<MerchantCategoryDto> Handle(CreateMerchantCategoryCommand request, CancellationToken cancellationToken)
|
public async Task<MerchantCategoryDto> Handle(CreateMerchantCategoryCommand request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var tenantId = _tenantProvider.GetCurrentTenantId();
|
// 1. 获取租户上下文
|
||||||
|
var tenantId = tenantProvider.GetCurrentTenantId();
|
||||||
var normalizedName = request.Name.Trim();
|
var normalizedName = request.Name.Trim();
|
||||||
|
|
||||||
if (await _categoryRepository.ExistsAsync(normalizedName, tenantId, cancellationToken))
|
// 2. 检查重名
|
||||||
|
if (await categoryRepository.ExistsAsync(normalizedName, tenantId, cancellationToken))
|
||||||
{
|
{
|
||||||
throw new BusinessException(ErrorCodes.Conflict, $"类目“{normalizedName}”已存在");
|
throw new BusinessException(ErrorCodes.Conflict, $"类目“{normalizedName}”已存在");
|
||||||
}
|
}
|
||||||
|
|
||||||
var categories = await _categoryRepository.ListAsync(tenantId, cancellationToken);
|
// 3. 计算排序
|
||||||
|
var categories = await categoryRepository.ListAsync(tenantId, cancellationToken);
|
||||||
var targetOrder = request.DisplayOrder ?? (categories.Count == 0 ? 1 : categories.Max(x => x.DisplayOrder) + 1);
|
var targetOrder = request.DisplayOrder ?? (categories.Count == 0 ? 1 : categories.Max(x => x.DisplayOrder) + 1);
|
||||||
|
|
||||||
|
// 4. 构建实体
|
||||||
var entity = new MerchantCategory
|
var entity = new MerchantCategory
|
||||||
{
|
{
|
||||||
Name = normalizedName,
|
Name = normalizedName,
|
||||||
@@ -41,8 +42,9 @@ public sealed class CreateMerchantCategoryCommandHandler(
|
|||||||
IsActive = request.IsActive
|
IsActive = request.IsActive
|
||||||
};
|
};
|
||||||
|
|
||||||
await _categoryRepository.AddAsync(entity, cancellationToken);
|
// 5. 持久化并返回
|
||||||
await _categoryRepository.SaveChangesAsync(cancellationToken);
|
await categoryRepository.AddAsync(entity, cancellationToken);
|
||||||
|
await categoryRepository.SaveChangesAsync(cancellationToken);
|
||||||
|
|
||||||
return MerchantMapping.ToDto(entity);
|
return MerchantMapping.ToDto(entity);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,12 +13,10 @@ namespace TakeoutSaaS.Application.App.Merchants.Handlers;
|
|||||||
public sealed class CreateMerchantCommandHandler(IMerchantRepository merchantRepository, ILogger<CreateMerchantCommandHandler> logger)
|
public sealed class CreateMerchantCommandHandler(IMerchantRepository merchantRepository, ILogger<CreateMerchantCommandHandler> logger)
|
||||||
: IRequestHandler<CreateMerchantCommand, MerchantDto>
|
: IRequestHandler<CreateMerchantCommand, MerchantDto>
|
||||||
{
|
{
|
||||||
private readonly IMerchantRepository _merchantRepository = merchantRepository;
|
|
||||||
private readonly ILogger<CreateMerchantCommandHandler> _logger = logger;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task<MerchantDto> Handle(CreateMerchantCommand request, CancellationToken cancellationToken)
|
public async Task<MerchantDto> Handle(CreateMerchantCommand request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 构建商户实体
|
||||||
var merchant = new Merchant
|
var merchant = new Merchant
|
||||||
{
|
{
|
||||||
BrandName = request.BrandName.Trim(),
|
BrandName = request.BrandName.Trim(),
|
||||||
@@ -31,10 +29,12 @@ public sealed class CreateMerchantCommandHandler(IMerchantRepository merchantRep
|
|||||||
JoinedAt = DateTime.UtcNow
|
JoinedAt = DateTime.UtcNow
|
||||||
};
|
};
|
||||||
|
|
||||||
await _merchantRepository.AddMerchantAsync(merchant, cancellationToken);
|
// 2. 持久化
|
||||||
await _merchantRepository.SaveChangesAsync(cancellationToken);
|
await merchantRepository.AddMerchantAsync(merchant, cancellationToken);
|
||||||
|
await merchantRepository.SaveChangesAsync(cancellationToken);
|
||||||
|
|
||||||
_logger.LogInformation("创建商户 {MerchantId} - {BrandName}", merchant.Id, merchant.BrandName);
|
// 3. 记录日志
|
||||||
|
logger.LogInformation("创建商户 {MerchantId} - {BrandName}", merchant.Id, merchant.BrandName);
|
||||||
return MapToDto(merchant);
|
return MapToDto(merchant);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,25 +22,23 @@ public sealed class CreateMerchantContractCommandHandler(
|
|||||||
ICurrentUserAccessor currentUserAccessor)
|
ICurrentUserAccessor currentUserAccessor)
|
||||||
: IRequestHandler<CreateMerchantContractCommand, MerchantContractDto>
|
: IRequestHandler<CreateMerchantContractCommand, MerchantContractDto>
|
||||||
{
|
{
|
||||||
private readonly IMerchantRepository _merchantRepository = merchantRepository;
|
|
||||||
private readonly ITenantProvider _tenantProvider = tenantProvider;
|
|
||||||
private readonly IIdGenerator _idGenerator = idGenerator;
|
|
||||||
private readonly ICurrentUserAccessor _currentUserAccessor = currentUserAccessor;
|
|
||||||
|
|
||||||
public async Task<MerchantContractDto> Handle(CreateMerchantContractCommand request, CancellationToken cancellationToken)
|
public async Task<MerchantContractDto> Handle(CreateMerchantContractCommand request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 校验时间
|
||||||
if (request.EndDate <= request.StartDate)
|
if (request.EndDate <= request.StartDate)
|
||||||
{
|
{
|
||||||
throw new BusinessException(ErrorCodes.BadRequest, "合同结束时间必须晚于开始时间");
|
throw new BusinessException(ErrorCodes.BadRequest, "合同结束时间必须晚于开始时间");
|
||||||
}
|
}
|
||||||
|
|
||||||
var tenantId = _tenantProvider.GetCurrentTenantId();
|
// 2. 查询商户
|
||||||
var merchant = await _merchantRepository.FindByIdAsync(request.MerchantId, tenantId, cancellationToken)
|
var tenantId = tenantProvider.GetCurrentTenantId();
|
||||||
|
var merchant = await merchantRepository.FindByIdAsync(request.MerchantId, tenantId, cancellationToken)
|
||||||
?? throw new BusinessException(ErrorCodes.NotFound, "商户不存在");
|
?? throw new BusinessException(ErrorCodes.NotFound, "商户不存在");
|
||||||
|
|
||||||
|
// 3. 构建合同
|
||||||
var contract = new MerchantContract
|
var contract = new MerchantContract
|
||||||
{
|
{
|
||||||
Id = _idGenerator.NextId(),
|
Id = idGenerator.NextId(),
|
||||||
MerchantId = merchant.Id,
|
MerchantId = merchant.Id,
|
||||||
ContractNumber = request.ContractNumber.Trim(),
|
ContractNumber = request.ContractNumber.Trim(),
|
||||||
StartDate = request.StartDate,
|
StartDate = request.StartDate,
|
||||||
@@ -48,8 +46,9 @@ public sealed class CreateMerchantContractCommandHandler(
|
|||||||
FileUrl = request.FileUrl.Trim()
|
FileUrl = request.FileUrl.Trim()
|
||||||
};
|
};
|
||||||
|
|
||||||
await _merchantRepository.AddContractAsync(contract, cancellationToken);
|
// 4. 持久化与审计
|
||||||
await _merchantRepository.AddAuditLogAsync(new MerchantAuditLog
|
await merchantRepository.AddContractAsync(contract, cancellationToken);
|
||||||
|
await merchantRepository.AddAuditLogAsync(new MerchantAuditLog
|
||||||
{
|
{
|
||||||
TenantId = tenantId,
|
TenantId = tenantId,
|
||||||
MerchantId = merchant.Id,
|
MerchantId = merchant.Id,
|
||||||
@@ -60,19 +59,21 @@ public sealed class CreateMerchantContractCommandHandler(
|
|||||||
OperatorName = ResolveOperatorName()
|
OperatorName = ResolveOperatorName()
|
||||||
}, cancellationToken);
|
}, cancellationToken);
|
||||||
|
|
||||||
await _merchantRepository.SaveChangesAsync(cancellationToken);
|
await merchantRepository.SaveChangesAsync(cancellationToken);
|
||||||
|
|
||||||
|
// 5. 返回 DTO
|
||||||
return MerchantMapping.ToDto(contract);
|
return MerchantMapping.ToDto(contract);
|
||||||
}
|
}
|
||||||
|
|
||||||
private long? ResolveOperatorId()
|
private long? ResolveOperatorId()
|
||||||
{
|
{
|
||||||
var id = _currentUserAccessor.UserId;
|
var id = currentUserAccessor.UserId;
|
||||||
return id == 0 ? null : id;
|
return id == 0 ? null : id;
|
||||||
}
|
}
|
||||||
|
|
||||||
private string ResolveOperatorName()
|
private string ResolveOperatorName()
|
||||||
{
|
{
|
||||||
var id = _currentUserAccessor.UserId;
|
var id = currentUserAccessor.UserId;
|
||||||
return id == 0 ? "system" : $"user:{id}";
|
return id == 0 ? "system" : $"user:{id}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,21 +13,20 @@ public sealed class DeleteMerchantCategoryCommandHandler(
|
|||||||
ITenantProvider tenantProvider)
|
ITenantProvider tenantProvider)
|
||||||
: IRequestHandler<DeleteMerchantCategoryCommand, bool>
|
: IRequestHandler<DeleteMerchantCategoryCommand, bool>
|
||||||
{
|
{
|
||||||
private readonly IMerchantCategoryRepository _categoryRepository = categoryRepository;
|
|
||||||
private readonly ITenantProvider _tenantProvider = tenantProvider;
|
|
||||||
|
|
||||||
public async Task<bool> Handle(DeleteMerchantCategoryCommand request, CancellationToken cancellationToken)
|
public async Task<bool> Handle(DeleteMerchantCategoryCommand request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var tenantId = _tenantProvider.GetCurrentTenantId();
|
// 1. 获取租户上下文
|
||||||
var existing = await _categoryRepository.FindByIdAsync(request.CategoryId, tenantId, cancellationToken);
|
var tenantId = tenantProvider.GetCurrentTenantId();
|
||||||
|
var existing = await categoryRepository.FindByIdAsync(request.CategoryId, tenantId, cancellationToken);
|
||||||
|
|
||||||
if (existing == null)
|
if (existing == null)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
await _categoryRepository.RemoveAsync(existing, cancellationToken);
|
// 2. 删除并保存
|
||||||
await _categoryRepository.SaveChangesAsync(cancellationToken);
|
await categoryRepository.RemoveAsync(existing, cancellationToken);
|
||||||
|
await categoryRepository.SaveChangesAsync(cancellationToken);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,25 +15,21 @@ public sealed class DeleteMerchantCommandHandler(
|
|||||||
ILogger<DeleteMerchantCommandHandler> logger)
|
ILogger<DeleteMerchantCommandHandler> logger)
|
||||||
: IRequestHandler<DeleteMerchantCommand, bool>
|
: IRequestHandler<DeleteMerchantCommand, bool>
|
||||||
{
|
{
|
||||||
private readonly IMerchantRepository _merchantRepository = merchantRepository;
|
|
||||||
private readonly ITenantProvider _tenantProvider = tenantProvider;
|
|
||||||
private readonly ILogger<DeleteMerchantCommandHandler> _logger = logger;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task<bool> Handle(DeleteMerchantCommand request, CancellationToken cancellationToken)
|
public async Task<bool> Handle(DeleteMerchantCommand request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
// 1. 校验存在性
|
// 1. 校验存在性
|
||||||
var tenantId = _tenantProvider.GetCurrentTenantId();
|
var tenantId = tenantProvider.GetCurrentTenantId();
|
||||||
var existing = await _merchantRepository.FindByIdAsync(request.MerchantId, tenantId, cancellationToken);
|
var existing = await merchantRepository.FindByIdAsync(request.MerchantId, tenantId, cancellationToken);
|
||||||
if (existing == null)
|
if (existing == null)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. 删除
|
// 2. 删除
|
||||||
await _merchantRepository.DeleteMerchantAsync(request.MerchantId, tenantId, cancellationToken);
|
await merchantRepository.DeleteMerchantAsync(request.MerchantId, tenantId, cancellationToken);
|
||||||
await _merchantRepository.SaveChangesAsync(cancellationToken);
|
await merchantRepository.SaveChangesAsync(cancellationToken);
|
||||||
_logger.LogInformation("删除商户 {MerchantId}", request.MerchantId);
|
logger.LogInformation("删除商户 {MerchantId}", request.MerchantId);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,20 +16,21 @@ public sealed class GetMerchantAuditLogsQueryHandler(
|
|||||||
ITenantProvider tenantProvider)
|
ITenantProvider tenantProvider)
|
||||||
: IRequestHandler<GetMerchantAuditLogsQuery, PagedResult<MerchantAuditLogDto>>
|
: IRequestHandler<GetMerchantAuditLogsQuery, PagedResult<MerchantAuditLogDto>>
|
||||||
{
|
{
|
||||||
private readonly IMerchantRepository _merchantRepository = merchantRepository;
|
|
||||||
private readonly ITenantProvider _tenantProvider = tenantProvider;
|
|
||||||
|
|
||||||
public async Task<PagedResult<MerchantAuditLogDto>> Handle(GetMerchantAuditLogsQuery request, CancellationToken cancellationToken)
|
public async Task<PagedResult<MerchantAuditLogDto>> Handle(GetMerchantAuditLogsQuery request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var tenantId = _tenantProvider.GetCurrentTenantId();
|
// 1. 获取租户上下文并查询日志
|
||||||
var logs = await _merchantRepository.GetAuditLogsAsync(request.MerchantId, tenantId, cancellationToken);
|
var tenantId = tenantProvider.GetCurrentTenantId();
|
||||||
|
var logs = await merchantRepository.GetAuditLogsAsync(request.MerchantId, tenantId, cancellationToken);
|
||||||
var total = logs.Count;
|
var total = logs.Count;
|
||||||
|
|
||||||
|
// 2. 分页映射
|
||||||
var paged = logs
|
var paged = logs
|
||||||
.Skip((request.Page - 1) * request.PageSize)
|
.Skip((request.Page - 1) * request.PageSize)
|
||||||
.Take(request.PageSize)
|
.Take(request.PageSize)
|
||||||
.Select(MerchantMapping.ToDto)
|
.Select(MerchantMapping.ToDto)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
|
// 3. 返回结果
|
||||||
return new PagedResult<MerchantAuditLogDto>(paged, request.Page, request.PageSize, total);
|
return new PagedResult<MerchantAuditLogDto>(paged, request.Page, request.PageSize, total);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,19 +12,18 @@ namespace TakeoutSaaS.Application.App.Merchants.Handlers;
|
|||||||
public sealed class GetMerchantByIdQueryHandler(IMerchantRepository merchantRepository, ITenantProvider tenantProvider)
|
public sealed class GetMerchantByIdQueryHandler(IMerchantRepository merchantRepository, ITenantProvider tenantProvider)
|
||||||
: IRequestHandler<GetMerchantByIdQuery, MerchantDto?>
|
: IRequestHandler<GetMerchantByIdQuery, MerchantDto?>
|
||||||
{
|
{
|
||||||
private readonly IMerchantRepository _merchantRepository = merchantRepository;
|
|
||||||
private readonly ITenantProvider _tenantProvider = tenantProvider;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task<MerchantDto?> Handle(GetMerchantByIdQuery request, CancellationToken cancellationToken)
|
public async Task<MerchantDto?> Handle(GetMerchantByIdQuery request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var tenantId = _tenantProvider.GetCurrentTenantId();
|
// 1. 获取租户上下文
|
||||||
var merchant = await _merchantRepository.FindByIdAsync(request.MerchantId, tenantId, cancellationToken);
|
var tenantId = tenantProvider.GetCurrentTenantId();
|
||||||
|
var merchant = await merchantRepository.FindByIdAsync(request.MerchantId, tenantId, cancellationToken);
|
||||||
if (merchant == null)
|
if (merchant == null)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 2. 返回 DTO
|
||||||
return new MerchantDto
|
return new MerchantDto
|
||||||
{
|
{
|
||||||
Id = merchant.Id,
|
Id = merchant.Id,
|
||||||
|
|||||||
@@ -15,14 +15,13 @@ public sealed class GetMerchantCategoriesQueryHandler(
|
|||||||
ITenantProvider tenantProvider)
|
ITenantProvider tenantProvider)
|
||||||
: IRequestHandler<GetMerchantCategoriesQuery, IReadOnlyList<string>>
|
: IRequestHandler<GetMerchantCategoriesQuery, IReadOnlyList<string>>
|
||||||
{
|
{
|
||||||
private readonly IMerchantCategoryRepository _categoryRepository = categoryRepository;
|
|
||||||
private readonly ITenantProvider _tenantProvider = tenantProvider;
|
|
||||||
|
|
||||||
public async Task<IReadOnlyList<string>> Handle(GetMerchantCategoriesQuery request, CancellationToken cancellationToken)
|
public async Task<IReadOnlyList<string>> Handle(GetMerchantCategoriesQuery request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var tenantId = _tenantProvider.GetCurrentTenantId();
|
// 1. 获取租户上下文并读取类目
|
||||||
var categories = await _categoryRepository.ListAsync(tenantId, cancellationToken);
|
var tenantId = tenantProvider.GetCurrentTenantId();
|
||||||
|
var categories = await categoryRepository.ListAsync(tenantId, cancellationToken);
|
||||||
|
|
||||||
|
// 2. 过滤启用类目并去重
|
||||||
return categories
|
return categories
|
||||||
.Where(x => x.IsActive)
|
.Where(x => x.IsActive)
|
||||||
.Select(x => x.Name.Trim())
|
.Select(x => x.Name.Trim())
|
||||||
|
|||||||
@@ -17,16 +17,15 @@ public sealed class GetMerchantContractsQueryHandler(
|
|||||||
ITenantProvider tenantProvider)
|
ITenantProvider tenantProvider)
|
||||||
: IRequestHandler<GetMerchantContractsQuery, IReadOnlyList<MerchantContractDto>>
|
: IRequestHandler<GetMerchantContractsQuery, IReadOnlyList<MerchantContractDto>>
|
||||||
{
|
{
|
||||||
private readonly IMerchantRepository _merchantRepository = merchantRepository;
|
|
||||||
private readonly ITenantProvider _tenantProvider = tenantProvider;
|
|
||||||
|
|
||||||
public async Task<IReadOnlyList<MerchantContractDto>> Handle(GetMerchantContractsQuery request, CancellationToken cancellationToken)
|
public async Task<IReadOnlyList<MerchantContractDto>> Handle(GetMerchantContractsQuery request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var tenantId = _tenantProvider.GetCurrentTenantId();
|
// 1. 获取租户上下文并校验商户存在
|
||||||
_ = await _merchantRepository.FindByIdAsync(request.MerchantId, tenantId, cancellationToken)
|
var tenantId = tenantProvider.GetCurrentTenantId();
|
||||||
|
_ = await merchantRepository.FindByIdAsync(request.MerchantId, tenantId, cancellationToken)
|
||||||
?? throw new BusinessException(ErrorCodes.NotFound, "商户不存在");
|
?? throw new BusinessException(ErrorCodes.NotFound, "商户不存在");
|
||||||
|
|
||||||
var contracts = await _merchantRepository.GetContractsAsync(request.MerchantId, tenantId, cancellationToken);
|
// 2. 查询合同列表
|
||||||
|
var contracts = await merchantRepository.GetContractsAsync(request.MerchantId, tenantId, cancellationToken);
|
||||||
return MerchantMapping.ToContractDtos(contracts);
|
return MerchantMapping.ToContractDtos(contracts);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,18 +16,18 @@ public sealed class GetMerchantDetailQueryHandler(
|
|||||||
ITenantProvider tenantProvider)
|
ITenantProvider tenantProvider)
|
||||||
: IRequestHandler<GetMerchantDetailQuery, MerchantDetailDto>
|
: IRequestHandler<GetMerchantDetailQuery, MerchantDetailDto>
|
||||||
{
|
{
|
||||||
private readonly IMerchantRepository _merchantRepository = merchantRepository;
|
|
||||||
private readonly ITenantProvider _tenantProvider = tenantProvider;
|
|
||||||
|
|
||||||
public async Task<MerchantDetailDto> Handle(GetMerchantDetailQuery request, CancellationToken cancellationToken)
|
public async Task<MerchantDetailDto> Handle(GetMerchantDetailQuery request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var tenantId = _tenantProvider.GetCurrentTenantId();
|
// 1. 获取租户上下文并查询商户
|
||||||
var merchant = await _merchantRepository.FindByIdAsync(request.MerchantId, tenantId, cancellationToken)
|
var tenantId = tenantProvider.GetCurrentTenantId();
|
||||||
|
var merchant = await merchantRepository.FindByIdAsync(request.MerchantId, tenantId, cancellationToken)
|
||||||
?? throw new BusinessException(ErrorCodes.NotFound, "商户不存在");
|
?? throw new BusinessException(ErrorCodes.NotFound, "商户不存在");
|
||||||
|
|
||||||
var documents = await _merchantRepository.GetDocumentsAsync(request.MerchantId, tenantId, cancellationToken);
|
// 2. 查询证照与合同
|
||||||
var contracts = await _merchantRepository.GetContractsAsync(request.MerchantId, tenantId, cancellationToken);
|
var documents = await merchantRepository.GetDocumentsAsync(request.MerchantId, tenantId, cancellationToken);
|
||||||
|
var contracts = await merchantRepository.GetContractsAsync(request.MerchantId, tenantId, cancellationToken);
|
||||||
|
|
||||||
|
// 3. 返回明细 DTO
|
||||||
return new MerchantDetailDto
|
return new MerchantDetailDto
|
||||||
{
|
{
|
||||||
Merchant = MerchantMapping.ToDto(merchant),
|
Merchant = MerchantMapping.ToDto(merchant),
|
||||||
|
|||||||
@@ -17,16 +17,15 @@ public sealed class GetMerchantDocumentsQueryHandler(
|
|||||||
ITenantProvider tenantProvider)
|
ITenantProvider tenantProvider)
|
||||||
: IRequestHandler<GetMerchantDocumentsQuery, IReadOnlyList<MerchantDocumentDto>>
|
: IRequestHandler<GetMerchantDocumentsQuery, IReadOnlyList<MerchantDocumentDto>>
|
||||||
{
|
{
|
||||||
private readonly IMerchantRepository _merchantRepository = merchantRepository;
|
|
||||||
private readonly ITenantProvider _tenantProvider = tenantProvider;
|
|
||||||
|
|
||||||
public async Task<IReadOnlyList<MerchantDocumentDto>> Handle(GetMerchantDocumentsQuery request, CancellationToken cancellationToken)
|
public async Task<IReadOnlyList<MerchantDocumentDto>> Handle(GetMerchantDocumentsQuery request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var tenantId = _tenantProvider.GetCurrentTenantId();
|
// 1. 获取租户上下文并校验商户存在
|
||||||
_ = await _merchantRepository.FindByIdAsync(request.MerchantId, tenantId, cancellationToken)
|
var tenantId = tenantProvider.GetCurrentTenantId();
|
||||||
|
_ = await merchantRepository.FindByIdAsync(request.MerchantId, tenantId, cancellationToken)
|
||||||
?? throw new BusinessException(ErrorCodes.NotFound, "商户不存在");
|
?? throw new BusinessException(ErrorCodes.NotFound, "商户不存在");
|
||||||
|
|
||||||
var documents = await _merchantRepository.GetDocumentsAsync(request.MerchantId, tenantId, cancellationToken);
|
// 2. 查询证照列表
|
||||||
|
var documents = await merchantRepository.GetDocumentsAsync(request.MerchantId, tenantId, cancellationToken);
|
||||||
return MerchantMapping.ToDocumentDtos(documents);
|
return MerchantMapping.ToDocumentDtos(documents);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,13 +15,13 @@ public sealed class ListMerchantCategoriesQueryHandler(
|
|||||||
ITenantProvider tenantProvider)
|
ITenantProvider tenantProvider)
|
||||||
: IRequestHandler<ListMerchantCategoriesQuery, IReadOnlyList<MerchantCategoryDto>>
|
: IRequestHandler<ListMerchantCategoriesQuery, IReadOnlyList<MerchantCategoryDto>>
|
||||||
{
|
{
|
||||||
private readonly IMerchantCategoryRepository _categoryRepository = categoryRepository;
|
|
||||||
private readonly ITenantProvider _tenantProvider = tenantProvider;
|
|
||||||
|
|
||||||
public async Task<IReadOnlyList<MerchantCategoryDto>> Handle(ListMerchantCategoriesQuery request, CancellationToken cancellationToken)
|
public async Task<IReadOnlyList<MerchantCategoryDto>> Handle(ListMerchantCategoriesQuery request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var tenantId = _tenantProvider.GetCurrentTenantId();
|
// 1. 获取租户上下文
|
||||||
var categories = await _categoryRepository.ListAsync(tenantId, cancellationToken);
|
var tenantId = tenantProvider.GetCurrentTenantId();
|
||||||
|
var categories = await categoryRepository.ListAsync(tenantId, cancellationToken);
|
||||||
|
|
||||||
|
// 2. 映射 DTO
|
||||||
return MerchantMapping.ToCategoryDtos(categories);
|
return MerchantMapping.ToCategoryDtos(categories);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,15 +16,14 @@ public sealed class ReorderMerchantCategoriesCommandHandler(
|
|||||||
ITenantProvider tenantProvider)
|
ITenantProvider tenantProvider)
|
||||||
: IRequestHandler<ReorderMerchantCategoriesCommand, bool>
|
: IRequestHandler<ReorderMerchantCategoriesCommand, bool>
|
||||||
{
|
{
|
||||||
private readonly IMerchantCategoryRepository _categoryRepository = categoryRepository;
|
|
||||||
private readonly ITenantProvider _tenantProvider = tenantProvider;
|
|
||||||
|
|
||||||
public async Task<bool> Handle(ReorderMerchantCategoriesCommand request, CancellationToken cancellationToken)
|
public async Task<bool> Handle(ReorderMerchantCategoriesCommand request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var tenantId = _tenantProvider.GetCurrentTenantId();
|
// 1. 获取租户并查询类目
|
||||||
var categories = await _categoryRepository.ListAsync(tenantId, cancellationToken);
|
var tenantId = tenantProvider.GetCurrentTenantId();
|
||||||
|
var categories = await categoryRepository.ListAsync(tenantId, cancellationToken);
|
||||||
var map = categories.ToDictionary(x => x.Id);
|
var map = categories.ToDictionary(x => x.Id);
|
||||||
|
|
||||||
|
// 2. 更新排序
|
||||||
foreach (var item in request.Items)
|
foreach (var item in request.Items)
|
||||||
{
|
{
|
||||||
if (!map.TryGetValue(item.CategoryId, out var category))
|
if (!map.TryGetValue(item.CategoryId, out var category))
|
||||||
@@ -35,8 +34,9 @@ public sealed class ReorderMerchantCategoriesCommandHandler(
|
|||||||
category.DisplayOrder = item.DisplayOrder;
|
category.DisplayOrder = item.DisplayOrder;
|
||||||
}
|
}
|
||||||
|
|
||||||
await _categoryRepository.UpdateRangeAsync(map.Values, cancellationToken);
|
// 3. 持久化
|
||||||
await _categoryRepository.SaveChangesAsync(cancellationToken);
|
await categoryRepository.UpdateRangeAsync(map.Values, cancellationToken);
|
||||||
|
await categoryRepository.SaveChangesAsync(cancellationToken);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,21 +20,20 @@ public sealed class ReviewMerchantCommandHandler(
|
|||||||
ICurrentUserAccessor currentUserAccessor)
|
ICurrentUserAccessor currentUserAccessor)
|
||||||
: IRequestHandler<ReviewMerchantCommand, MerchantDto>
|
: IRequestHandler<ReviewMerchantCommand, MerchantDto>
|
||||||
{
|
{
|
||||||
private readonly IMerchantRepository _merchantRepository = merchantRepository;
|
|
||||||
private readonly ITenantProvider _tenantProvider = tenantProvider;
|
|
||||||
private readonly ICurrentUserAccessor _currentUserAccessor = currentUserAccessor;
|
|
||||||
|
|
||||||
public async Task<MerchantDto> Handle(ReviewMerchantCommand request, CancellationToken cancellationToken)
|
public async Task<MerchantDto> Handle(ReviewMerchantCommand request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var tenantId = _tenantProvider.GetCurrentTenantId();
|
// 1. 读取商户
|
||||||
var merchant = await _merchantRepository.FindByIdAsync(request.MerchantId, tenantId, cancellationToken)
|
var tenantId = tenantProvider.GetCurrentTenantId();
|
||||||
|
var merchant = await merchantRepository.FindByIdAsync(request.MerchantId, tenantId, cancellationToken)
|
||||||
?? throw new BusinessException(ErrorCodes.NotFound, "商户不存在");
|
?? throw new BusinessException(ErrorCodes.NotFound, "商户不存在");
|
||||||
|
|
||||||
|
// 2. 已审核通过则直接返回
|
||||||
if (request.Approve && merchant.Status == MerchantStatus.Approved)
|
if (request.Approve && merchant.Status == MerchantStatus.Approved)
|
||||||
{
|
{
|
||||||
return MerchantMapping.ToDto(merchant);
|
return MerchantMapping.ToDto(merchant);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 3. 更新审核状态
|
||||||
var previousStatus = merchant.Status;
|
var previousStatus = merchant.Status;
|
||||||
merchant.Status = request.Approve ? MerchantStatus.Approved : MerchantStatus.Rejected;
|
merchant.Status = request.Approve ? MerchantStatus.Approved : MerchantStatus.Rejected;
|
||||||
merchant.ReviewRemarks = request.Remarks;
|
merchant.ReviewRemarks = request.Remarks;
|
||||||
@@ -44,8 +43,9 @@ public sealed class ReviewMerchantCommandHandler(
|
|||||||
merchant.JoinedAt = DateTime.UtcNow;
|
merchant.JoinedAt = DateTime.UtcNow;
|
||||||
}
|
}
|
||||||
|
|
||||||
await _merchantRepository.UpdateMerchantAsync(merchant, cancellationToken);
|
// 4. 持久化与审计
|
||||||
await _merchantRepository.AddAuditLogAsync(new MerchantAuditLog
|
await merchantRepository.UpdateMerchantAsync(merchant, cancellationToken);
|
||||||
|
await merchantRepository.AddAuditLogAsync(new MerchantAuditLog
|
||||||
{
|
{
|
||||||
TenantId = tenantId,
|
TenantId = tenantId,
|
||||||
MerchantId = merchant.Id,
|
MerchantId = merchant.Id,
|
||||||
@@ -55,20 +55,21 @@ public sealed class ReviewMerchantCommandHandler(
|
|||||||
OperatorId = ResolveOperatorId(),
|
OperatorId = ResolveOperatorId(),
|
||||||
OperatorName = ResolveOperatorName()
|
OperatorName = ResolveOperatorName()
|
||||||
}, cancellationToken);
|
}, cancellationToken);
|
||||||
await _merchantRepository.SaveChangesAsync(cancellationToken);
|
await merchantRepository.SaveChangesAsync(cancellationToken);
|
||||||
|
|
||||||
|
// 5. 返回 DTO
|
||||||
return MerchantMapping.ToDto(merchant);
|
return MerchantMapping.ToDto(merchant);
|
||||||
}
|
}
|
||||||
|
|
||||||
private long? ResolveOperatorId()
|
private long? ResolveOperatorId()
|
||||||
{
|
{
|
||||||
var id = _currentUserAccessor.UserId;
|
var id = currentUserAccessor.UserId;
|
||||||
return id == 0 ? null : id;
|
return id == 0 ? null : id;
|
||||||
}
|
}
|
||||||
|
|
||||||
private string ResolveOperatorName()
|
private string ResolveOperatorName()
|
||||||
{
|
{
|
||||||
var id = _currentUserAccessor.UserId;
|
var id = currentUserAccessor.UserId;
|
||||||
return id == 0 ? "system" : $"user:{id}";
|
return id == 0 ? "system" : $"user:{id}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,27 +20,27 @@ public sealed class ReviewMerchantDocumentCommandHandler(
|
|||||||
ICurrentUserAccessor currentUserAccessor)
|
ICurrentUserAccessor currentUserAccessor)
|
||||||
: IRequestHandler<ReviewMerchantDocumentCommand, MerchantDocumentDto>
|
: IRequestHandler<ReviewMerchantDocumentCommand, MerchantDocumentDto>
|
||||||
{
|
{
|
||||||
private readonly IMerchantRepository _merchantRepository = merchantRepository;
|
|
||||||
private readonly ITenantProvider _tenantProvider = tenantProvider;
|
|
||||||
private readonly ICurrentUserAccessor _currentUserAccessor = currentUserAccessor;
|
|
||||||
|
|
||||||
public async Task<MerchantDocumentDto> Handle(ReviewMerchantDocumentCommand request, CancellationToken cancellationToken)
|
public async Task<MerchantDocumentDto> Handle(ReviewMerchantDocumentCommand request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var tenantId = _tenantProvider.GetCurrentTenantId();
|
// 1. 读取证照
|
||||||
var document = await _merchantRepository.FindDocumentByIdAsync(request.MerchantId, tenantId, request.DocumentId, cancellationToken)
|
var tenantId = tenantProvider.GetCurrentTenantId();
|
||||||
|
var document = await merchantRepository.FindDocumentByIdAsync(request.MerchantId, tenantId, request.DocumentId, cancellationToken)
|
||||||
?? throw new BusinessException(ErrorCodes.NotFound, "证照不存在");
|
?? throw new BusinessException(ErrorCodes.NotFound, "证照不存在");
|
||||||
|
|
||||||
|
// 2. 若状态无变化且备注相同,直接返回
|
||||||
var targetStatus = request.Approve ? MerchantDocumentStatus.Approved : MerchantDocumentStatus.Rejected;
|
var targetStatus = request.Approve ? MerchantDocumentStatus.Approved : MerchantDocumentStatus.Rejected;
|
||||||
if (document.Status == targetStatus && document.Remarks == request.Remarks)
|
if (document.Status == targetStatus && document.Remarks == request.Remarks)
|
||||||
{
|
{
|
||||||
return MerchantMapping.ToDto(document);
|
return MerchantMapping.ToDto(document);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 3. 更新状态
|
||||||
document.Status = targetStatus;
|
document.Status = targetStatus;
|
||||||
document.Remarks = request.Remarks;
|
document.Remarks = request.Remarks;
|
||||||
|
|
||||||
await _merchantRepository.UpdateDocumentAsync(document, cancellationToken);
|
// 4. 持久化与审计
|
||||||
await _merchantRepository.AddAuditLogAsync(new MerchantAuditLog
|
await merchantRepository.UpdateDocumentAsync(document, cancellationToken);
|
||||||
|
await merchantRepository.AddAuditLogAsync(new MerchantAuditLog
|
||||||
{
|
{
|
||||||
TenantId = tenantId,
|
TenantId = tenantId,
|
||||||
MerchantId = document.MerchantId,
|
MerchantId = document.MerchantId,
|
||||||
@@ -50,20 +50,21 @@ public sealed class ReviewMerchantDocumentCommandHandler(
|
|||||||
OperatorId = ResolveOperatorId(),
|
OperatorId = ResolveOperatorId(),
|
||||||
OperatorName = ResolveOperatorName()
|
OperatorName = ResolveOperatorName()
|
||||||
}, cancellationToken);
|
}, cancellationToken);
|
||||||
await _merchantRepository.SaveChangesAsync(cancellationToken);
|
await merchantRepository.SaveChangesAsync(cancellationToken);
|
||||||
|
|
||||||
|
// 5. 返回 DTO
|
||||||
return MerchantMapping.ToDto(document);
|
return MerchantMapping.ToDto(document);
|
||||||
}
|
}
|
||||||
|
|
||||||
private long? ResolveOperatorId()
|
private long? ResolveOperatorId()
|
||||||
{
|
{
|
||||||
var id = _currentUserAccessor.UserId;
|
var id = currentUserAccessor.UserId;
|
||||||
return id == 0 ? null : id;
|
return id == 0 ? null : id;
|
||||||
}
|
}
|
||||||
|
|
||||||
private string ResolveOperatorName()
|
private string ResolveOperatorName()
|
||||||
{
|
{
|
||||||
var id = _currentUserAccessor.UserId;
|
var id = currentUserAccessor.UserId;
|
||||||
return id == 0 ? "system" : $"user:{id}";
|
return id == 0 ? "system" : $"user:{id}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,21 +15,21 @@ public sealed class SearchMerchantsQueryHandler(
|
|||||||
ITenantProvider tenantProvider)
|
ITenantProvider tenantProvider)
|
||||||
: IRequestHandler<SearchMerchantsQuery, PagedResult<MerchantDto>>
|
: IRequestHandler<SearchMerchantsQuery, PagedResult<MerchantDto>>
|
||||||
{
|
{
|
||||||
private readonly IMerchantRepository _merchantRepository = merchantRepository;
|
|
||||||
private readonly ITenantProvider _tenantProvider = tenantProvider;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task<PagedResult<MerchantDto>> Handle(SearchMerchantsQuery request, CancellationToken cancellationToken)
|
public async Task<PagedResult<MerchantDto>> Handle(SearchMerchantsQuery request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var tenantId = _tenantProvider.GetCurrentTenantId();
|
// 1. 获取租户并查询商户
|
||||||
var merchants = await _merchantRepository.SearchAsync(tenantId, request.Status, cancellationToken);
|
var tenantId = tenantProvider.GetCurrentTenantId();
|
||||||
|
var merchants = await merchantRepository.SearchAsync(tenantId, request.Status, cancellationToken);
|
||||||
|
|
||||||
|
// 2. 排序与分页
|
||||||
var sorted = ApplySorting(merchants, request.SortBy, request.SortDescending);
|
var sorted = ApplySorting(merchants, request.SortBy, request.SortDescending);
|
||||||
var paged = sorted
|
var paged = sorted
|
||||||
.Skip((request.Page - 1) * request.PageSize)
|
.Skip((request.Page - 1) * request.PageSize)
|
||||||
.Take(request.PageSize)
|
.Take(request.PageSize)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
|
// 3. 映射 DTO
|
||||||
var items = paged.Select(merchant => new MerchantDto
|
var items = paged.Select(merchant => new MerchantDto
|
||||||
{
|
{
|
||||||
Id = merchant.Id,
|
Id = merchant.Id,
|
||||||
@@ -45,6 +45,7 @@ public sealed class SearchMerchantsQueryHandler(
|
|||||||
CreatedAt = merchant.CreatedAt
|
CreatedAt = merchant.CreatedAt
|
||||||
}).ToList();
|
}).ToList();
|
||||||
|
|
||||||
|
// 4. 返回分页结果
|
||||||
return new PagedResult<MerchantDto>(items, request.Page, request.PageSize, merchants.Count);
|
return new PagedResult<MerchantDto>(items, request.Page, request.PageSize, merchants.Count);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,16 +16,12 @@ public sealed class UpdateMerchantCommandHandler(
|
|||||||
ILogger<UpdateMerchantCommandHandler> logger)
|
ILogger<UpdateMerchantCommandHandler> logger)
|
||||||
: IRequestHandler<UpdateMerchantCommand, MerchantDto?>
|
: IRequestHandler<UpdateMerchantCommand, MerchantDto?>
|
||||||
{
|
{
|
||||||
private readonly IMerchantRepository _merchantRepository = merchantRepository;
|
|
||||||
private readonly ITenantProvider _tenantProvider = tenantProvider;
|
|
||||||
private readonly ILogger<UpdateMerchantCommandHandler> _logger = logger;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task<MerchantDto?> Handle(UpdateMerchantCommand request, CancellationToken cancellationToken)
|
public async Task<MerchantDto?> Handle(UpdateMerchantCommand request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
// 1. 读取现有商户
|
// 1. 读取现有商户
|
||||||
var tenantId = _tenantProvider.GetCurrentTenantId();
|
var tenantId = tenantProvider.GetCurrentTenantId();
|
||||||
var existing = await _merchantRepository.FindByIdAsync(request.MerchantId, tenantId, cancellationToken);
|
var existing = await merchantRepository.FindByIdAsync(request.MerchantId, tenantId, cancellationToken);
|
||||||
if (existing == null)
|
if (existing == null)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
@@ -41,9 +37,9 @@ public sealed class UpdateMerchantCommandHandler(
|
|||||||
existing.Status = request.Status;
|
existing.Status = request.Status;
|
||||||
|
|
||||||
// 3. 持久化
|
// 3. 持久化
|
||||||
await _merchantRepository.UpdateMerchantAsync(existing, cancellationToken);
|
await merchantRepository.UpdateMerchantAsync(existing, cancellationToken);
|
||||||
await _merchantRepository.SaveChangesAsync(cancellationToken);
|
await merchantRepository.SaveChangesAsync(cancellationToken);
|
||||||
_logger.LogInformation("更新商户 {MerchantId} - {BrandName}", existing.Id, existing.BrandName);
|
logger.LogInformation("更新商户 {MerchantId} - {BrandName}", existing.Id, existing.BrandName);
|
||||||
|
|
||||||
// 4. 返回 DTO
|
// 4. 返回 DTO
|
||||||
return MapToDto(existing);
|
return MapToDto(existing);
|
||||||
|
|||||||
@@ -20,16 +20,14 @@ public sealed class UpdateMerchantContractStatusCommandHandler(
|
|||||||
ICurrentUserAccessor currentUserAccessor)
|
ICurrentUserAccessor currentUserAccessor)
|
||||||
: IRequestHandler<UpdateMerchantContractStatusCommand, MerchantContractDto>
|
: IRequestHandler<UpdateMerchantContractStatusCommand, MerchantContractDto>
|
||||||
{
|
{
|
||||||
private readonly IMerchantRepository _merchantRepository = merchantRepository;
|
|
||||||
private readonly ITenantProvider _tenantProvider = tenantProvider;
|
|
||||||
private readonly ICurrentUserAccessor _currentUserAccessor = currentUserAccessor;
|
|
||||||
|
|
||||||
public async Task<MerchantContractDto> Handle(UpdateMerchantContractStatusCommand request, CancellationToken cancellationToken)
|
public async Task<MerchantContractDto> Handle(UpdateMerchantContractStatusCommand request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var tenantId = _tenantProvider.GetCurrentTenantId();
|
// 1. 查询合同
|
||||||
var contract = await _merchantRepository.FindContractByIdAsync(request.MerchantId, tenantId, request.ContractId, cancellationToken)
|
var tenantId = tenantProvider.GetCurrentTenantId();
|
||||||
|
var contract = await merchantRepository.FindContractByIdAsync(request.MerchantId, tenantId, request.ContractId, cancellationToken)
|
||||||
?? throw new BusinessException(ErrorCodes.NotFound, "合同不存在");
|
?? throw new BusinessException(ErrorCodes.NotFound, "合同不存在");
|
||||||
|
|
||||||
|
// 2. 更新状态
|
||||||
if (request.Status == ContractStatus.Active)
|
if (request.Status == ContractStatus.Active)
|
||||||
{
|
{
|
||||||
contract.Status = ContractStatus.Active;
|
contract.Status = ContractStatus.Active;
|
||||||
@@ -46,8 +44,9 @@ public sealed class UpdateMerchantContractStatusCommandHandler(
|
|||||||
contract.Status = request.Status;
|
contract.Status = request.Status;
|
||||||
}
|
}
|
||||||
|
|
||||||
await _merchantRepository.UpdateContractAsync(contract, cancellationToken);
|
// 3. 持久化与审计
|
||||||
await _merchantRepository.AddAuditLogAsync(new MerchantAuditLog
|
await merchantRepository.UpdateContractAsync(contract, cancellationToken);
|
||||||
|
await merchantRepository.AddAuditLogAsync(new MerchantAuditLog
|
||||||
{
|
{
|
||||||
TenantId = tenantId,
|
TenantId = tenantId,
|
||||||
MerchantId = contract.MerchantId,
|
MerchantId = contract.MerchantId,
|
||||||
@@ -58,19 +57,21 @@ public sealed class UpdateMerchantContractStatusCommandHandler(
|
|||||||
OperatorName = ResolveOperatorName()
|
OperatorName = ResolveOperatorName()
|
||||||
}, cancellationToken);
|
}, cancellationToken);
|
||||||
|
|
||||||
await _merchantRepository.SaveChangesAsync(cancellationToken);
|
await merchantRepository.SaveChangesAsync(cancellationToken);
|
||||||
|
|
||||||
|
// 4. 返回 DTO
|
||||||
return MerchantMapping.ToDto(contract);
|
return MerchantMapping.ToDto(contract);
|
||||||
}
|
}
|
||||||
|
|
||||||
private long? ResolveOperatorId()
|
private long? ResolveOperatorId()
|
||||||
{
|
{
|
||||||
var id = _currentUserAccessor.UserId;
|
var id = currentUserAccessor.UserId;
|
||||||
return id == 0 ? null : id;
|
return id == 0 ? null : id;
|
||||||
}
|
}
|
||||||
|
|
||||||
private string ResolveOperatorName()
|
private string ResolveOperatorName()
|
||||||
{
|
{
|
||||||
var id = _currentUserAccessor.UserId;
|
var id = currentUserAccessor.UserId;
|
||||||
return id == 0 ? "system" : $"user:{id}";
|
return id == 0 ? "system" : $"user:{id}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,17 +17,13 @@ public sealed class CreateOrderCommandHandler(
|
|||||||
ILogger<CreateOrderCommandHandler> logger)
|
ILogger<CreateOrderCommandHandler> logger)
|
||||||
: IRequestHandler<CreateOrderCommand, OrderDto>
|
: IRequestHandler<CreateOrderCommand, OrderDto>
|
||||||
{
|
{
|
||||||
private readonly IOrderRepository _orderRepository = orderRepository;
|
|
||||||
private readonly IIdGenerator _idGenerator = idGenerator;
|
|
||||||
private readonly ILogger<CreateOrderCommandHandler> _logger = logger;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task<OrderDto> Handle(CreateOrderCommand request, CancellationToken cancellationToken)
|
public async Task<OrderDto> Handle(CreateOrderCommand request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
// 1. 构建订单
|
// 1. 构建订单
|
||||||
var order = new Order
|
var order = new Order
|
||||||
{
|
{
|
||||||
Id = _idGenerator.NextId(),
|
Id = idGenerator.NextId(),
|
||||||
OrderNo = request.OrderNo.Trim(),
|
OrderNo = request.OrderNo.Trim(),
|
||||||
StoreId = request.StoreId,
|
StoreId = request.StoreId,
|
||||||
Channel = request.Channel,
|
Channel = request.Channel,
|
||||||
@@ -77,15 +73,17 @@ public sealed class CreateOrderCommandHandler(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 4. 持久化
|
// 4. 持久化
|
||||||
await _orderRepository.AddOrderAsync(order, cancellationToken);
|
await orderRepository.AddOrderAsync(order, cancellationToken);
|
||||||
if (items.Count > 0)
|
if (items.Count > 0)
|
||||||
{
|
{
|
||||||
await _orderRepository.AddItemsAsync(items, cancellationToken);
|
await orderRepository.AddItemsAsync(items, cancellationToken);
|
||||||
}
|
}
|
||||||
await _orderRepository.SaveChangesAsync(cancellationToken);
|
await orderRepository.SaveChangesAsync(cancellationToken);
|
||||||
_logger.LogInformation("创建订单 {OrderNo} ({OrderId})", order.OrderNo, order.Id);
|
|
||||||
|
|
||||||
// 5. 返回 DTO
|
// 5. 记录日志
|
||||||
|
logger.LogInformation("创建订单 {OrderNo} ({OrderId})", order.OrderNo, order.Id);
|
||||||
|
|
||||||
|
// 6. 返回 DTO
|
||||||
return MapToDto(order, items, [], []);
|
return MapToDto(order, items, [], []);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,26 +15,25 @@ public sealed class DeleteOrderCommandHandler(
|
|||||||
ILogger<DeleteOrderCommandHandler> logger)
|
ILogger<DeleteOrderCommandHandler> logger)
|
||||||
: IRequestHandler<DeleteOrderCommand, bool>
|
: IRequestHandler<DeleteOrderCommand, bool>
|
||||||
{
|
{
|
||||||
private readonly IOrderRepository _orderRepository = orderRepository;
|
|
||||||
private readonly ITenantProvider _tenantProvider = tenantProvider;
|
|
||||||
private readonly ILogger<DeleteOrderCommandHandler> _logger = logger;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task<bool> Handle(DeleteOrderCommand request, CancellationToken cancellationToken)
|
public async Task<bool> Handle(DeleteOrderCommand request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
// 1. 校验存在性
|
// 1. 校验存在性
|
||||||
var tenantId = _tenantProvider.GetCurrentTenantId();
|
var tenantId = tenantProvider.GetCurrentTenantId();
|
||||||
var existing = await _orderRepository.FindByIdAsync(request.OrderId, tenantId, cancellationToken);
|
var existing = await orderRepository.FindByIdAsync(request.OrderId, tenantId, cancellationToken);
|
||||||
if (existing == null)
|
if (existing == null)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. 删除
|
// 2. 删除
|
||||||
await _orderRepository.DeleteOrderAsync(request.OrderId, tenantId, cancellationToken);
|
await orderRepository.DeleteOrderAsync(request.OrderId, tenantId, cancellationToken);
|
||||||
await _orderRepository.SaveChangesAsync(cancellationToken);
|
await orderRepository.SaveChangesAsync(cancellationToken);
|
||||||
_logger.LogInformation("删除订单 {OrderId}", request.OrderId);
|
|
||||||
|
|
||||||
|
// 3. 记录日志
|
||||||
|
logger.LogInformation("删除订单 {OrderId}", request.OrderId);
|
||||||
|
|
||||||
|
// 4. 返回执行结果
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,23 +15,25 @@ public sealed class GetOrderByIdQueryHandler(
|
|||||||
ITenantProvider tenantProvider)
|
ITenantProvider tenantProvider)
|
||||||
: IRequestHandler<GetOrderByIdQuery, OrderDto?>
|
: IRequestHandler<GetOrderByIdQuery, OrderDto?>
|
||||||
{
|
{
|
||||||
private readonly IOrderRepository _orderRepository = orderRepository;
|
|
||||||
private readonly ITenantProvider _tenantProvider = tenantProvider;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task<OrderDto?> Handle(GetOrderByIdQuery request, CancellationToken cancellationToken)
|
public async Task<OrderDto?> Handle(GetOrderByIdQuery request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var tenantId = _tenantProvider.GetCurrentTenantId();
|
// 1. 获取当前租户
|
||||||
var order = await _orderRepository.FindByIdAsync(request.OrderId, tenantId, cancellationToken);
|
var tenantId = tenantProvider.GetCurrentTenantId();
|
||||||
|
|
||||||
|
// 2. 查询订单主体
|
||||||
|
var order = await orderRepository.FindByIdAsync(request.OrderId, tenantId, cancellationToken);
|
||||||
if (order == null)
|
if (order == null)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var items = await _orderRepository.GetItemsAsync(order.Id, tenantId, cancellationToken);
|
// 3. 查询关联明细
|
||||||
var histories = await _orderRepository.GetStatusHistoryAsync(order.Id, tenantId, cancellationToken);
|
var items = await orderRepository.GetItemsAsync(order.Id, tenantId, cancellationToken);
|
||||||
var refunds = await _orderRepository.GetRefundsAsync(order.Id, tenantId, cancellationToken);
|
var histories = await orderRepository.GetStatusHistoryAsync(order.Id, tenantId, cancellationToken);
|
||||||
|
var refunds = await orderRepository.GetRefundsAsync(order.Id, tenantId, cancellationToken);
|
||||||
|
|
||||||
|
// 4. 映射并返回
|
||||||
return MapToDto(order, items, histories, refunds);
|
return MapToDto(order, items, histories, refunds);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,20 +15,20 @@ public sealed class SearchOrdersQueryHandler(
|
|||||||
ITenantProvider tenantProvider)
|
ITenantProvider tenantProvider)
|
||||||
: IRequestHandler<SearchOrdersQuery, PagedResult<OrderDto>>
|
: IRequestHandler<SearchOrdersQuery, PagedResult<OrderDto>>
|
||||||
{
|
{
|
||||||
private readonly IOrderRepository _orderRepository = orderRepository;
|
|
||||||
private readonly ITenantProvider _tenantProvider = tenantProvider;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task<PagedResult<OrderDto>> Handle(SearchOrdersQuery request, CancellationToken cancellationToken)
|
public async Task<PagedResult<OrderDto>> Handle(SearchOrdersQuery request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var tenantId = _tenantProvider.GetCurrentTenantId();
|
// 1. 获取当前租户并查询订单
|
||||||
var orders = await _orderRepository.SearchAsync(tenantId, request.Status, request.PaymentStatus, cancellationToken);
|
var tenantId = tenantProvider.GetCurrentTenantId();
|
||||||
|
var orders = await orderRepository.SearchAsync(tenantId, request.Status, request.PaymentStatus, cancellationToken);
|
||||||
|
|
||||||
|
// 2. 可选过滤:门店
|
||||||
if (request.StoreId.HasValue)
|
if (request.StoreId.HasValue)
|
||||||
{
|
{
|
||||||
orders = orders.Where(x => x.StoreId == request.StoreId.Value).ToList();
|
orders = orders.Where(x => x.StoreId == request.StoreId.Value).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 3. 可选过滤:订单号模糊
|
||||||
if (!string.IsNullOrWhiteSpace(request.OrderNo))
|
if (!string.IsNullOrWhiteSpace(request.OrderNo))
|
||||||
{
|
{
|
||||||
var orderNo = request.OrderNo.Trim();
|
var orderNo = request.OrderNo.Trim();
|
||||||
@@ -37,12 +37,14 @@ public sealed class SearchOrdersQueryHandler(
|
|||||||
.ToList();
|
.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 4. 排序与分页
|
||||||
var sorted = ApplySorting(orders, request.SortBy, request.SortDescending);
|
var sorted = ApplySorting(orders, request.SortBy, request.SortDescending);
|
||||||
var paged = sorted
|
var paged = sorted
|
||||||
.Skip((request.Page - 1) * request.PageSize)
|
.Skip((request.Page - 1) * request.PageSize)
|
||||||
.Take(request.PageSize)
|
.Take(request.PageSize)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
|
// 5. 映射 DTO
|
||||||
var items = paged.Select(order => new OrderDto
|
var items = paged.Select(order => new OrderDto
|
||||||
{
|
{
|
||||||
Id = order.Id,
|
Id = order.Id,
|
||||||
@@ -70,6 +72,7 @@ public sealed class SearchOrdersQueryHandler(
|
|||||||
CreatedAt = order.CreatedAt
|
CreatedAt = order.CreatedAt
|
||||||
}).ToList();
|
}).ToList();
|
||||||
|
|
||||||
|
// 6. 返回分页结果
|
||||||
return new PagedResult<OrderDto>(items, request.Page, request.PageSize, orders.Count);
|
return new PagedResult<OrderDto>(items, request.Page, request.PageSize, orders.Count);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,16 +17,12 @@ public sealed class UpdateOrderCommandHandler(
|
|||||||
ILogger<UpdateOrderCommandHandler> logger)
|
ILogger<UpdateOrderCommandHandler> logger)
|
||||||
: IRequestHandler<UpdateOrderCommand, OrderDto?>
|
: IRequestHandler<UpdateOrderCommand, OrderDto?>
|
||||||
{
|
{
|
||||||
private readonly IOrderRepository _orderRepository = orderRepository;
|
|
||||||
private readonly ITenantProvider _tenantProvider = tenantProvider;
|
|
||||||
private readonly ILogger<UpdateOrderCommandHandler> _logger = logger;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task<OrderDto?> Handle(UpdateOrderCommand request, CancellationToken cancellationToken)
|
public async Task<OrderDto?> Handle(UpdateOrderCommand request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
// 1. 读取订单
|
// 1. 读取订单
|
||||||
var tenantId = _tenantProvider.GetCurrentTenantId();
|
var tenantId = tenantProvider.GetCurrentTenantId();
|
||||||
var existing = await _orderRepository.FindByIdAsync(request.OrderId, tenantId, cancellationToken);
|
var existing = await orderRepository.FindByIdAsync(request.OrderId, tenantId, cancellationToken);
|
||||||
if (existing == null)
|
if (existing == null)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
@@ -55,14 +51,16 @@ public sealed class UpdateOrderCommandHandler(
|
|||||||
existing.Remark = request.Remark?.Trim();
|
existing.Remark = request.Remark?.Trim();
|
||||||
|
|
||||||
// 3. 持久化
|
// 3. 持久化
|
||||||
await _orderRepository.UpdateOrderAsync(existing, cancellationToken);
|
await orderRepository.UpdateOrderAsync(existing, cancellationToken);
|
||||||
await _orderRepository.SaveChangesAsync(cancellationToken);
|
await orderRepository.SaveChangesAsync(cancellationToken);
|
||||||
_logger.LogInformation("更新订单 {OrderNo} ({OrderId})", existing.OrderNo, existing.Id);
|
|
||||||
|
|
||||||
// 4. 读取关联数据并返回
|
// 4. 记录更新日志
|
||||||
var items = await _orderRepository.GetItemsAsync(existing.Id, tenantId, cancellationToken);
|
logger.LogInformation("更新订单 {OrderNo} ({OrderId})", existing.OrderNo, existing.Id);
|
||||||
var histories = await _orderRepository.GetStatusHistoryAsync(existing.Id, tenantId, cancellationToken);
|
|
||||||
var refunds = await _orderRepository.GetRefundsAsync(existing.Id, tenantId, cancellationToken);
|
// 5. 读取关联数据并返回
|
||||||
|
var items = await orderRepository.GetItemsAsync(existing.Id, tenantId, cancellationToken);
|
||||||
|
var histories = await orderRepository.GetStatusHistoryAsync(existing.Id, tenantId, cancellationToken);
|
||||||
|
var refunds = await orderRepository.GetRefundsAsync(existing.Id, tenantId, cancellationToken);
|
||||||
|
|
||||||
return MapToDto(existing, items, histories, refunds);
|
return MapToDto(existing, items, histories, refunds);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,20 +18,19 @@ public sealed class ChangeTenantSubscriptionPlanCommandHandler(
|
|||||||
IIdGenerator idGenerator)
|
IIdGenerator idGenerator)
|
||||||
: IRequestHandler<ChangeTenantSubscriptionPlanCommand, TenantSubscriptionDto>
|
: IRequestHandler<ChangeTenantSubscriptionPlanCommand, TenantSubscriptionDto>
|
||||||
{
|
{
|
||||||
private readonly ITenantRepository _tenantRepository = tenantRepository;
|
|
||||||
private readonly IIdGenerator _idGenerator = idGenerator;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task<TenantSubscriptionDto> Handle(ChangeTenantSubscriptionPlanCommand request, CancellationToken cancellationToken)
|
public async Task<TenantSubscriptionDto> Handle(ChangeTenantSubscriptionPlanCommand request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
_ = await _tenantRepository.FindByIdAsync(request.TenantId, cancellationToken)
|
// 1. 校验租户与订阅存在性
|
||||||
|
_ = await tenantRepository.FindByIdAsync(request.TenantId, cancellationToken)
|
||||||
?? throw new BusinessException(ErrorCodes.NotFound, "租户不存在");
|
?? throw new BusinessException(ErrorCodes.NotFound, "租户不存在");
|
||||||
|
|
||||||
var subscription = await _tenantRepository.FindSubscriptionByIdAsync(request.TenantId, request.TenantSubscriptionId, cancellationToken)
|
var subscription = await tenantRepository.FindSubscriptionByIdAsync(request.TenantId, request.TenantSubscriptionId, cancellationToken)
|
||||||
?? throw new BusinessException(ErrorCodes.NotFound, "订阅不存在");
|
?? throw new BusinessException(ErrorCodes.NotFound, "订阅不存在");
|
||||||
|
|
||||||
var previousPackage = subscription.TenantPackageId;
|
var previousPackage = subscription.TenantPackageId;
|
||||||
|
|
||||||
|
// 2. 根据立即生效或排期设置目标套餐
|
||||||
if (request.Immediate)
|
if (request.Immediate)
|
||||||
{
|
{
|
||||||
subscription.TenantPackageId = request.TargetPackageId;
|
subscription.TenantPackageId = request.TargetPackageId;
|
||||||
@@ -42,10 +41,11 @@ public sealed class ChangeTenantSubscriptionPlanCommandHandler(
|
|||||||
subscription.ScheduledPackageId = request.TargetPackageId;
|
subscription.ScheduledPackageId = request.TargetPackageId;
|
||||||
}
|
}
|
||||||
|
|
||||||
await _tenantRepository.UpdateSubscriptionAsync(subscription, cancellationToken);
|
// 3. 更新订阅并记录变更历史
|
||||||
await _tenantRepository.AddSubscriptionHistoryAsync(new TenantSubscriptionHistory
|
await tenantRepository.UpdateSubscriptionAsync(subscription, cancellationToken);
|
||||||
|
await tenantRepository.AddSubscriptionHistoryAsync(new TenantSubscriptionHistory
|
||||||
{
|
{
|
||||||
Id = _idGenerator.NextId(),
|
Id = idGenerator.NextId(),
|
||||||
TenantId = subscription.TenantId,
|
TenantId = subscription.TenantId,
|
||||||
TenantSubscriptionId = subscription.Id,
|
TenantSubscriptionId = subscription.Id,
|
||||||
FromPackageId = previousPackage,
|
FromPackageId = previousPackage,
|
||||||
@@ -56,7 +56,8 @@ public sealed class ChangeTenantSubscriptionPlanCommandHandler(
|
|||||||
Notes = request.Notes
|
Notes = request.Notes
|
||||||
}, cancellationToken);
|
}, cancellationToken);
|
||||||
|
|
||||||
await _tenantRepository.AddAuditLogAsync(new TenantAuditLog
|
// 4. 记录审计日志
|
||||||
|
await tenantRepository.AddAuditLogAsync(new TenantAuditLog
|
||||||
{
|
{
|
||||||
TenantId = subscription.TenantId,
|
TenantId = subscription.TenantId,
|
||||||
Action = TenantAuditAction.SubscriptionPlanChanged,
|
Action = TenantAuditAction.SubscriptionPlanChanged,
|
||||||
@@ -66,7 +67,8 @@ public sealed class ChangeTenantSubscriptionPlanCommandHandler(
|
|||||||
CurrentStatus = null
|
CurrentStatus = null
|
||||||
}, cancellationToken);
|
}, cancellationToken);
|
||||||
|
|
||||||
await _tenantRepository.SaveChangesAsync(cancellationToken);
|
// 5. 保存并返回 DTO
|
||||||
|
await tenantRepository.SaveChangesAsync(cancellationToken);
|
||||||
|
|
||||||
return subscription.ToSubscriptionDto()
|
return subscription.ToSubscriptionDto()
|
||||||
?? throw new BusinessException(ErrorCodes.InternalServerError, "订阅更新失败");
|
?? throw new BusinessException(ErrorCodes.InternalServerError, "订阅更新失败");
|
||||||
|
|||||||
@@ -24,18 +24,20 @@ public sealed class CheckTenantQuotaCommandHandler(
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task<QuotaCheckResultDto> Handle(CheckTenantQuotaCommand request, CancellationToken cancellationToken)
|
public async Task<QuotaCheckResultDto> Handle(CheckTenantQuotaCommand request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 校验请求参数
|
||||||
if (request.Delta <= 0)
|
if (request.Delta <= 0)
|
||||||
{
|
{
|
||||||
throw new BusinessException(ErrorCodes.BadRequest, "配额消耗量必须大于 0");
|
throw new BusinessException(ErrorCodes.BadRequest, "配额消耗量必须大于 0");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 2. 校验租户上下文
|
||||||
var currentTenantId = tenantProvider.GetCurrentTenantId();
|
var currentTenantId = tenantProvider.GetCurrentTenantId();
|
||||||
if (currentTenantId == 0 || currentTenantId != request.TenantId)
|
if (currentTenantId == 0 || currentTenantId != request.TenantId)
|
||||||
{
|
{
|
||||||
throw new BusinessException(ErrorCodes.Forbidden, "租户上下文不匹配,请在请求头 X-Tenant-Id 指定目标租户");
|
throw new BusinessException(ErrorCodes.Forbidden, "租户上下文不匹配,请在请求头 X-Tenant-Id 指定目标租户");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1. 获取租户与当前订阅。
|
// 3. 获取租户与当前订阅
|
||||||
_ = await tenantRepository.FindByIdAsync(request.TenantId, cancellationToken)
|
_ = await tenantRepository.FindByIdAsync(request.TenantId, cancellationToken)
|
||||||
?? throw new BusinessException(ErrorCodes.NotFound, "租户不存在");
|
?? throw new BusinessException(ErrorCodes.NotFound, "租户不存在");
|
||||||
|
|
||||||
@@ -50,7 +52,7 @@ public sealed class CheckTenantQuotaCommandHandler(
|
|||||||
|
|
||||||
var limit = ResolveLimit(package, request.QuotaType);
|
var limit = ResolveLimit(package, request.QuotaType);
|
||||||
|
|
||||||
// 2. 加载配额使用记录并计算。
|
// 4. 加载配额使用记录并计算
|
||||||
var usage = await quotaUsageRepository.FindAsync(request.TenantId, request.QuotaType, cancellationToken)
|
var usage = await quotaUsageRepository.FindAsync(request.TenantId, request.QuotaType, cancellationToken)
|
||||||
?? new TenantQuotaUsage
|
?? new TenantQuotaUsage
|
||||||
{
|
{
|
||||||
@@ -69,12 +71,14 @@ public sealed class CheckTenantQuotaCommandHandler(
|
|||||||
throw new BusinessException(ErrorCodes.Conflict, $"{request.QuotaType} 配额不足");
|
throw new BusinessException(ErrorCodes.Conflict, $"{request.QuotaType} 配额不足");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 5. 更新使用并保存
|
||||||
usage.LimitValue = limit ?? usage.LimitValue;
|
usage.LimitValue = limit ?? usage.LimitValue;
|
||||||
usage.UsedValue = usedAfter;
|
usage.UsedValue = usedAfter;
|
||||||
usage.ResetCycle ??= ResolveResetCycle(request.QuotaType);
|
usage.ResetCycle ??= ResolveResetCycle(request.QuotaType);
|
||||||
|
|
||||||
await PersistUsageAsync(usage, quotaUsageRepository, cancellationToken);
|
await PersistUsageAsync(usage, quotaUsageRepository, cancellationToken);
|
||||||
|
|
||||||
|
// 6. 返回结果
|
||||||
return new QuotaCheckResultDto
|
return new QuotaCheckResultDto
|
||||||
{
|
{
|
||||||
QuotaType = request.QuotaType,
|
QuotaType = request.QuotaType,
|
||||||
|
|||||||
@@ -16,11 +16,13 @@ public sealed class CreateTenantAnnouncementCommandHandler(ITenantAnnouncementRe
|
|||||||
{
|
{
|
||||||
public async Task<TenantAnnouncementDto> Handle(CreateTenantAnnouncementCommand request, CancellationToken cancellationToken)
|
public async Task<TenantAnnouncementDto> Handle(CreateTenantAnnouncementCommand request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 校验标题与内容
|
||||||
if (string.IsNullOrWhiteSpace(request.Title) || string.IsNullOrWhiteSpace(request.Content))
|
if (string.IsNullOrWhiteSpace(request.Title) || string.IsNullOrWhiteSpace(request.Content))
|
||||||
{
|
{
|
||||||
throw new BusinessException(ErrorCodes.BadRequest, "公告标题和内容不能为空");
|
throw new BusinessException(ErrorCodes.BadRequest, "公告标题和内容不能为空");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 2. 构建公告实体
|
||||||
var announcement = new TenantAnnouncement
|
var announcement = new TenantAnnouncement
|
||||||
{
|
{
|
||||||
TenantId = request.TenantId,
|
TenantId = request.TenantId,
|
||||||
@@ -33,6 +35,7 @@ public sealed class CreateTenantAnnouncementCommandHandler(ITenantAnnouncementRe
|
|||||||
IsActive = request.IsActive
|
IsActive = request.IsActive
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 3. 持久化并返回 DTO
|
||||||
await announcementRepository.AddAsync(announcement, cancellationToken);
|
await announcementRepository.AddAsync(announcement, cancellationToken);
|
||||||
await announcementRepository.SaveChangesAsync(cancellationToken);
|
await announcementRepository.SaveChangesAsync(cancellationToken);
|
||||||
|
|
||||||
|
|||||||
@@ -16,11 +16,13 @@ public sealed class CreateTenantBillingCommandHandler(ITenantBillingRepository b
|
|||||||
{
|
{
|
||||||
public async Task<TenantBillingDto> Handle(CreateTenantBillingCommand request, CancellationToken cancellationToken)
|
public async Task<TenantBillingDto> Handle(CreateTenantBillingCommand request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 校验账单编号
|
||||||
if (string.IsNullOrWhiteSpace(request.StatementNo))
|
if (string.IsNullOrWhiteSpace(request.StatementNo))
|
||||||
{
|
{
|
||||||
throw new BusinessException(ErrorCodes.BadRequest, "账单编号不能为空");
|
throw new BusinessException(ErrorCodes.BadRequest, "账单编号不能为空");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 2. 构建账单实体
|
||||||
var bill = new TenantBillingStatement
|
var bill = new TenantBillingStatement
|
||||||
{
|
{
|
||||||
TenantId = request.TenantId,
|
TenantId = request.TenantId,
|
||||||
@@ -34,9 +36,11 @@ public sealed class CreateTenantBillingCommandHandler(ITenantBillingRepository b
|
|||||||
LineItemsJson = request.LineItemsJson
|
LineItemsJson = request.LineItemsJson
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 3. 持久化账单
|
||||||
await billingRepository.AddAsync(bill, cancellationToken);
|
await billingRepository.AddAsync(bill, cancellationToken);
|
||||||
await billingRepository.SaveChangesAsync(cancellationToken);
|
await billingRepository.SaveChangesAsync(cancellationToken);
|
||||||
|
|
||||||
|
// 4. 返回 DTO
|
||||||
return bill.ToDto();
|
return bill.ToDto();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,11 +17,13 @@ public sealed class CreateTenantPackageCommandHandler(ITenantPackageRepository p
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task<TenantPackageDto> Handle(CreateTenantPackageCommand request, CancellationToken cancellationToken)
|
public async Task<TenantPackageDto> Handle(CreateTenantPackageCommand request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 校验套餐名称
|
||||||
if (string.IsNullOrWhiteSpace(request.Name))
|
if (string.IsNullOrWhiteSpace(request.Name))
|
||||||
{
|
{
|
||||||
throw new BusinessException(ErrorCodes.BadRequest, "套餐名称不能为空");
|
throw new BusinessException(ErrorCodes.BadRequest, "套餐名称不能为空");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 2. 构建套餐实体
|
||||||
var package = new TenantPackage
|
var package = new TenantPackage
|
||||||
{
|
{
|
||||||
Name = request.Name.Trim(),
|
Name = request.Name.Trim(),
|
||||||
@@ -38,6 +40,7 @@ public sealed class CreateTenantPackageCommandHandler(ITenantPackageRepository p
|
|||||||
IsActive = request.IsActive
|
IsActive = request.IsActive
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 3. 持久化并返回
|
||||||
await packageRepository.AddAsync(package, cancellationToken);
|
await packageRepository.AddAsync(package, cancellationToken);
|
||||||
await packageRepository.SaveChangesAsync(cancellationToken);
|
await packageRepository.SaveChangesAsync(cancellationToken);
|
||||||
|
|
||||||
|
|||||||
@@ -18,28 +18,28 @@ public sealed class CreateTenantSubscriptionCommandHandler(
|
|||||||
IIdGenerator idGenerator)
|
IIdGenerator idGenerator)
|
||||||
: IRequestHandler<CreateTenantSubscriptionCommand, TenantSubscriptionDto>
|
: IRequestHandler<CreateTenantSubscriptionCommand, TenantSubscriptionDto>
|
||||||
{
|
{
|
||||||
private readonly ITenantRepository _tenantRepository = tenantRepository;
|
|
||||||
private readonly IIdGenerator _idGenerator = idGenerator;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task<TenantSubscriptionDto> Handle(CreateTenantSubscriptionCommand request, CancellationToken cancellationToken)
|
public async Task<TenantSubscriptionDto> Handle(CreateTenantSubscriptionCommand request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 校验订阅时长
|
||||||
if (request.DurationMonths <= 0)
|
if (request.DurationMonths <= 0)
|
||||||
{
|
{
|
||||||
throw new BusinessException(ErrorCodes.BadRequest, "订阅时长必须大于 0");
|
throw new BusinessException(ErrorCodes.BadRequest, "订阅时长必须大于 0");
|
||||||
}
|
}
|
||||||
|
|
||||||
var tenant = await _tenantRepository.FindByIdAsync(request.TenantId, cancellationToken)
|
// 2. 获取租户与当前订阅
|
||||||
|
var tenant = await tenantRepository.FindByIdAsync(request.TenantId, cancellationToken)
|
||||||
?? throw new BusinessException(ErrorCodes.NotFound, "租户不存在");
|
?? throw new BusinessException(ErrorCodes.NotFound, "租户不存在");
|
||||||
|
|
||||||
var current = await _tenantRepository.GetActiveSubscriptionAsync(request.TenantId, cancellationToken);
|
var current = await tenantRepository.GetActiveSubscriptionAsync(request.TenantId, cancellationToken);
|
||||||
var from = current?.EffectiveTo ?? tenant.EffectiveTo ?? DateTime.UtcNow;
|
var from = current?.EffectiveTo ?? tenant.EffectiveTo ?? DateTime.UtcNow;
|
||||||
var effectiveFrom = from > DateTime.UtcNow ? from : DateTime.UtcNow;
|
var effectiveFrom = from > DateTime.UtcNow ? from : DateTime.UtcNow;
|
||||||
var effectiveTo = effectiveFrom.AddMonths(request.DurationMonths);
|
var effectiveTo = effectiveFrom.AddMonths(request.DurationMonths);
|
||||||
|
|
||||||
|
// 3. 创建订阅实体
|
||||||
var subscription = new TenantSubscription
|
var subscription = new TenantSubscription
|
||||||
{
|
{
|
||||||
Id = _idGenerator.NextId(),
|
Id = idGenerator.NextId(),
|
||||||
TenantId = tenant.Id,
|
TenantId = tenant.Id,
|
||||||
TenantPackageId = request.TenantPackageId,
|
TenantPackageId = request.TenantPackageId,
|
||||||
EffectiveFrom = effectiveFrom,
|
EffectiveFrom = effectiveFrom,
|
||||||
@@ -50,10 +50,11 @@ public sealed class CreateTenantSubscriptionCommandHandler(
|
|||||||
Notes = request.Notes
|
Notes = request.Notes
|
||||||
};
|
};
|
||||||
|
|
||||||
await _tenantRepository.AddSubscriptionAsync(subscription, cancellationToken);
|
// 4. 记录订阅与历史
|
||||||
await _tenantRepository.AddSubscriptionHistoryAsync(new TenantSubscriptionHistory
|
await tenantRepository.AddSubscriptionAsync(subscription, cancellationToken);
|
||||||
|
await tenantRepository.AddSubscriptionHistoryAsync(new TenantSubscriptionHistory
|
||||||
{
|
{
|
||||||
Id = _idGenerator.NextId(),
|
Id = idGenerator.NextId(),
|
||||||
TenantId = tenant.Id,
|
TenantId = tenant.Id,
|
||||||
TenantSubscriptionId = subscription.Id,
|
TenantSubscriptionId = subscription.Id,
|
||||||
FromPackageId = current?.TenantPackageId ?? request.TenantPackageId,
|
FromPackageId = current?.TenantPackageId ?? request.TenantPackageId,
|
||||||
@@ -66,7 +67,8 @@ public sealed class CreateTenantSubscriptionCommandHandler(
|
|||||||
Notes = request.Notes
|
Notes = request.Notes
|
||||||
}, cancellationToken);
|
}, cancellationToken);
|
||||||
|
|
||||||
await _tenantRepository.AddAuditLogAsync(new TenantAuditLog
|
// 5. 记录审计
|
||||||
|
await tenantRepository.AddAuditLogAsync(new TenantAuditLog
|
||||||
{
|
{
|
||||||
TenantId = tenant.Id,
|
TenantId = tenant.Id,
|
||||||
Action = TenantAuditAction.SubscriptionUpdated,
|
Action = TenantAuditAction.SubscriptionUpdated,
|
||||||
@@ -74,8 +76,10 @@ public sealed class CreateTenantSubscriptionCommandHandler(
|
|||||||
Description = $"套餐 {request.TenantPackageId} 时长 {request.DurationMonths} 月"
|
Description = $"套餐 {request.TenantPackageId} 时长 {request.DurationMonths} 月"
|
||||||
}, cancellationToken);
|
}, cancellationToken);
|
||||||
|
|
||||||
await _tenantRepository.SaveChangesAsync(cancellationToken);
|
// 6. 保存变更
|
||||||
|
await tenantRepository.SaveChangesAsync(cancellationToken);
|
||||||
|
|
||||||
|
// 7. 返回 DTO
|
||||||
return subscription.ToSubscriptionDto()
|
return subscription.ToSubscriptionDto()
|
||||||
?? throw new BusinessException(ErrorCodes.InternalServerError, "订阅生成失败");
|
?? throw new BusinessException(ErrorCodes.InternalServerError, "订阅生成失败");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,8 +12,11 @@ public sealed class DeleteTenantAnnouncementCommandHandler(ITenantAnnouncementRe
|
|||||||
{
|
{
|
||||||
public async Task<bool> Handle(DeleteTenantAnnouncementCommand request, CancellationToken cancellationToken)
|
public async Task<bool> Handle(DeleteTenantAnnouncementCommand request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 删除公告
|
||||||
await announcementRepository.DeleteAsync(request.TenantId, request.AnnouncementId, cancellationToken);
|
await announcementRepository.DeleteAsync(request.TenantId, request.AnnouncementId, cancellationToken);
|
||||||
await announcementRepository.SaveChangesAsync(cancellationToken);
|
await announcementRepository.SaveChangesAsync(cancellationToken);
|
||||||
|
|
||||||
|
// 2. 返回执行结果
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,8 +13,11 @@ public sealed class DeleteTenantPackageCommandHandler(ITenantPackageRepository p
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task<bool> Handle(DeleteTenantPackageCommand request, CancellationToken cancellationToken)
|
public async Task<bool> Handle(DeleteTenantPackageCommand request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 删除套餐
|
||||||
await packageRepository.DeleteAsync(request.TenantPackageId, cancellationToken);
|
await packageRepository.DeleteAsync(request.TenantPackageId, cancellationToken);
|
||||||
await packageRepository.SaveChangesAsync(cancellationToken);
|
await packageRepository.SaveChangesAsync(cancellationToken);
|
||||||
|
|
||||||
|
// 2. 返回执行结果
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,12 +17,14 @@ public sealed class GetTenantAnnouncementQueryHandler(
|
|||||||
{
|
{
|
||||||
public async Task<TenantAnnouncementDto?> Handle(GetTenantAnnouncementQuery request, CancellationToken cancellationToken)
|
public async Task<TenantAnnouncementDto?> Handle(GetTenantAnnouncementQuery request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 查询公告主体
|
||||||
var announcement = await announcementRepository.FindByIdAsync(request.TenantId, request.AnnouncementId, cancellationToken);
|
var announcement = await announcementRepository.FindByIdAsync(request.TenantId, request.AnnouncementId, cancellationToken);
|
||||||
if (announcement == null)
|
if (announcement == null)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 2. 优先查用户级已读
|
||||||
var userId = currentUserAccessor?.UserId ?? 0;
|
var userId = currentUserAccessor?.UserId ?? 0;
|
||||||
var reads = await readRepository.GetByAnnouncementAsync(
|
var reads = await readRepository.GetByAnnouncementAsync(
|
||||||
request.TenantId,
|
request.TenantId,
|
||||||
@@ -37,6 +39,7 @@ public sealed class GetTenantAnnouncementQueryHandler(
|
|||||||
reads = tenantReads;
|
reads = tenantReads;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 3. 返回 DTO 并附带已读状态
|
||||||
var readRecord = reads.FirstOrDefault();
|
var readRecord = reads.FirstOrDefault();
|
||||||
return announcement.ToDto(readRecord != null, readRecord?.ReadAt);
|
return announcement.ToDto(readRecord != null, readRecord?.ReadAt);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,20 +13,21 @@ namespace TakeoutSaaS.Application.App.Tenants.Handlers;
|
|||||||
public sealed class GetTenantAuditLogsQueryHandler(ITenantRepository tenantRepository)
|
public sealed class GetTenantAuditLogsQueryHandler(ITenantRepository tenantRepository)
|
||||||
: IRequestHandler<GetTenantAuditLogsQuery, PagedResult<TenantAuditLogDto>>
|
: IRequestHandler<GetTenantAuditLogsQuery, PagedResult<TenantAuditLogDto>>
|
||||||
{
|
{
|
||||||
private readonly ITenantRepository _tenantRepository = tenantRepository;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task<PagedResult<TenantAuditLogDto>> Handle(GetTenantAuditLogsQuery request, CancellationToken cancellationToken)
|
public async Task<PagedResult<TenantAuditLogDto>> Handle(GetTenantAuditLogsQuery request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var logs = await _tenantRepository.GetAuditLogsAsync(request.TenantId, cancellationToken);
|
// 1. 查询审核日志
|
||||||
|
var logs = await tenantRepository.GetAuditLogsAsync(request.TenantId, cancellationToken);
|
||||||
var total = logs.Count;
|
var total = logs.Count;
|
||||||
|
|
||||||
|
// 2. 分页映射
|
||||||
var paged = logs
|
var paged = logs
|
||||||
.Skip((request.Page - 1) * request.PageSize)
|
.Skip((request.Page - 1) * request.PageSize)
|
||||||
.Take(request.PageSize)
|
.Take(request.PageSize)
|
||||||
.Select(TenantMapping.ToDto)
|
.Select(TenantMapping.ToDto)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
|
// 3. 返回分页结果
|
||||||
return new PagedResult<TenantAuditLogDto>(paged, request.Page, request.PageSize, total);
|
return new PagedResult<TenantAuditLogDto>(paged, request.Page, request.PageSize, total);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,10 @@ public sealed class GetTenantBillQueryHandler(ITenantBillingRepository billingRe
|
|||||||
{
|
{
|
||||||
public async Task<TenantBillingDto?> Handle(GetTenantBillQuery request, CancellationToken cancellationToken)
|
public async Task<TenantBillingDto?> Handle(GetTenantBillQuery request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 查询账单
|
||||||
var bill = await billingRepository.FindByIdAsync(request.TenantId, request.BillingId, cancellationToken);
|
var bill = await billingRepository.FindByIdAsync(request.TenantId, request.BillingId, cancellationToken);
|
||||||
|
|
||||||
|
// 2. 返回 DTO 或 null
|
||||||
return bill?.ToDto();
|
return bill?.ToDto();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,17 +13,18 @@ namespace TakeoutSaaS.Application.App.Tenants.Handlers;
|
|||||||
public sealed class GetTenantByIdQueryHandler(ITenantRepository tenantRepository)
|
public sealed class GetTenantByIdQueryHandler(ITenantRepository tenantRepository)
|
||||||
: IRequestHandler<GetTenantByIdQuery, TenantDetailDto>
|
: IRequestHandler<GetTenantByIdQuery, TenantDetailDto>
|
||||||
{
|
{
|
||||||
private readonly ITenantRepository _tenantRepository = tenantRepository;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task<TenantDetailDto> Handle(GetTenantByIdQuery request, CancellationToken cancellationToken)
|
public async Task<TenantDetailDto> Handle(GetTenantByIdQuery request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var tenant = await _tenantRepository.FindByIdAsync(request.TenantId, cancellationToken)
|
// 1. 查询租户
|
||||||
|
var tenant = await tenantRepository.FindByIdAsync(request.TenantId, cancellationToken)
|
||||||
?? throw new BusinessException(ErrorCodes.NotFound, "租户不存在");
|
?? throw new BusinessException(ErrorCodes.NotFound, "租户不存在");
|
||||||
|
|
||||||
var subscription = await _tenantRepository.GetActiveSubscriptionAsync(request.TenantId, cancellationToken);
|
// 2. 查询订阅与认证
|
||||||
var verification = await _tenantRepository.GetVerificationProfileAsync(request.TenantId, cancellationToken);
|
var subscription = await tenantRepository.GetActiveSubscriptionAsync(request.TenantId, cancellationToken);
|
||||||
|
var verification = await tenantRepository.GetVerificationProfileAsync(request.TenantId, cancellationToken);
|
||||||
|
|
||||||
|
// 3. 组装返回
|
||||||
return new TenantDetailDto
|
return new TenantDetailDto
|
||||||
{
|
{
|
||||||
Tenant = TenantMapping.ToDto(tenant, subscription, verification),
|
Tenant = TenantMapping.ToDto(tenant, subscription, verification),
|
||||||
|
|||||||
@@ -14,7 +14,10 @@ public sealed class GetTenantPackageByIdQueryHandler(ITenantPackageRepository pa
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task<TenantPackageDto?> Handle(GetTenantPackageByIdQuery request, CancellationToken cancellationToken)
|
public async Task<TenantPackageDto?> Handle(GetTenantPackageByIdQuery request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 查询套餐
|
||||||
var package = await packageRepository.FindByIdAsync(request.TenantPackageId, cancellationToken);
|
var package = await packageRepository.FindByIdAsync(request.TenantPackageId, cancellationToken);
|
||||||
|
|
||||||
|
// 2. 返回 DTO 或 null
|
||||||
return package?.ToDto();
|
return package?.ToDto();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,15 +20,18 @@ public sealed class MarkTenantAnnouncementReadCommandHandler(
|
|||||||
{
|
{
|
||||||
public async Task<TenantAnnouncementDto?> Handle(MarkTenantAnnouncementReadCommand request, CancellationToken cancellationToken)
|
public async Task<TenantAnnouncementDto?> Handle(MarkTenantAnnouncementReadCommand request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 查询公告
|
||||||
var announcement = await announcementRepository.FindByIdAsync(request.TenantId, request.AnnouncementId, cancellationToken);
|
var announcement = await announcementRepository.FindByIdAsync(request.TenantId, request.AnnouncementId, cancellationToken);
|
||||||
if (announcement == null)
|
if (announcement == null)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 2. 确定用户标识
|
||||||
var userId = currentUserAccessor?.UserId ?? 0;
|
var userId = currentUserAccessor?.UserId ?? 0;
|
||||||
var existing = await readRepository.FindAsync(request.TenantId, request.AnnouncementId, userId == 0 ? null : userId, cancellationToken);
|
var existing = await readRepository.FindAsync(request.TenantId, request.AnnouncementId, userId == 0 ? null : userId, cancellationToken);
|
||||||
|
|
||||||
|
// 3. 如未读则写入已读记录
|
||||||
if (existing == null)
|
if (existing == null)
|
||||||
{
|
{
|
||||||
var record = new TenantAnnouncementRead
|
var record = new TenantAnnouncementRead
|
||||||
@@ -44,6 +47,7 @@ public sealed class MarkTenantAnnouncementReadCommandHandler(
|
|||||||
existing = record;
|
existing = record;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 4. 返回带已读时间的公告 DTO
|
||||||
return announcement.ToDto(true, existing.ReadAt);
|
return announcement.ToDto(true, existing.ReadAt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,19 +14,23 @@ public sealed class MarkTenantBillingPaidCommandHandler(ITenantBillingRepository
|
|||||||
{
|
{
|
||||||
public async Task<TenantBillingDto?> Handle(MarkTenantBillingPaidCommand request, CancellationToken cancellationToken)
|
public async Task<TenantBillingDto?> Handle(MarkTenantBillingPaidCommand request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 查询账单
|
||||||
var bill = await billingRepository.FindByIdAsync(request.TenantId, request.BillingId, cancellationToken);
|
var bill = await billingRepository.FindByIdAsync(request.TenantId, request.BillingId, cancellationToken);
|
||||||
if (bill == null)
|
if (bill == null)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 2. 更新支付状态
|
||||||
bill.AmountPaid = request.AmountPaid;
|
bill.AmountPaid = request.AmountPaid;
|
||||||
bill.Status = TenantBillingStatus.Paid;
|
bill.Status = TenantBillingStatus.Paid;
|
||||||
bill.DueDate = bill.DueDate;
|
bill.DueDate = bill.DueDate;
|
||||||
|
|
||||||
|
// 3. 持久化变更
|
||||||
await billingRepository.UpdateAsync(bill, cancellationToken);
|
await billingRepository.UpdateAsync(bill, cancellationToken);
|
||||||
await billingRepository.SaveChangesAsync(cancellationToken);
|
await billingRepository.SaveChangesAsync(cancellationToken);
|
||||||
|
|
||||||
|
// 4. 返回 DTO
|
||||||
return bill.ToDto();
|
return bill.ToDto();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,12 +13,14 @@ public sealed class MarkTenantNotificationReadCommandHandler(ITenantNotification
|
|||||||
{
|
{
|
||||||
public async Task<TenantNotificationDto?> Handle(MarkTenantNotificationReadCommand request, CancellationToken cancellationToken)
|
public async Task<TenantNotificationDto?> Handle(MarkTenantNotificationReadCommand request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 查询通知
|
||||||
var notification = await notificationRepository.FindByIdAsync(request.TenantId, request.NotificationId, cancellationToken);
|
var notification = await notificationRepository.FindByIdAsync(request.TenantId, request.NotificationId, cancellationToken);
|
||||||
if (notification == null)
|
if (notification == null)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 2. 若未读则标记已读
|
||||||
if (notification.ReadAt == null)
|
if (notification.ReadAt == null)
|
||||||
{
|
{
|
||||||
notification.ReadAt = DateTime.UtcNow;
|
notification.ReadAt = DateTime.UtcNow;
|
||||||
@@ -26,6 +28,7 @@ public sealed class MarkTenantNotificationReadCommandHandler(ITenantNotification
|
|||||||
await notificationRepository.SaveChangesAsync(cancellationToken);
|
await notificationRepository.SaveChangesAsync(cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 3. 返回 DTO
|
||||||
return notification.ToDto();
|
return notification.ToDto();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,30 +20,30 @@ public sealed class RegisterTenantCommandHandler(
|
|||||||
ILogger<RegisterTenantCommandHandler> logger)
|
ILogger<RegisterTenantCommandHandler> logger)
|
||||||
: IRequestHandler<RegisterTenantCommand, TenantDto>
|
: IRequestHandler<RegisterTenantCommand, TenantDto>
|
||||||
{
|
{
|
||||||
private readonly ITenantRepository _tenantRepository = tenantRepository;
|
|
||||||
private readonly IIdGenerator _idGenerator = idGenerator;
|
|
||||||
private readonly ILogger<RegisterTenantCommandHandler> _logger = logger;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task<TenantDto> Handle(RegisterTenantCommand request, CancellationToken cancellationToken)
|
public async Task<TenantDto> Handle(RegisterTenantCommand request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 校验订阅时长
|
||||||
if (request.DurationMonths <= 0)
|
if (request.DurationMonths <= 0)
|
||||||
{
|
{
|
||||||
throw new BusinessException(ErrorCodes.BadRequest, "订阅时长必须大于 0");
|
throw new BusinessException(ErrorCodes.BadRequest, "订阅时长必须大于 0");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (await _tenantRepository.ExistsByCodeAsync(request.Code, cancellationToken))
|
// 2. 检查租户编码唯一性
|
||||||
|
if (await tenantRepository.ExistsByCodeAsync(request.Code, cancellationToken))
|
||||||
{
|
{
|
||||||
throw new BusinessException(ErrorCodes.Conflict, $"租户编码 {request.Code} 已存在");
|
throw new BusinessException(ErrorCodes.Conflict, $"租户编码 {request.Code} 已存在");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 3. 计算生效时间
|
||||||
var now = DateTime.UtcNow;
|
var now = DateTime.UtcNow;
|
||||||
var effectiveFrom = request.EffectiveFrom ?? now;
|
var effectiveFrom = request.EffectiveFrom ?? now;
|
||||||
var effectiveTo = effectiveFrom.AddMonths(request.DurationMonths);
|
var effectiveTo = effectiveFrom.AddMonths(request.DurationMonths);
|
||||||
|
|
||||||
|
// 4. 构建租户实体
|
||||||
var tenant = new Tenant
|
var tenant = new Tenant
|
||||||
{
|
{
|
||||||
Id = _idGenerator.NextId(),
|
Id = idGenerator.NextId(),
|
||||||
Code = request.Code.Trim(),
|
Code = request.Code.Trim(),
|
||||||
Name = request.Name,
|
Name = request.Name,
|
||||||
ShortName = request.ShortName,
|
ShortName = request.ShortName,
|
||||||
@@ -56,9 +56,10 @@ public sealed class RegisterTenantCommandHandler(
|
|||||||
EffectiveTo = effectiveTo
|
EffectiveTo = effectiveTo
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 5. 构建订阅实体
|
||||||
var subscription = new TenantSubscription
|
var subscription = new TenantSubscription
|
||||||
{
|
{
|
||||||
Id = _idGenerator.NextId(),
|
Id = idGenerator.NextId(),
|
||||||
TenantId = tenant.Id,
|
TenantId = tenant.Id,
|
||||||
TenantPackageId = request.TenantPackageId,
|
TenantPackageId = request.TenantPackageId,
|
||||||
EffectiveFrom = effectiveFrom,
|
EffectiveFrom = effectiveFrom,
|
||||||
@@ -69,9 +70,10 @@ public sealed class RegisterTenantCommandHandler(
|
|||||||
Notes = "Init subscription"
|
Notes = "Init subscription"
|
||||||
};
|
};
|
||||||
|
|
||||||
await _tenantRepository.AddTenantAsync(tenant, cancellationToken);
|
// 6. 持久化租户、订阅和审计日志
|
||||||
await _tenantRepository.AddSubscriptionAsync(subscription, cancellationToken);
|
await tenantRepository.AddTenantAsync(tenant, cancellationToken);
|
||||||
await _tenantRepository.AddAuditLogAsync(new TenantAuditLog
|
await tenantRepository.AddSubscriptionAsync(subscription, cancellationToken);
|
||||||
|
await tenantRepository.AddAuditLogAsync(new TenantAuditLog
|
||||||
{
|
{
|
||||||
TenantId = tenant.Id,
|
TenantId = tenant.Id,
|
||||||
Action = TenantAuditAction.RegistrationSubmitted,
|
Action = TenantAuditAction.RegistrationSubmitted,
|
||||||
@@ -79,10 +81,12 @@ public sealed class RegisterTenantCommandHandler(
|
|||||||
Description = $"提交套餐 {request.TenantPackageId},时长 {request.DurationMonths} 月"
|
Description = $"提交套餐 {request.TenantPackageId},时长 {request.DurationMonths} 月"
|
||||||
}, cancellationToken);
|
}, cancellationToken);
|
||||||
|
|
||||||
await _tenantRepository.SaveChangesAsync(cancellationToken);
|
await tenantRepository.SaveChangesAsync(cancellationToken);
|
||||||
|
|
||||||
_logger.LogInformation("已注册租户 {TenantCode}", tenant.Code);
|
// 7. 记录日志
|
||||||
|
logger.LogInformation("已注册租户 {TenantCode}", tenant.Code);
|
||||||
|
|
||||||
|
// 8. 返回 DTO
|
||||||
return TenantMapping.ToDto(tenant, subscription, null);
|
return TenantMapping.ToDto(tenant, subscription, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,31 +17,32 @@ public sealed class ReviewTenantCommandHandler(
|
|||||||
ICurrentUserAccessor currentUserAccessor)
|
ICurrentUserAccessor currentUserAccessor)
|
||||||
: IRequestHandler<ReviewTenantCommand, TenantDto>
|
: IRequestHandler<ReviewTenantCommand, TenantDto>
|
||||||
{
|
{
|
||||||
private readonly ITenantRepository _tenantRepository = tenantRepository;
|
|
||||||
private readonly ICurrentUserAccessor _currentUserAccessor = currentUserAccessor;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task<TenantDto> Handle(ReviewTenantCommand request, CancellationToken cancellationToken)
|
public async Task<TenantDto> Handle(ReviewTenantCommand request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var tenant = await _tenantRepository.FindByIdAsync(request.TenantId, cancellationToken)
|
// 1. 获取租户与认证资料
|
||||||
|
var tenant = await tenantRepository.FindByIdAsync(request.TenantId, cancellationToken)
|
||||||
?? throw new BusinessException(ErrorCodes.NotFound, "租户不存在");
|
?? throw new BusinessException(ErrorCodes.NotFound, "租户不存在");
|
||||||
|
|
||||||
var verification = await _tenantRepository.GetVerificationProfileAsync(request.TenantId, cancellationToken)
|
var verification = await tenantRepository.GetVerificationProfileAsync(request.TenantId, cancellationToken)
|
||||||
?? throw new BusinessException(ErrorCodes.BadRequest, "请先提交实名认证资料");
|
?? throw new BusinessException(ErrorCodes.BadRequest, "请先提交实名认证资料");
|
||||||
|
|
||||||
var subscription = await _tenantRepository.GetActiveSubscriptionAsync(request.TenantId, cancellationToken);
|
var subscription = await tenantRepository.GetActiveSubscriptionAsync(request.TenantId, cancellationToken);
|
||||||
|
|
||||||
var actorName = _currentUserAccessor.IsAuthenticated
|
// 2. 记录审核人
|
||||||
? $"user:{_currentUserAccessor.UserId}"
|
var actorName = currentUserAccessor.IsAuthenticated
|
||||||
|
? $"user:{currentUserAccessor.UserId}"
|
||||||
: "system";
|
: "system";
|
||||||
|
|
||||||
|
// 3. 写入审核信息
|
||||||
verification.ReviewedAt = DateTime.UtcNow;
|
verification.ReviewedAt = DateTime.UtcNow;
|
||||||
verification.ReviewedBy = _currentUserAccessor.UserId == 0 ? null : _currentUserAccessor.UserId;
|
verification.ReviewedBy = currentUserAccessor.UserId == 0 ? null : currentUserAccessor.UserId;
|
||||||
verification.ReviewedByName = actorName;
|
verification.ReviewedByName = actorName;
|
||||||
verification.ReviewRemarks = request.Reason;
|
verification.ReviewRemarks = request.Reason;
|
||||||
|
|
||||||
var previousStatus = tenant.Status;
|
var previousStatus = tenant.Status;
|
||||||
|
|
||||||
|
// 4. 更新租户与订阅状态
|
||||||
if (request.Approve)
|
if (request.Approve)
|
||||||
{
|
{
|
||||||
verification.Status = TenantVerificationStatus.Approved;
|
verification.Status = TenantVerificationStatus.Approved;
|
||||||
@@ -61,26 +62,29 @@ public sealed class ReviewTenantCommandHandler(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await _tenantRepository.UpdateTenantAsync(tenant, cancellationToken);
|
// 5. 持久化租户与认证资料
|
||||||
await _tenantRepository.UpsertVerificationProfileAsync(verification, cancellationToken);
|
await tenantRepository.UpdateTenantAsync(tenant, cancellationToken);
|
||||||
|
await tenantRepository.UpsertVerificationProfileAsync(verification, cancellationToken);
|
||||||
if (subscription != null)
|
if (subscription != null)
|
||||||
{
|
{
|
||||||
await _tenantRepository.UpdateSubscriptionAsync(subscription, cancellationToken);
|
await tenantRepository.UpdateSubscriptionAsync(subscription, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
await _tenantRepository.AddAuditLogAsync(new Domain.Tenants.Entities.TenantAuditLog
|
// 6. 记录审核日志
|
||||||
|
await tenantRepository.AddAuditLogAsync(new Domain.Tenants.Entities.TenantAuditLog
|
||||||
{
|
{
|
||||||
TenantId = tenant.Id,
|
TenantId = tenant.Id,
|
||||||
Action = request.Approve ? TenantAuditAction.VerificationApproved : TenantAuditAction.VerificationRejected,
|
Action = request.Approve ? TenantAuditAction.VerificationApproved : TenantAuditAction.VerificationRejected,
|
||||||
Title = request.Approve ? "审核通过" : "审核驳回",
|
Title = request.Approve ? "审核通过" : "审核驳回",
|
||||||
Description = request.Reason,
|
Description = request.Reason,
|
||||||
OperatorId = _currentUserAccessor.UserId == 0 ? null : _currentUserAccessor.UserId,
|
OperatorId = currentUserAccessor.UserId == 0 ? null : currentUserAccessor.UserId,
|
||||||
OperatorName = actorName,
|
OperatorName = actorName,
|
||||||
PreviousStatus = previousStatus,
|
PreviousStatus = previousStatus,
|
||||||
CurrentStatus = tenant.Status
|
CurrentStatus = tenant.Status
|
||||||
}, cancellationToken);
|
}, cancellationToken);
|
||||||
|
|
||||||
await _tenantRepository.SaveChangesAsync(cancellationToken);
|
// 7. 保存并返回 DTO
|
||||||
|
await tenantRepository.SaveChangesAsync(cancellationToken);
|
||||||
|
|
||||||
return TenantMapping.ToDto(tenant, subscription, verification);
|
return TenantMapping.ToDto(tenant, subscription, verification);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,22 +20,27 @@ public sealed class SearchTenantAnnouncementsQueryHandler(
|
|||||||
{
|
{
|
||||||
public async Task<PagedResult<TenantAnnouncementDto>> Handle(SearchTenantAnnouncementsQuery request, CancellationToken cancellationToken)
|
public async Task<PagedResult<TenantAnnouncementDto>> Handle(SearchTenantAnnouncementsQuery request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 过滤有效期条件
|
||||||
var effectiveAt = request.OnlyEffective == true ? DateTime.UtcNow : (DateTime?)null;
|
var effectiveAt = request.OnlyEffective == true ? DateTime.UtcNow : (DateTime?)null;
|
||||||
var announcements = await announcementRepository.SearchAsync(request.TenantId, request.AnnouncementType, request.IsActive, effectiveAt, cancellationToken);
|
var announcements = await announcementRepository.SearchAsync(request.TenantId, request.AnnouncementType, request.IsActive, effectiveAt, cancellationToken);
|
||||||
|
|
||||||
|
// 2. 排序(优先级/时间)
|
||||||
var ordered = announcements
|
var ordered = announcements
|
||||||
.OrderByDescending(x => x.Priority)
|
.OrderByDescending(x => x.Priority)
|
||||||
.ThenByDescending(x => x.CreatedAt)
|
.ThenByDescending(x => x.CreatedAt)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
|
// 3. 计算分页参数
|
||||||
var page = request.Page <= 0 ? 1 : request.Page;
|
var page = request.Page <= 0 ? 1 : request.Page;
|
||||||
var size = request.PageSize <= 0 ? 20 : request.PageSize;
|
var size = request.PageSize <= 0 ? 20 : request.PageSize;
|
||||||
|
|
||||||
|
// 4. 分页
|
||||||
var pageItems = ordered
|
var pageItems = ordered
|
||||||
.Skip((page - 1) * size)
|
.Skip((page - 1) * size)
|
||||||
.Take(size)
|
.Take(size)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
|
// 5. 构建已读映射
|
||||||
var announcementIds = pageItems.Select(x => x.Id).ToArray();
|
var announcementIds = pageItems.Select(x => x.Id).ToArray();
|
||||||
var userId = currentUserAccessor?.UserId ?? 0;
|
var userId = currentUserAccessor?.UserId ?? 0;
|
||||||
|
|
||||||
@@ -65,6 +70,7 @@ public sealed class SearchTenantAnnouncementsQueryHandler(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 6. 映射 DTO 并带上已读状态
|
||||||
var items = pageItems
|
var items = pageItems
|
||||||
.Select(a =>
|
.Select(a =>
|
||||||
{
|
{
|
||||||
@@ -73,6 +79,7 @@ public sealed class SearchTenantAnnouncementsQueryHandler(
|
|||||||
})
|
})
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
|
// 7. 返回分页结果
|
||||||
return new PagedResult<TenantAnnouncementDto>(items, page, size, ordered.Count);
|
return new PagedResult<TenantAnnouncementDto>(items, page, size, ordered.Count);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,13 +15,16 @@ public sealed class SearchTenantBillsQueryHandler(ITenantBillingRepository billi
|
|||||||
{
|
{
|
||||||
public async Task<PagedResult<TenantBillingDto>> Handle(SearchTenantBillsQuery request, CancellationToken cancellationToken)
|
public async Task<PagedResult<TenantBillingDto>> Handle(SearchTenantBillsQuery request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 查询账单
|
||||||
var bills = await billingRepository.SearchAsync(request.TenantId, request.Status, request.From, request.To, cancellationToken);
|
var bills = await billingRepository.SearchAsync(request.TenantId, request.Status, request.From, request.To, cancellationToken);
|
||||||
|
|
||||||
|
// 2. 排序与分页
|
||||||
var ordered = bills.OrderByDescending(x => x.PeriodEnd).ToList();
|
var ordered = bills.OrderByDescending(x => x.PeriodEnd).ToList();
|
||||||
var page = request.Page <= 0 ? 1 : request.Page;
|
var page = request.Page <= 0 ? 1 : request.Page;
|
||||||
var size = request.PageSize <= 0 ? 20 : request.PageSize;
|
var size = request.PageSize <= 0 ? 20 : request.PageSize;
|
||||||
var items = ordered.Skip((page - 1) * size).Take(size).Select(x => x.ToDto()).ToList();
|
var items = ordered.Skip((page - 1) * size).Take(size).Select(x => x.ToDto()).ToList();
|
||||||
|
|
||||||
|
// 3. 返回分页结果
|
||||||
return new PagedResult<TenantBillingDto>(items, page, size, ordered.Count);
|
return new PagedResult<TenantBillingDto>(items, page, size, ordered.Count);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ public sealed class SearchTenantNotificationsQueryHandler(ITenantNotificationRep
|
|||||||
{
|
{
|
||||||
public async Task<PagedResult<TenantNotificationDto>> Handle(SearchTenantNotificationsQuery request, CancellationToken cancellationToken)
|
public async Task<PagedResult<TenantNotificationDto>> Handle(SearchTenantNotificationsQuery request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 查询通知
|
||||||
var notifications = await notificationRepository.SearchAsync(
|
var notifications = await notificationRepository.SearchAsync(
|
||||||
request.TenantId,
|
request.TenantId,
|
||||||
request.Severity,
|
request.Severity,
|
||||||
@@ -23,11 +24,13 @@ public sealed class SearchTenantNotificationsQueryHandler(ITenantNotificationRep
|
|||||||
null,
|
null,
|
||||||
cancellationToken);
|
cancellationToken);
|
||||||
|
|
||||||
|
// 2. 排序与分页
|
||||||
var ordered = notifications.OrderByDescending(x => x.SentAt).ToList();
|
var ordered = notifications.OrderByDescending(x => x.SentAt).ToList();
|
||||||
var page = request.Page <= 0 ? 1 : request.Page;
|
var page = request.Page <= 0 ? 1 : request.Page;
|
||||||
var size = request.PageSize <= 0 ? 20 : request.PageSize;
|
var size = request.PageSize <= 0 ? 20 : request.PageSize;
|
||||||
var items = ordered.Skip((page - 1) * size).Take(size).Select(x => x.ToDto()).ToList();
|
var items = ordered.Skip((page - 1) * size).Take(size).Select(x => x.ToDto()).ToList();
|
||||||
|
|
||||||
|
// 3. 返回分页结果
|
||||||
return new PagedResult<TenantNotificationDto>(items, page, size, ordered.Count);
|
return new PagedResult<TenantNotificationDto>(items, page, size, ordered.Count);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,8 +16,10 @@ public sealed class SearchTenantPackagesQueryHandler(ITenantPackageRepository pa
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task<PagedResult<TenantPackageDto>> Handle(SearchTenantPackagesQuery request, CancellationToken cancellationToken)
|
public async Task<PagedResult<TenantPackageDto>> Handle(SearchTenantPackagesQuery request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 查询套餐
|
||||||
var packages = await packageRepository.SearchAsync(request.Keyword, request.IsActive, cancellationToken);
|
var packages = await packageRepository.SearchAsync(request.Keyword, request.IsActive, cancellationToken);
|
||||||
|
|
||||||
|
// 2. 排序与分页
|
||||||
var ordered = packages.OrderByDescending(x => x.CreatedAt).ToList();
|
var ordered = packages.OrderByDescending(x => x.CreatedAt).ToList();
|
||||||
var pageIndex = request.Page <= 0 ? 1 : request.Page;
|
var pageIndex = request.Page <= 0 ? 1 : request.Page;
|
||||||
var size = request.PageSize <= 0 ? 20 : request.PageSize;
|
var size = request.PageSize <= 0 ? 20 : request.PageSize;
|
||||||
@@ -28,6 +30,7 @@ public sealed class SearchTenantPackagesQueryHandler(ITenantPackageRepository pa
|
|||||||
.Select(x => x.ToDto())
|
.Select(x => x.ToDto())
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
|
// 3. 返回分页结果
|
||||||
return new PagedResult<TenantPackageDto>(pagedItems, pageIndex, size, ordered.Count);
|
return new PagedResult<TenantPackageDto>(pagedItems, pageIndex, size, ordered.Count);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,27 +13,29 @@ namespace TakeoutSaaS.Application.App.Tenants.Handlers;
|
|||||||
public sealed class SearchTenantsQueryHandler(ITenantRepository tenantRepository)
|
public sealed class SearchTenantsQueryHandler(ITenantRepository tenantRepository)
|
||||||
: IRequestHandler<SearchTenantsQuery, PagedResult<TenantDto>>
|
: IRequestHandler<SearchTenantsQuery, PagedResult<TenantDto>>
|
||||||
{
|
{
|
||||||
private readonly ITenantRepository _tenantRepository = tenantRepository;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task<PagedResult<TenantDto>> Handle(SearchTenantsQuery request, CancellationToken cancellationToken)
|
public async Task<PagedResult<TenantDto>> Handle(SearchTenantsQuery request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var tenants = await _tenantRepository.SearchAsync(request.Status, request.Keyword, cancellationToken);
|
// 1. 查询租户列表
|
||||||
|
var tenants = await tenantRepository.SearchAsync(request.Status, request.Keyword, cancellationToken);
|
||||||
var total = tenants.Count;
|
var total = tenants.Count;
|
||||||
|
|
||||||
|
// 2. 分页
|
||||||
var paged = tenants
|
var paged = tenants
|
||||||
.Skip((request.Page - 1) * request.PageSize)
|
.Skip((request.Page - 1) * request.PageSize)
|
||||||
.Take(request.PageSize)
|
.Take(request.PageSize)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
|
// 3. 映射 DTO(带订阅与认证)
|
||||||
var result = new List<TenantDto>(paged.Count);
|
var result = new List<TenantDto>(paged.Count);
|
||||||
foreach (var tenant in paged)
|
foreach (var tenant in paged)
|
||||||
{
|
{
|
||||||
var subscription = await _tenantRepository.GetActiveSubscriptionAsync(tenant.Id, cancellationToken);
|
var subscription = await tenantRepository.GetActiveSubscriptionAsync(tenant.Id, cancellationToken);
|
||||||
var verification = await _tenantRepository.GetVerificationProfileAsync(tenant.Id, cancellationToken);
|
var verification = await tenantRepository.GetVerificationProfileAsync(tenant.Id, cancellationToken);
|
||||||
result.Add(TenantMapping.ToDto(tenant, subscription, verification));
|
result.Add(TenantMapping.ToDto(tenant, subscription, verification));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 4. 返回分页结果
|
||||||
return new PagedResult<TenantDto>(result, request.Page, request.PageSize, total);
|
return new PagedResult<TenantDto>(result, request.Page, request.PageSize, total);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,18 +18,18 @@ public sealed class SubmitTenantVerificationCommandHandler(
|
|||||||
IIdGenerator idGenerator)
|
IIdGenerator idGenerator)
|
||||||
: IRequestHandler<SubmitTenantVerificationCommand, TenantVerificationDto>
|
: IRequestHandler<SubmitTenantVerificationCommand, TenantVerificationDto>
|
||||||
{
|
{
|
||||||
private readonly ITenantRepository _tenantRepository = tenantRepository;
|
|
||||||
private readonly IIdGenerator _idGenerator = idGenerator;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task<TenantVerificationDto> Handle(SubmitTenantVerificationCommand request, CancellationToken cancellationToken)
|
public async Task<TenantVerificationDto> Handle(SubmitTenantVerificationCommand request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var tenant = await _tenantRepository.FindByIdAsync(request.TenantId, cancellationToken)
|
// 1. 获取租户
|
||||||
|
var tenant = await tenantRepository.FindByIdAsync(request.TenantId, cancellationToken)
|
||||||
?? throw new BusinessException(ErrorCodes.NotFound, "租户不存在");
|
?? throw new BusinessException(ErrorCodes.NotFound, "租户不存在");
|
||||||
|
|
||||||
var profile = await _tenantRepository.GetVerificationProfileAsync(request.TenantId, cancellationToken)
|
// 2. 读取或初始化实名资料
|
||||||
?? new TenantVerificationProfile { Id = _idGenerator.NextId(), TenantId = tenant.Id };
|
var profile = await tenantRepository.GetVerificationProfileAsync(request.TenantId, cancellationToken)
|
||||||
|
?? new TenantVerificationProfile { Id = idGenerator.NextId(), TenantId = tenant.Id };
|
||||||
|
|
||||||
|
// 3. 填充资料
|
||||||
profile.BusinessLicenseNumber = request.BusinessLicenseNumber;
|
profile.BusinessLicenseNumber = request.BusinessLicenseNumber;
|
||||||
profile.BusinessLicenseUrl = request.BusinessLicenseUrl;
|
profile.BusinessLicenseUrl = request.BusinessLicenseUrl;
|
||||||
profile.LegalPersonName = request.LegalPersonName;
|
profile.LegalPersonName = request.LegalPersonName;
|
||||||
@@ -47,16 +47,18 @@ public sealed class SubmitTenantVerificationCommandHandler(
|
|||||||
profile.ReviewedBy = null;
|
profile.ReviewedBy = null;
|
||||||
profile.ReviewedByName = null;
|
profile.ReviewedByName = null;
|
||||||
|
|
||||||
await _tenantRepository.UpsertVerificationProfileAsync(profile, cancellationToken);
|
// 4. 保存资料并记录审计
|
||||||
await _tenantRepository.AddAuditLogAsync(new TenantAuditLog
|
await tenantRepository.UpsertVerificationProfileAsync(profile, cancellationToken);
|
||||||
|
await tenantRepository.AddAuditLogAsync(new TenantAuditLog
|
||||||
{
|
{
|
||||||
TenantId = tenant.Id,
|
TenantId = tenant.Id,
|
||||||
Action = TenantAuditAction.VerificationSubmitted,
|
Action = TenantAuditAction.VerificationSubmitted,
|
||||||
Title = "提交实名认证资料",
|
Title = "提交实名认证资料",
|
||||||
Description = request.BusinessLicenseNumber
|
Description = request.BusinessLicenseNumber
|
||||||
}, cancellationToken);
|
}, cancellationToken);
|
||||||
await _tenantRepository.SaveChangesAsync(cancellationToken);
|
await tenantRepository.SaveChangesAsync(cancellationToken);
|
||||||
|
|
||||||
|
// 5. 返回 DTO
|
||||||
return profile.ToVerificationDto()
|
return profile.ToVerificationDto()
|
||||||
?? throw new BusinessException(ErrorCodes.InternalServerError, "实名资料保存失败");
|
?? throw new BusinessException(ErrorCodes.InternalServerError, "实名资料保存失败");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,17 +15,20 @@ public sealed class UpdateTenantAnnouncementCommandHandler(ITenantAnnouncementRe
|
|||||||
{
|
{
|
||||||
public async Task<TenantAnnouncementDto?> Handle(UpdateTenantAnnouncementCommand request, CancellationToken cancellationToken)
|
public async Task<TenantAnnouncementDto?> Handle(UpdateTenantAnnouncementCommand request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 校验输入
|
||||||
if (string.IsNullOrWhiteSpace(request.Title) || string.IsNullOrWhiteSpace(request.Content))
|
if (string.IsNullOrWhiteSpace(request.Title) || string.IsNullOrWhiteSpace(request.Content))
|
||||||
{
|
{
|
||||||
throw new BusinessException(ErrorCodes.BadRequest, "公告标题和内容不能为空");
|
throw new BusinessException(ErrorCodes.BadRequest, "公告标题和内容不能为空");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 2. 查询公告
|
||||||
var announcement = await announcementRepository.FindByIdAsync(request.TenantId, request.AnnouncementId, cancellationToken);
|
var announcement = await announcementRepository.FindByIdAsync(request.TenantId, request.AnnouncementId, cancellationToken);
|
||||||
if (announcement == null)
|
if (announcement == null)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 3. 更新字段
|
||||||
announcement.Title = request.Title.Trim();
|
announcement.Title = request.Title.Trim();
|
||||||
announcement.Content = request.Content;
|
announcement.Content = request.Content;
|
||||||
announcement.AnnouncementType = request.AnnouncementType;
|
announcement.AnnouncementType = request.AnnouncementType;
|
||||||
@@ -34,9 +37,11 @@ public sealed class UpdateTenantAnnouncementCommandHandler(ITenantAnnouncementRe
|
|||||||
announcement.EffectiveTo = request.EffectiveTo;
|
announcement.EffectiveTo = request.EffectiveTo;
|
||||||
announcement.IsActive = request.IsActive;
|
announcement.IsActive = request.IsActive;
|
||||||
|
|
||||||
|
// 4. 持久化
|
||||||
await announcementRepository.UpdateAsync(announcement, cancellationToken);
|
await announcementRepository.UpdateAsync(announcement, cancellationToken);
|
||||||
await announcementRepository.SaveChangesAsync(cancellationToken);
|
await announcementRepository.SaveChangesAsync(cancellationToken);
|
||||||
|
|
||||||
|
// 5. 返回 DTO
|
||||||
return announcement.ToDto(false, null);
|
return announcement.ToDto(false, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,17 +16,20 @@ public sealed class UpdateTenantPackageCommandHandler(ITenantPackageRepository p
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task<TenantPackageDto?> Handle(UpdateTenantPackageCommand request, CancellationToken cancellationToken)
|
public async Task<TenantPackageDto?> Handle(UpdateTenantPackageCommand request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 校验必填项
|
||||||
if (string.IsNullOrWhiteSpace(request.Name))
|
if (string.IsNullOrWhiteSpace(request.Name))
|
||||||
{
|
{
|
||||||
throw new BusinessException(ErrorCodes.BadRequest, "套餐名称不能为空");
|
throw new BusinessException(ErrorCodes.BadRequest, "套餐名称不能为空");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 2. 查询套餐
|
||||||
var package = await packageRepository.FindByIdAsync(request.TenantPackageId, cancellationToken);
|
var package = await packageRepository.FindByIdAsync(request.TenantPackageId, cancellationToken);
|
||||||
if (package == null)
|
if (package == null)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 3. 更新字段
|
||||||
package.Name = request.Name.Trim();
|
package.Name = request.Name.Trim();
|
||||||
package.Description = request.Description;
|
package.Description = request.Description;
|
||||||
package.PackageType = request.PackageType;
|
package.PackageType = request.PackageType;
|
||||||
@@ -40,6 +43,7 @@ public sealed class UpdateTenantPackageCommandHandler(ITenantPackageRepository p
|
|||||||
package.FeaturePoliciesJson = request.FeaturePoliciesJson;
|
package.FeaturePoliciesJson = request.FeaturePoliciesJson;
|
||||||
package.IsActive = request.IsActive;
|
package.IsActive = request.IsActive;
|
||||||
|
|
||||||
|
// 4. 持久化并返回
|
||||||
await packageRepository.UpdateAsync(package, cancellationToken);
|
await packageRepository.UpdateAsync(package, cancellationToken);
|
||||||
await packageRepository.SaveChangesAsync(cancellationToken);
|
await packageRepository.SaveChangesAsync(cancellationToken);
|
||||||
|
|
||||||
|
|||||||
@@ -21,18 +21,20 @@ public sealed class DictionaryAppService(
|
|||||||
ITenantProvider tenantProvider,
|
ITenantProvider tenantProvider,
|
||||||
ILogger<DictionaryAppService> logger) : IDictionaryAppService
|
ILogger<DictionaryAppService> logger) : IDictionaryAppService
|
||||||
{
|
{
|
||||||
|
|
||||||
public async Task<DictionaryGroupDto> CreateGroupAsync(CreateDictionaryGroupRequest request, CancellationToken cancellationToken = default)
|
public async Task<DictionaryGroupDto> CreateGroupAsync(CreateDictionaryGroupRequest request, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
|
// 1. 规范化编码并确定租户
|
||||||
var normalizedCode = NormalizeCode(request.Code);
|
var normalizedCode = NormalizeCode(request.Code);
|
||||||
var targetTenant = ResolveTargetTenant(request.Scope);
|
var targetTenant = ResolveTargetTenant(request.Scope);
|
||||||
|
|
||||||
|
// 2. 校验编码唯一
|
||||||
var existing = await repository.FindGroupByCodeAsync(normalizedCode, cancellationToken);
|
var existing = await repository.FindGroupByCodeAsync(normalizedCode, cancellationToken);
|
||||||
if (existing != null)
|
if (existing != null)
|
||||||
{
|
{
|
||||||
throw new BusinessException(ErrorCodes.Conflict, $"字典分组编码 {normalizedCode} 已存在");
|
throw new BusinessException(ErrorCodes.Conflict, $"字典分组编码 {normalizedCode} 已存在");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 3. 构建分组实体
|
||||||
var group = new DictionaryGroup
|
var group = new DictionaryGroup
|
||||||
{
|
{
|
||||||
Id = 0,
|
Id = 0,
|
||||||
@@ -44,6 +46,7 @@ public sealed class DictionaryAppService(
|
|||||||
IsEnabled = true
|
IsEnabled = true
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 4. 持久化并返回
|
||||||
await repository.AddGroupAsync(group, cancellationToken);
|
await repository.AddGroupAsync(group, cancellationToken);
|
||||||
await repository.SaveChangesAsync(cancellationToken);
|
await repository.SaveChangesAsync(cancellationToken);
|
||||||
logger.LogInformation("创建字典分组:{Code}({Scope})", group.Code, group.Scope);
|
logger.LogInformation("创建字典分组:{Code}({Scope})", group.Code, group.Scope);
|
||||||
@@ -52,13 +55,16 @@ public sealed class DictionaryAppService(
|
|||||||
|
|
||||||
public async Task<DictionaryGroupDto> UpdateGroupAsync(long groupId, UpdateDictionaryGroupRequest request, CancellationToken cancellationToken = default)
|
public async Task<DictionaryGroupDto> UpdateGroupAsync(long groupId, UpdateDictionaryGroupRequest request, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
|
// 1. 读取分组并校验权限
|
||||||
var group = await RequireGroupAsync(groupId, cancellationToken);
|
var group = await RequireGroupAsync(groupId, cancellationToken);
|
||||||
EnsureScopePermission(group.Scope);
|
EnsureScopePermission(group.Scope);
|
||||||
|
|
||||||
|
// 2. 更新字段
|
||||||
group.Name = request.Name.Trim();
|
group.Name = request.Name.Trim();
|
||||||
group.Description = request.Description?.Trim();
|
group.Description = request.Description?.Trim();
|
||||||
group.IsEnabled = request.IsEnabled;
|
group.IsEnabled = request.IsEnabled;
|
||||||
|
|
||||||
|
// 3. 持久化并失效缓存
|
||||||
await repository.SaveChangesAsync(cancellationToken);
|
await repository.SaveChangesAsync(cancellationToken);
|
||||||
await InvalidateCacheAsync(group, cancellationToken);
|
await InvalidateCacheAsync(group, cancellationToken);
|
||||||
logger.LogInformation("更新字典分组:{GroupId}", group.Id);
|
logger.LogInformation("更新字典分组:{GroupId}", group.Id);
|
||||||
@@ -67,9 +73,11 @@ public sealed class DictionaryAppService(
|
|||||||
|
|
||||||
public async Task DeleteGroupAsync(long groupId, CancellationToken cancellationToken = default)
|
public async Task DeleteGroupAsync(long groupId, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
|
// 1. 读取分组并校验权限
|
||||||
var group = await RequireGroupAsync(groupId, cancellationToken);
|
var group = await RequireGroupAsync(groupId, cancellationToken);
|
||||||
EnsureScopePermission(group.Scope);
|
EnsureScopePermission(group.Scope);
|
||||||
|
|
||||||
|
// 2. 删除并失效缓存
|
||||||
await repository.RemoveGroupAsync(group, cancellationToken);
|
await repository.RemoveGroupAsync(group, cancellationToken);
|
||||||
await repository.SaveChangesAsync(cancellationToken);
|
await repository.SaveChangesAsync(cancellationToken);
|
||||||
await InvalidateCacheAsync(group, cancellationToken);
|
await InvalidateCacheAsync(group, cancellationToken);
|
||||||
@@ -78,10 +86,12 @@ public sealed class DictionaryAppService(
|
|||||||
|
|
||||||
public async Task<IReadOnlyList<DictionaryGroupDto>> SearchGroupsAsync(DictionaryGroupQuery request, CancellationToken cancellationToken = default)
|
public async Task<IReadOnlyList<DictionaryGroupDto>> SearchGroupsAsync(DictionaryGroupQuery request, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
|
// 1. 确定查询范围并校验权限
|
||||||
var tenantId = tenantProvider.GetCurrentTenantId();
|
var tenantId = tenantProvider.GetCurrentTenantId();
|
||||||
var scope = ResolveScopeForQuery(request.Scope, tenantId);
|
var scope = ResolveScopeForQuery(request.Scope, tenantId);
|
||||||
EnsureScopePermission(scope);
|
EnsureScopePermission(scope);
|
||||||
|
|
||||||
|
// 2. 查询分组及可选项
|
||||||
var groups = await repository.SearchGroupsAsync(scope, cancellationToken);
|
var groups = await repository.SearchGroupsAsync(scope, cancellationToken);
|
||||||
var includeItems = request.IncludeItems;
|
var includeItems = request.IncludeItems;
|
||||||
var result = new List<DictionaryGroupDto>(groups.Count);
|
var result = new List<DictionaryGroupDto>(groups.Count);
|
||||||
@@ -91,6 +101,7 @@ public sealed class DictionaryAppService(
|
|||||||
IReadOnlyList<DictionaryItemDto> items = Array.Empty<DictionaryItemDto>();
|
IReadOnlyList<DictionaryItemDto> items = Array.Empty<DictionaryItemDto>();
|
||||||
if (includeItems)
|
if (includeItems)
|
||||||
{
|
{
|
||||||
|
// 查询分组下字典项
|
||||||
var itemEntities = await repository.GetItemsByGroupIdAsync(group.Id, cancellationToken);
|
var itemEntities = await repository.GetItemsByGroupIdAsync(group.Id, cancellationToken);
|
||||||
items = itemEntities.Select(MapItem).ToList();
|
items = itemEntities.Select(MapItem).ToList();
|
||||||
}
|
}
|
||||||
@@ -103,9 +114,11 @@ public sealed class DictionaryAppService(
|
|||||||
|
|
||||||
public async Task<DictionaryItemDto> CreateItemAsync(CreateDictionaryItemRequest request, CancellationToken cancellationToken = default)
|
public async Task<DictionaryItemDto> CreateItemAsync(CreateDictionaryItemRequest request, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
|
// 1. 校验分组与权限
|
||||||
var group = await RequireGroupAsync(request.GroupId, cancellationToken);
|
var group = await RequireGroupAsync(request.GroupId, cancellationToken);
|
||||||
EnsureScopePermission(group.Scope);
|
EnsureScopePermission(group.Scope);
|
||||||
|
|
||||||
|
// 2. 构建字典项
|
||||||
var item = new DictionaryItem
|
var item = new DictionaryItem
|
||||||
{
|
{
|
||||||
Id = 0,
|
Id = 0,
|
||||||
@@ -119,6 +132,7 @@ public sealed class DictionaryAppService(
|
|||||||
IsEnabled = request.IsEnabled
|
IsEnabled = request.IsEnabled
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 3. 持久化并失效缓存
|
||||||
await repository.AddItemAsync(item, cancellationToken);
|
await repository.AddItemAsync(item, cancellationToken);
|
||||||
await repository.SaveChangesAsync(cancellationToken);
|
await repository.SaveChangesAsync(cancellationToken);
|
||||||
await InvalidateCacheAsync(group, cancellationToken);
|
await InvalidateCacheAsync(group, cancellationToken);
|
||||||
@@ -128,16 +142,19 @@ public sealed class DictionaryAppService(
|
|||||||
|
|
||||||
public async Task<DictionaryItemDto> UpdateItemAsync(long itemId, UpdateDictionaryItemRequest request, CancellationToken cancellationToken = default)
|
public async Task<DictionaryItemDto> UpdateItemAsync(long itemId, UpdateDictionaryItemRequest request, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
|
// 1. 读取字典项与分组并校验权限
|
||||||
var item = await RequireItemAsync(itemId, cancellationToken);
|
var item = await RequireItemAsync(itemId, cancellationToken);
|
||||||
var group = await RequireGroupAsync(item.GroupId, cancellationToken);
|
var group = await RequireGroupAsync(item.GroupId, cancellationToken);
|
||||||
EnsureScopePermission(group.Scope);
|
EnsureScopePermission(group.Scope);
|
||||||
|
|
||||||
|
// 2. 更新字段
|
||||||
item.Value = request.Value.Trim();
|
item.Value = request.Value.Trim();
|
||||||
item.Description = request.Description?.Trim();
|
item.Description = request.Description?.Trim();
|
||||||
item.SortOrder = request.SortOrder;
|
item.SortOrder = request.SortOrder;
|
||||||
item.IsDefault = request.IsDefault;
|
item.IsDefault = request.IsDefault;
|
||||||
item.IsEnabled = request.IsEnabled;
|
item.IsEnabled = request.IsEnabled;
|
||||||
|
|
||||||
|
// 3. 持久化并失效缓存
|
||||||
await repository.SaveChangesAsync(cancellationToken);
|
await repository.SaveChangesAsync(cancellationToken);
|
||||||
await InvalidateCacheAsync(group, cancellationToken);
|
await InvalidateCacheAsync(group, cancellationToken);
|
||||||
logger.LogInformation("更新字典项:{ItemId}", item.Id);
|
logger.LogInformation("更新字典项:{ItemId}", item.Id);
|
||||||
@@ -146,10 +163,12 @@ public sealed class DictionaryAppService(
|
|||||||
|
|
||||||
public async Task DeleteItemAsync(long itemId, CancellationToken cancellationToken = default)
|
public async Task DeleteItemAsync(long itemId, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
|
// 1. 读取字典项与分组并校验权限
|
||||||
var item = await RequireItemAsync(itemId, cancellationToken);
|
var item = await RequireItemAsync(itemId, cancellationToken);
|
||||||
var group = await RequireGroupAsync(item.GroupId, cancellationToken);
|
var group = await RequireGroupAsync(item.GroupId, cancellationToken);
|
||||||
EnsureScopePermission(group.Scope);
|
EnsureScopePermission(group.Scope);
|
||||||
|
|
||||||
|
// 2. 删除并失效缓存
|
||||||
await repository.RemoveItemAsync(item, cancellationToken);
|
await repository.RemoveItemAsync(item, cancellationToken);
|
||||||
await repository.SaveChangesAsync(cancellationToken);
|
await repository.SaveChangesAsync(cancellationToken);
|
||||||
await InvalidateCacheAsync(group, cancellationToken);
|
await InvalidateCacheAsync(group, cancellationToken);
|
||||||
@@ -158,6 +177,7 @@ public sealed class DictionaryAppService(
|
|||||||
|
|
||||||
public async Task<IReadOnlyDictionary<string, IReadOnlyList<DictionaryItemDto>>> GetCachedItemsAsync(DictionaryBatchQueryRequest request, CancellationToken cancellationToken = default)
|
public async Task<IReadOnlyDictionary<string, IReadOnlyList<DictionaryItemDto>>> GetCachedItemsAsync(DictionaryBatchQueryRequest request, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
|
// 1. 规范化编码
|
||||||
var normalizedCodes = request.Codes
|
var normalizedCodes = request.Codes
|
||||||
.Where(code => !string.IsNullOrWhiteSpace(code))
|
.Where(code => !string.IsNullOrWhiteSpace(code))
|
||||||
.Select(NormalizeCode)
|
.Select(NormalizeCode)
|
||||||
@@ -169,6 +189,7 @@ public sealed class DictionaryAppService(
|
|||||||
return new Dictionary<string, IReadOnlyList<DictionaryItemDto>>(StringComparer.OrdinalIgnoreCase);
|
return new Dictionary<string, IReadOnlyList<DictionaryItemDto>>(StringComparer.OrdinalIgnoreCase);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 2. 按租户合并系统与业务字典
|
||||||
var tenantId = tenantProvider.GetCurrentTenantId();
|
var tenantId = tenantProvider.GetCurrentTenantId();
|
||||||
var result = new Dictionary<string, IReadOnlyList<DictionaryItemDto>>(StringComparer.OrdinalIgnoreCase);
|
var result = new Dictionary<string, IReadOnlyList<DictionaryItemDto>>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
@@ -190,6 +211,7 @@ public sealed class DictionaryAppService(
|
|||||||
|
|
||||||
private async Task<DictionaryGroup> RequireGroupAsync(long groupId, CancellationToken cancellationToken)
|
private async Task<DictionaryGroup> RequireGroupAsync(long groupId, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 读取分组,找不到抛异常
|
||||||
var group = await repository.FindGroupByIdAsync(groupId, cancellationToken);
|
var group = await repository.FindGroupByIdAsync(groupId, cancellationToken);
|
||||||
if (group == null)
|
if (group == null)
|
||||||
{
|
{
|
||||||
@@ -201,6 +223,7 @@ public sealed class DictionaryAppService(
|
|||||||
|
|
||||||
private async Task<DictionaryItem> RequireItemAsync(long itemId, CancellationToken cancellationToken)
|
private async Task<DictionaryItem> RequireItemAsync(long itemId, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 读取字典项,找不到抛异常
|
||||||
var item = await repository.FindItemByIdAsync(itemId, cancellationToken);
|
var item = await repository.FindItemByIdAsync(itemId, cancellationToken);
|
||||||
if (item == null)
|
if (item == null)
|
||||||
{
|
{
|
||||||
@@ -269,12 +292,14 @@ public sealed class DictionaryAppService(
|
|||||||
|
|
||||||
private async Task<IReadOnlyList<DictionaryItemDto>> GetOrLoadCacheAsync(long tenantId, string code, CancellationToken cancellationToken)
|
private async Task<IReadOnlyList<DictionaryItemDto>> GetOrLoadCacheAsync(long tenantId, string code, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 先查缓存
|
||||||
var cached = await cache.GetAsync(tenantId, code, cancellationToken);
|
var cached = await cache.GetAsync(tenantId, code, cancellationToken);
|
||||||
if (cached != null)
|
if (cached != null)
|
||||||
{
|
{
|
||||||
return cached;
|
return cached;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 2. 从仓储加载并写入缓存
|
||||||
var entities = await repository.GetItemsByCodesAsync(new[] { code }, tenantId, includeSystem: false, cancellationToken);
|
var entities = await repository.GetItemsByCodesAsync(new[] { code }, tenantId, includeSystem: false, cancellationToken);
|
||||||
var items = entities
|
var items = entities
|
||||||
.Where(item => item.IsEnabled && (item.Group?.IsEnabled ?? true))
|
.Where(item => item.IsEnabled && (item.Group?.IsEnabled ?? true))
|
||||||
|
|||||||
@@ -15,9 +15,14 @@ public sealed class AssignUserRolesCommandHandler(
|
|||||||
{
|
{
|
||||||
public async Task<bool> Handle(AssignUserRolesCommand request, CancellationToken cancellationToken)
|
public async Task<bool> Handle(AssignUserRolesCommand request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 获取租户上下文
|
||||||
var tenantId = tenantProvider.GetCurrentTenantId();
|
var tenantId = tenantProvider.GetCurrentTenantId();
|
||||||
|
|
||||||
|
// 2. 覆盖式绑定角色
|
||||||
await userRoleRepository.ReplaceUserRolesAsync(tenantId, request.UserId, request.RoleIds, cancellationToken);
|
await userRoleRepository.ReplaceUserRolesAsync(tenantId, request.UserId, request.RoleIds, cancellationToken);
|
||||||
await userRoleRepository.SaveChangesAsync(cancellationToken);
|
await userRoleRepository.SaveChangesAsync(cancellationToken);
|
||||||
|
|
||||||
|
// 3. 返回执行结果
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,9 +15,14 @@ public sealed class BindRolePermissionsCommandHandler(
|
|||||||
{
|
{
|
||||||
public async Task<bool> Handle(BindRolePermissionsCommand request, CancellationToken cancellationToken)
|
public async Task<bool> Handle(BindRolePermissionsCommand request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 获取租户上下文
|
||||||
var tenantId = tenantProvider.GetCurrentTenantId();
|
var tenantId = tenantProvider.GetCurrentTenantId();
|
||||||
|
|
||||||
|
// 2. 覆盖式绑定权限
|
||||||
await rolePermissionRepository.ReplaceRolePermissionsAsync(tenantId, request.RoleId, request.PermissionIds, cancellationToken);
|
await rolePermissionRepository.ReplaceRolePermissionsAsync(tenantId, request.RoleId, request.PermissionIds, cancellationToken);
|
||||||
await rolePermissionRepository.SaveChangesAsync(cancellationToken);
|
await rolePermissionRepository.SaveChangesAsync(cancellationToken);
|
||||||
|
|
||||||
|
// 3. 返回执行结果
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ public sealed class CopyRoleTemplateCommandHandler(
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task<RoleDto> Handle(CopyRoleTemplateCommand request, CancellationToken cancellationToken)
|
public async Task<RoleDto> Handle(CopyRoleTemplateCommand request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 查询模板与模板权限
|
||||||
var template = await roleTemplateRepository.FindByCodeAsync(request.TemplateCode, cancellationToken)
|
var template = await roleTemplateRepository.FindByCodeAsync(request.TemplateCode, cancellationToken)
|
||||||
?? throw new BusinessException(ErrorCodes.NotFound, $"角色模板 {request.TemplateCode} 不存在");
|
?? throw new BusinessException(ErrorCodes.NotFound, $"角色模板 {request.TemplateCode} 不存在");
|
||||||
|
|
||||||
@@ -36,6 +37,7 @@ public sealed class CopyRoleTemplateCommandHandler(
|
|||||||
.Distinct(StringComparer.OrdinalIgnoreCase)
|
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||||
.ToArray();
|
.ToArray();
|
||||||
|
|
||||||
|
// 2. 计算角色名称/编码与描述
|
||||||
var tenantId = tenantProvider.GetCurrentTenantId();
|
var tenantId = tenantProvider.GetCurrentTenantId();
|
||||||
var roleCode = string.IsNullOrWhiteSpace(request.RoleCode) ? template.TemplateCode : request.RoleCode.Trim();
|
var roleCode = string.IsNullOrWhiteSpace(request.RoleCode) ? template.TemplateCode : request.RoleCode.Trim();
|
||||||
var roleName = string.IsNullOrWhiteSpace(request.RoleName) ? template.Name : request.RoleName.Trim();
|
var roleName = string.IsNullOrWhiteSpace(request.RoleName) ? template.Name : request.RoleName.Trim();
|
||||||
@@ -69,7 +71,7 @@ public sealed class CopyRoleTemplateCommandHandler(
|
|||||||
await roleRepository.UpdateAsync(role, cancellationToken);
|
await roleRepository.UpdateAsync(role, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. 确保模板权限全部存在,不存在则按模板定义创建。
|
// 3. 确保模板权限全部存在,不存在则按模板定义创建。
|
||||||
var existingPermissions = await permissionRepository.GetByCodesAsync(tenantId, permissionCodes, cancellationToken);
|
var existingPermissions = await permissionRepository.GetByCodesAsync(tenantId, permissionCodes, cancellationToken);
|
||||||
var permissionMap = existingPermissions.ToDictionary(x => x.Code, StringComparer.OrdinalIgnoreCase);
|
var permissionMap = existingPermissions.ToDictionary(x => x.Code, StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
@@ -94,7 +96,7 @@ public sealed class CopyRoleTemplateCommandHandler(
|
|||||||
|
|
||||||
await roleRepository.SaveChangesAsync(cancellationToken);
|
await roleRepository.SaveChangesAsync(cancellationToken);
|
||||||
|
|
||||||
// 3. 绑定缺失的权限,保留租户自定义的已有授权。
|
// 4. 绑定缺失的权限,保留租户自定义的已有授权。
|
||||||
var rolePermissions = await rolePermissionRepository.GetByRoleIdsAsync(tenantId, new[] { role.Id }, cancellationToken);
|
var rolePermissions = await rolePermissionRepository.GetByRoleIdsAsync(tenantId, new[] { role.Id }, cancellationToken);
|
||||||
var existingPermissionIds = rolePermissions
|
var existingPermissionIds = rolePermissions
|
||||||
.Select(x => x.PermissionId)
|
.Select(x => x.PermissionId)
|
||||||
|
|||||||
@@ -17,7 +17,10 @@ public sealed class CreatePermissionCommandHandler(
|
|||||||
{
|
{
|
||||||
public async Task<PermissionDto> Handle(CreatePermissionCommand request, CancellationToken cancellationToken)
|
public async Task<PermissionDto> Handle(CreatePermissionCommand request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 获取租户上下文
|
||||||
var tenantId = tenantProvider.GetCurrentTenantId();
|
var tenantId = tenantProvider.GetCurrentTenantId();
|
||||||
|
|
||||||
|
// 2. 构建权限实体
|
||||||
var permission = new Permission
|
var permission = new Permission
|
||||||
{
|
{
|
||||||
TenantId = tenantId,
|
TenantId = tenantId,
|
||||||
@@ -26,9 +29,11 @@ public sealed class CreatePermissionCommandHandler(
|
|||||||
Description = request.Description
|
Description = request.Description
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 3. 持久化
|
||||||
await permissionRepository.AddAsync(permission, cancellationToken);
|
await permissionRepository.AddAsync(permission, cancellationToken);
|
||||||
await permissionRepository.SaveChangesAsync(cancellationToken);
|
await permissionRepository.SaveChangesAsync(cancellationToken);
|
||||||
|
|
||||||
|
// 4. 返回 DTO
|
||||||
return new PermissionDto
|
return new PermissionDto
|
||||||
{
|
{
|
||||||
Id = permission.Id,
|
Id = permission.Id,
|
||||||
|
|||||||
@@ -17,7 +17,10 @@ public sealed class CreateRoleCommandHandler(
|
|||||||
{
|
{
|
||||||
public async Task<RoleDto> Handle(CreateRoleCommand request, CancellationToken cancellationToken)
|
public async Task<RoleDto> Handle(CreateRoleCommand request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 获取租户上下文
|
||||||
var tenantId = tenantProvider.GetCurrentTenantId();
|
var tenantId = tenantProvider.GetCurrentTenantId();
|
||||||
|
|
||||||
|
// 2. 构建角色实体
|
||||||
var role = new Role
|
var role = new Role
|
||||||
{
|
{
|
||||||
TenantId = tenantId,
|
TenantId = tenantId,
|
||||||
@@ -26,9 +29,11 @@ public sealed class CreateRoleCommandHandler(
|
|||||||
Description = request.Description
|
Description = request.Description
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 3. 持久化
|
||||||
await roleRepository.AddAsync(role, cancellationToken);
|
await roleRepository.AddAsync(role, cancellationToken);
|
||||||
await roleRepository.SaveChangesAsync(cancellationToken);
|
await roleRepository.SaveChangesAsync(cancellationToken);
|
||||||
|
|
||||||
|
// 4. 返回 DTO
|
||||||
return new RoleDto
|
return new RoleDto
|
||||||
{
|
{
|
||||||
Id = role.Id,
|
Id = role.Id,
|
||||||
|
|||||||
@@ -19,17 +19,20 @@ public sealed class CreateRoleTemplateCommandHandler(IRoleTemplateRepository rol
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task<RoleTemplateDto> Handle(CreateRoleTemplateCommand request, CancellationToken cancellationToken)
|
public async Task<RoleTemplateDto> Handle(CreateRoleTemplateCommand request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 校验必填
|
||||||
if (string.IsNullOrWhiteSpace(request.TemplateCode) || string.IsNullOrWhiteSpace(request.Name))
|
if (string.IsNullOrWhiteSpace(request.TemplateCode) || string.IsNullOrWhiteSpace(request.Name))
|
||||||
{
|
{
|
||||||
throw new BusinessException(ErrorCodes.BadRequest, "模板编码与名称不能为空");
|
throw new BusinessException(ErrorCodes.BadRequest, "模板编码与名称不能为空");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 2. 检查编码唯一
|
||||||
var existing = await roleTemplateRepository.FindByCodeAsync(request.TemplateCode, cancellationToken);
|
var existing = await roleTemplateRepository.FindByCodeAsync(request.TemplateCode, cancellationToken);
|
||||||
if (existing != null)
|
if (existing != null)
|
||||||
{
|
{
|
||||||
throw new BusinessException(ErrorCodes.Conflict, $"模板编码 {request.TemplateCode} 已存在");
|
throw new BusinessException(ErrorCodes.Conflict, $"模板编码 {request.TemplateCode} 已存在");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 3. 构建模板实体
|
||||||
var template = new RoleTemplate
|
var template = new RoleTemplate
|
||||||
{
|
{
|
||||||
TemplateCode = request.TemplateCode.Trim(),
|
TemplateCode = request.TemplateCode.Trim(),
|
||||||
@@ -38,12 +41,14 @@ public sealed class CreateRoleTemplateCommandHandler(IRoleTemplateRepository rol
|
|||||||
IsActive = request.IsActive
|
IsActive = request.IsActive
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 4. 清洗权限编码
|
||||||
var permissions = request.PermissionCodes
|
var permissions = request.PermissionCodes
|
||||||
.Where(code => !string.IsNullOrWhiteSpace(code))
|
.Where(code => !string.IsNullOrWhiteSpace(code))
|
||||||
.Select(code => code.Trim())
|
.Select(code => code.Trim())
|
||||||
.Distinct(StringComparer.OrdinalIgnoreCase)
|
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||||
.ToArray();
|
.ToArray();
|
||||||
|
|
||||||
|
// 5. 持久化并返回 DTO
|
||||||
await roleTemplateRepository.AddAsync(template, permissions, cancellationToken);
|
await roleTemplateRepository.AddAsync(template, permissions, cancellationToken);
|
||||||
await roleTemplateRepository.SaveChangesAsync(cancellationToken);
|
await roleTemplateRepository.SaveChangesAsync(cancellationToken);
|
||||||
|
|
||||||
|
|||||||
@@ -15,9 +15,14 @@ public sealed class DeletePermissionCommandHandler(
|
|||||||
{
|
{
|
||||||
public async Task<bool> Handle(DeletePermissionCommand request, CancellationToken cancellationToken)
|
public async Task<bool> Handle(DeletePermissionCommand request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 获取租户上下文
|
||||||
var tenantId = tenantProvider.GetCurrentTenantId();
|
var tenantId = tenantProvider.GetCurrentTenantId();
|
||||||
|
|
||||||
|
// 2. 删除权限
|
||||||
await permissionRepository.DeleteAsync(request.PermissionId, tenantId, cancellationToken);
|
await permissionRepository.DeleteAsync(request.PermissionId, tenantId, cancellationToken);
|
||||||
await permissionRepository.SaveChangesAsync(cancellationToken);
|
await permissionRepository.SaveChangesAsync(cancellationToken);
|
||||||
|
|
||||||
|
// 3. 返回执行结果
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,9 +15,14 @@ public sealed class DeleteRoleCommandHandler(
|
|||||||
{
|
{
|
||||||
public async Task<bool> Handle(DeleteRoleCommand request, CancellationToken cancellationToken)
|
public async Task<bool> Handle(DeleteRoleCommand request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 获取租户上下文
|
||||||
var tenantId = tenantProvider.GetCurrentTenantId();
|
var tenantId = tenantProvider.GetCurrentTenantId();
|
||||||
|
|
||||||
|
// 2. 删除角色
|
||||||
await roleRepository.DeleteAsync(request.RoleId, tenantId, cancellationToken);
|
await roleRepository.DeleteAsync(request.RoleId, tenantId, cancellationToken);
|
||||||
await roleRepository.SaveChangesAsync(cancellationToken);
|
await roleRepository.SaveChangesAsync(cancellationToken);
|
||||||
|
|
||||||
|
// 3. 返回执行结果
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,14 +12,18 @@ public sealed class DeleteRoleTemplateCommandHandler(IRoleTemplateRepository rol
|
|||||||
{
|
{
|
||||||
public async Task<bool> Handle(DeleteRoleTemplateCommand request, CancellationToken cancellationToken)
|
public async Task<bool> Handle(DeleteRoleTemplateCommand request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 查询模板
|
||||||
var template = await roleTemplateRepository.FindByCodeAsync(request.TemplateCode, cancellationToken);
|
var template = await roleTemplateRepository.FindByCodeAsync(request.TemplateCode, cancellationToken);
|
||||||
if (template == null)
|
if (template == null)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 2. 删除并保存
|
||||||
await roleTemplateRepository.DeleteAsync(template.Id, cancellationToken);
|
await roleTemplateRepository.DeleteAsync(template.Id, cancellationToken);
|
||||||
await roleTemplateRepository.SaveChangesAsync(cancellationToken);
|
await roleTemplateRepository.SaveChangesAsync(cancellationToken);
|
||||||
|
|
||||||
|
// 3. 返回执行结果
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,14 +15,18 @@ public sealed class GetRoleTemplateQueryHandler(IRoleTemplateRepository roleTemp
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task<RoleTemplateDto?> Handle(GetRoleTemplateQuery request, CancellationToken cancellationToken)
|
public async Task<RoleTemplateDto?> Handle(GetRoleTemplateQuery request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 查询模板
|
||||||
var template = await roleTemplateRepository.FindByCodeAsync(request.TemplateCode, cancellationToken);
|
var template = await roleTemplateRepository.FindByCodeAsync(request.TemplateCode, cancellationToken);
|
||||||
if (template == null)
|
if (template == null)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 2. 查询模板权限
|
||||||
var permissions = await roleTemplateRepository.GetPermissionsAsync(template.Id, cancellationToken);
|
var permissions = await roleTemplateRepository.GetPermissionsAsync(template.Id, cancellationToken);
|
||||||
var codes = permissions.Select(x => x.PermissionCode).ToArray();
|
var codes = permissions.Select(x => x.PermissionCode).ToArray();
|
||||||
|
|
||||||
|
// 3. 返回 DTO
|
||||||
return TemplateMapper.ToDto(template, codes);
|
return TemplateMapper.ToDto(template, codes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,26 +20,22 @@ public sealed class GetUserPermissionsQueryHandler(
|
|||||||
ITenantProvider tenantProvider)
|
ITenantProvider tenantProvider)
|
||||||
: IRequestHandler<GetUserPermissionsQuery, UserPermissionDto?>
|
: IRequestHandler<GetUserPermissionsQuery, UserPermissionDto?>
|
||||||
{
|
{
|
||||||
private readonly IIdentityUserRepository _identityUserRepository = identityUserRepository;
|
|
||||||
private readonly IUserRoleRepository _userRoleRepository = userRoleRepository;
|
|
||||||
private readonly IRoleRepository _roleRepository = roleRepository;
|
|
||||||
private readonly IPermissionRepository _permissionRepository = permissionRepository;
|
|
||||||
private readonly IRolePermissionRepository _rolePermissionRepository = rolePermissionRepository;
|
|
||||||
private readonly ITenantProvider _tenantProvider = tenantProvider;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task<UserPermissionDto?> Handle(GetUserPermissionsQuery request, CancellationToken cancellationToken)
|
public async Task<UserPermissionDto?> Handle(GetUserPermissionsQuery request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var tenantId = _tenantProvider.GetCurrentTenantId();
|
// 1. 获取租户并查询用户
|
||||||
var user = await _identityUserRepository.FindByIdAsync(request.UserId, cancellationToken);
|
var tenantId = tenantProvider.GetCurrentTenantId();
|
||||||
|
var user = await identityUserRepository.FindByIdAsync(request.UserId, cancellationToken);
|
||||||
if (user == null || user.TenantId != tenantId)
|
if (user == null || user.TenantId != tenantId)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 2. 解析角色与权限
|
||||||
var roleCodes = await ResolveUserRolesAsync(tenantId, user.Id, cancellationToken);
|
var roleCodes = await ResolveUserRolesAsync(tenantId, user.Id, cancellationToken);
|
||||||
var permissionCodes = await ResolveUserPermissionsAsync(tenantId, user.Id, cancellationToken);
|
var permissionCodes = await ResolveUserPermissionsAsync(tenantId, user.Id, cancellationToken);
|
||||||
|
|
||||||
|
// 3. 返回用户权限概览
|
||||||
return new UserPermissionDto
|
return new UserPermissionDto
|
||||||
{
|
{
|
||||||
UserId = user.Id,
|
UserId = user.Id,
|
||||||
@@ -55,34 +51,39 @@ public sealed class GetUserPermissionsQueryHandler(
|
|||||||
|
|
||||||
private async Task<string[]> ResolveUserRolesAsync(long tenantId, long userId, CancellationToken cancellationToken)
|
private async Task<string[]> ResolveUserRolesAsync(long tenantId, long userId, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var relations = await _userRoleRepository.GetByUserIdAsync(tenantId, userId, cancellationToken);
|
// 1. 查询用户角色关系
|
||||||
|
var relations = await userRoleRepository.GetByUserIdAsync(tenantId, userId, cancellationToken);
|
||||||
var roleIds = relations.Select(x => x.RoleId).Distinct().ToArray();
|
var roleIds = relations.Select(x => x.RoleId).Distinct().ToArray();
|
||||||
if (roleIds.Length == 0)
|
if (roleIds.Length == 0)
|
||||||
{
|
{
|
||||||
return Array.Empty<string>();
|
return Array.Empty<string>();
|
||||||
}
|
}
|
||||||
|
|
||||||
var roles = await _roleRepository.GetByIdsAsync(tenantId, roleIds, cancellationToken);
|
// 2. 查询角色编码
|
||||||
|
var roles = await roleRepository.GetByIdsAsync(tenantId, roleIds, cancellationToken);
|
||||||
return roles.Select(x => x.Code).Distinct(StringComparer.OrdinalIgnoreCase).ToArray();
|
return roles.Select(x => x.Code).Distinct(StringComparer.OrdinalIgnoreCase).ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<string[]> ResolveUserPermissionsAsync(long tenantId, long userId, CancellationToken cancellationToken)
|
private async Task<string[]> ResolveUserPermissionsAsync(long tenantId, long userId, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var relations = await _userRoleRepository.GetByUserIdAsync(tenantId, userId, cancellationToken);
|
// 1. 查询用户角色关系
|
||||||
|
var relations = await userRoleRepository.GetByUserIdAsync(tenantId, userId, cancellationToken);
|
||||||
var roleIds = relations.Select(x => x.RoleId).Distinct().ToArray();
|
var roleIds = relations.Select(x => x.RoleId).Distinct().ToArray();
|
||||||
if (roleIds.Length == 0)
|
if (roleIds.Length == 0)
|
||||||
{
|
{
|
||||||
return Array.Empty<string>();
|
return Array.Empty<string>();
|
||||||
}
|
}
|
||||||
|
|
||||||
var rolePermissions = await _rolePermissionRepository.GetByRoleIdsAsync(tenantId, roleIds, cancellationToken);
|
// 2. 查询角色-权限关系
|
||||||
|
var rolePermissions = await rolePermissionRepository.GetByRoleIdsAsync(tenantId, roleIds, cancellationToken);
|
||||||
var permissionIds = rolePermissions.Select(x => x.PermissionId).Distinct().ToArray();
|
var permissionIds = rolePermissions.Select(x => x.PermissionId).Distinct().ToArray();
|
||||||
if (permissionIds.Length == 0)
|
if (permissionIds.Length == 0)
|
||||||
{
|
{
|
||||||
return Array.Empty<string>();
|
return Array.Empty<string>();
|
||||||
}
|
}
|
||||||
|
|
||||||
var permissions = await _permissionRepository.GetByIdsAsync(tenantId, permissionIds, cancellationToken);
|
// 3. 查询权限编码
|
||||||
|
var permissions = await permissionRepository.GetByIdsAsync(tenantId, permissionIds, cancellationToken);
|
||||||
return permissions.Select(x => x.Code).Distinct(StringComparer.OrdinalIgnoreCase).ToArray();
|
return permissions.Select(x => x.Code).Distinct(StringComparer.OrdinalIgnoreCase).ToArray();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,9 +16,11 @@ public sealed class ListRoleTemplatesQueryHandler(IRoleTemplateRepository roleTe
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task<IReadOnlyList<RoleTemplateDto>> Handle(ListRoleTemplatesQuery request, CancellationToken cancellationToken)
|
public async Task<IReadOnlyList<RoleTemplateDto>> Handle(ListRoleTemplatesQuery request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 查询模板与权限映射
|
||||||
var templates = await roleTemplateRepository.GetAllAsync(request.IsActive, cancellationToken);
|
var templates = await roleTemplateRepository.GetAllAsync(request.IsActive, cancellationToken);
|
||||||
var permissionsMap = await roleTemplateRepository.GetPermissionsAsync(templates.Select(t => t.Id), cancellationToken);
|
var permissionsMap = await roleTemplateRepository.GetPermissionsAsync(templates.Select(t => t.Id), cancellationToken);
|
||||||
|
|
||||||
|
// 2. 排序并映射 DTO
|
||||||
var dtos = templates
|
var dtos = templates
|
||||||
.OrderBy(template => template.TemplateCode, StringComparer.OrdinalIgnoreCase)
|
.OrderBy(template => template.TemplateCode, StringComparer.OrdinalIgnoreCase)
|
||||||
.Select(template =>
|
.Select(template =>
|
||||||
@@ -30,6 +32,7 @@ public sealed class ListRoleTemplatesQueryHandler(IRoleTemplateRepository roleTe
|
|||||||
})
|
})
|
||||||
.ToArray();
|
.ToArray();
|
||||||
|
|
||||||
|
// 3. 返回结果
|
||||||
return dtos;
|
return dtos;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,9 +19,11 @@ public sealed class SearchPermissionsQueryHandler(
|
|||||||
{
|
{
|
||||||
public async Task<PagedResult<PermissionDto>> Handle(SearchPermissionsQuery request, CancellationToken cancellationToken)
|
public async Task<PagedResult<PermissionDto>> Handle(SearchPermissionsQuery request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 获取租户上下文并查询权限
|
||||||
var tenantId = tenantProvider.GetCurrentTenantId();
|
var tenantId = tenantProvider.GetCurrentTenantId();
|
||||||
var permissions = await permissionRepository.SearchAsync(tenantId, request.Keyword, cancellationToken);
|
var permissions = await permissionRepository.SearchAsync(tenantId, request.Keyword, cancellationToken);
|
||||||
|
|
||||||
|
// 2. 排序
|
||||||
var sorted = request.SortBy?.ToLowerInvariant() switch
|
var sorted = request.SortBy?.ToLowerInvariant() switch
|
||||||
{
|
{
|
||||||
"name" => request.SortDescending
|
"name" => request.SortDescending
|
||||||
@@ -35,11 +37,13 @@ public sealed class SearchPermissionsQueryHandler(
|
|||||||
: permissions.OrderBy(x => x.CreatedAt)
|
: permissions.OrderBy(x => x.CreatedAt)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 3. 分页
|
||||||
var paged = sorted
|
var paged = sorted
|
||||||
.Skip((request.Page - 1) * request.PageSize)
|
.Skip((request.Page - 1) * request.PageSize)
|
||||||
.Take(request.PageSize)
|
.Take(request.PageSize)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
|
// 4. 映射 DTO
|
||||||
var items = paged.Select(permission => new PermissionDto
|
var items = paged.Select(permission => new PermissionDto
|
||||||
{
|
{
|
||||||
Id = permission.Id,
|
Id = permission.Id,
|
||||||
@@ -49,6 +53,7 @@ public sealed class SearchPermissionsQueryHandler(
|
|||||||
Description = permission.Description
|
Description = permission.Description
|
||||||
}).ToList();
|
}).ToList();
|
||||||
|
|
||||||
|
// 5. 返回分页结果
|
||||||
return new PagedResult<PermissionDto>(items, request.Page, request.PageSize, permissions.Count);
|
return new PagedResult<PermissionDto>(items, request.Page, request.PageSize, permissions.Count);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,9 +19,11 @@ public sealed class SearchRolesQueryHandler(
|
|||||||
{
|
{
|
||||||
public async Task<PagedResult<RoleDto>> Handle(SearchRolesQuery request, CancellationToken cancellationToken)
|
public async Task<PagedResult<RoleDto>> Handle(SearchRolesQuery request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// 1. 获取租户上下文并查询角色
|
||||||
var tenantId = tenantProvider.GetCurrentTenantId();
|
var tenantId = tenantProvider.GetCurrentTenantId();
|
||||||
var roles = await roleRepository.SearchAsync(tenantId, request.Keyword, cancellationToken);
|
var roles = await roleRepository.SearchAsync(tenantId, request.Keyword, cancellationToken);
|
||||||
|
|
||||||
|
// 2. 排序
|
||||||
var sorted = request.SortBy?.ToLowerInvariant() switch
|
var sorted = request.SortBy?.ToLowerInvariant() switch
|
||||||
{
|
{
|
||||||
"name" => request.SortDescending
|
"name" => request.SortDescending
|
||||||
@@ -32,11 +34,13 @@ public sealed class SearchRolesQueryHandler(
|
|||||||
: roles.OrderBy(x => x.CreatedAt)
|
: roles.OrderBy(x => x.CreatedAt)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 3. 分页
|
||||||
var paged = sorted
|
var paged = sorted
|
||||||
.Skip((request.Page - 1) * request.PageSize)
|
.Skip((request.Page - 1) * request.PageSize)
|
||||||
.Take(request.PageSize)
|
.Take(request.PageSize)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
|
// 4. 映射 DTO
|
||||||
var items = paged.Select(role => new RoleDto
|
var items = paged.Select(role => new RoleDto
|
||||||
{
|
{
|
||||||
Id = role.Id,
|
Id = role.Id,
|
||||||
@@ -46,6 +50,7 @@ public sealed class SearchRolesQueryHandler(
|
|||||||
Description = role.Description
|
Description = role.Description
|
||||||
}).ToList();
|
}).ToList();
|
||||||
|
|
||||||
|
// 5. 返回分页结果
|
||||||
return new PagedResult<RoleDto>(items, request.Page, request.PageSize, roles.Count);
|
return new PagedResult<RoleDto>(items, request.Page, request.PageSize, roles.Count);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user