订单的取消和发货命令

This commit is contained in:
hello 2024-09-09 22:53:22 +08:00
parent 3cad5598b7
commit 923089b83b
8 changed files with 193 additions and 41 deletions

View File

@ -0,0 +1,9 @@
// Copyright (c) HelloShop Corporation. All rights reserved.
// See the license file in the project root for more information.
using MediatR;
namespace HelloShop.OrderingService.Commands.Orders
{
public record CancelOrderCommand(int OrderNumber) : IRequest<bool>;
}

View File

@ -0,0 +1,32 @@
// Copyright (c) HelloShop Corporation. All rights reserved.
// See the license file in the project root for more information.
using HelloShop.OrderingService.Entities.Orders;
using HelloShop.OrderingService.Infrastructure;
using HelloShop.OrderingService.Services;
using MediatR;
namespace HelloShop.OrderingService.Commands.Orders
{
public class CancelOrderCommandHandler(OrderingServiceDbContext dbContext) : IRequestHandler<CancelOrderCommand, bool>
{
public async Task<bool> Handle(CancelOrderCommand request, CancellationToken cancellationToken)
{
var orderToUpdate = await dbContext.Set<Order>().FindAsync([request.OrderNumber], cancellationToken);
if (orderToUpdate == null)
{
return false;
}
orderToUpdate.OrderStatus = OrderStatus.Cancelled;
return await dbContext.SaveChangesAsync(cancellationToken) > 0;
}
}
public class CancelOrderIdentifiedCommandHandler(IMediator mediator, IClientRequestManager requestManager) : IdentifiedCommandHandler<CancelOrderCommand, bool>(mediator, requestManager)
{
protected override bool CreateResultForDuplicateRequest() => true;
}
}

View File

@ -0,0 +1,9 @@
// Copyright (c) HelloShop Corporation. All rights reserved.
// See the license file in the project root for more information.
using MediatR;
namespace HelloShop.OrderingService.Commands.Orders
{
public record ShipOrderCommand(int OrderNumber) : IRequest<bool>;
}

View File

@ -0,0 +1,32 @@
// Copyright (c) HelloShop Corporation. All rights reserved.
// See the license file in the project root for more information.
using HelloShop.OrderingService.Entities.Orders;
using HelloShop.OrderingService.Infrastructure;
using HelloShop.OrderingService.Services;
using MediatR;
namespace HelloShop.OrderingService.Commands.Orders
{
public class ShipOrderCommandHandler(OrderingServiceDbContext dbContext) : IRequestHandler<ShipOrderCommand, bool>
{
public async Task<bool> Handle(ShipOrderCommand request, CancellationToken cancellationToken)
{
var orderToUpdate = await dbContext.Set<Order>().FindAsync([request.OrderNumber], cancellationToken: cancellationToken);
if (orderToUpdate == null)
{
return false;
}
orderToUpdate.OrderStatus = OrderStatus.Shipped;
return await dbContext.SaveChangesAsync(cancellationToken) > 0;
}
}
public class ShipOrderIdentifiedCommandHandler(IMediator mediator, IClientRequestManager requestManager) : IdentifiedCommandHandler<ShipOrderCommand, bool>(mediator, requestManager)
{
protected override bool CreateResultForDuplicateRequest() => true;
}
}

View File

@ -7,59 +7,95 @@ using HelloShop.OrderingService.Commands.Orders;
using HelloShop.OrderingService.Models.Orders; using HelloShop.OrderingService.Models.Orders;
using MediatR; using MediatR;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Components.Forms;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.ModelBinding;
using System.ComponentModel.DataAnnotations;
using System.Security.Claims; using System.Security.Claims;
namespace HelloShop.OrderingService.Controllers namespace HelloShop.OrderingService.Controllers;
[Route("api/[controller]")]
[ApiController]
[Authorize]
public class OrdersController(ILogger<OrdersController> logger, IMediator mediator, IMapper mapper) : ControllerBase
{ {
[Route("api/[controller]")] [HttpPost]
[ApiController] public async Task<IActionResult> CreateOrder([FromHeader(Name = "x-request-id")] Guid requestId, CreateOrderRequest request)
[Authorize]
public class OrdersController(ILogger<OrdersController> logger, IMediator mediator, IMapper mapper) : ControllerBase
{ {
[HttpPost] using (logger.BeginScope(new List<KeyValuePair<string, object>> { new("IdentifiedCommandId", requestId) }))
public async Task<IActionResult> CreateOrder([FromHeader(Name = "x-request-id")] Guid requestId, CreateOrderRequest request)
{ {
using (logger.BeginScope(new List<KeyValuePair<string, object>> { new("IdentifiedCommandId", requestId) })) string? nameIdentifier = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
string? userName = User.FindFirst(ClaimTypes.Name)?.Value;
if (!int.TryParse(nameIdentifier, out int userId) || string.IsNullOrWhiteSpace(userName))
{ {
string? nameIdentifier = User.FindFirst(ClaimTypes.NameIdentifier)?.Value; throw new InvalidOperationException("User id or name not found in claims.");
string? userName = User.FindFirst(ClaimTypes.Name)?.Value; }
if (!int.TryParse(nameIdentifier, out int userId) || string.IsNullOrWhiteSpace(userName)) CreateOrderCommand createOrderCommand = mapper.Map<CreateOrderCommand>(request, opts => opts.AfterMap((src, dest) =>
{
dest.UserId = userId;
dest.UserName = userName;
}));
var createOrderIdentifiedCommand = new IdentifiedCommand<CreateOrderCommand, bool>(createOrderCommand, requestId);
logger.LogInformation("Sending create order command.");
try
{
var result = await mediator.Send(createOrderIdentifiedCommand);
return result ? Ok() : Problem(detail: "Create order failed to process.", statusCode: 500);
}
catch (ApplicationException ex) when (ex.InnerException is FluentValidation.ValidationException validationException)
{
logger.LogWarning("Validation error in create order command.");
ModelStateDictionary modelState = validationException.Errors.Aggregate(ModelState, (acc, error) =>
{ {
throw new InvalidOperationException("User id or name not found in claims."); acc.AddModelError(error.PropertyName, error.ErrorMessage);
} return acc;
});
CreateOrderCommand createOrderCommand = mapper.Map<CreateOrderCommand>(request, opts => opts.AfterMap((src, dest) => return ValidationProblem(modelState);
{
dest.UserId = userId;
dest.UserName = userName;
}));
var createOrderIdentifiedCommand = new IdentifiedCommand<CreateOrderCommand, bool>(createOrderCommand, requestId);
logger.LogInformation("Sending create order command.");
try
{
var result = await mediator.Send(createOrderIdentifiedCommand);
return result ? Ok() : Problem(detail: "Create order failed to process.", statusCode: 500);
}
catch (ApplicationException ex) when (ex.InnerException is FluentValidation.ValidationException validationException)
{
logger.LogWarning("Validation error in create order command.");
ModelStateDictionary modelState = validationException.Errors.Aggregate(ModelState, (acc, error) =>
{
acc.AddModelError(error.PropertyName, error.ErrorMessage);
return acc;
});
return ValidationProblem(modelState);
}
} }
} }
} }
[HttpPut("Cancel/{id}")]
public async Task<IActionResult> CancelOrder([FromHeader(Name = "x-request-id")] Guid requestId, int id)
{
CancelOrderCommand cancelOrderCommand = new(id);
var requestCancelOrder = new IdentifiedCommand<CancelOrderCommand, bool>(cancelOrderCommand, requestId);
var commandResult = await mediator.Send(requestCancelOrder);
if (!commandResult)
{
return Problem(detail: "Cancel order failed to process.", statusCode: 500);
}
return Ok();
}
[HttpPut("Ship/{id}")]
public async Task<IActionResult> ShipOrder([FromHeader(Name = "x-request-id")] Guid requestId, int id)
{
ShipOrderCommand shipOrderCommand = new(id);
var requestShipOrder = new IdentifiedCommand<ShipOrderCommand, bool>(shipOrderCommand, requestId);
var commandResult = await mediator.Send(requestShipOrder);
if (!commandResult)
{
return Problem(detail: "Ship order failed to process.", statusCode: 500);
}
return Ok();
}
} }

View File

@ -30,6 +30,8 @@ namespace HelloShop.OrderingService.Extensions
options.AddBehavior(typeof(LoggingBehavior<,>)); options.AddBehavior(typeof(LoggingBehavior<,>));
options.AddBehavior(typeof(ValidatorBehavior<,>)); options.AddBehavior(typeof(ValidatorBehavior<,>));
}); });
builder.Services.AddModelMapper().AddModelValidator();
} }
public static WebApplication MapApplicationEndpoints(this WebApplication app) public static WebApplication MapApplicationEndpoints(this WebApplication app)

View File

@ -0,0 +1,16 @@
// Copyright (c) HelloShop Corporation. All rights reserved.
// See the license file in the project root for more information.
using FluentValidation;
using HelloShop.OrderingService.Commands.Orders;
namespace HelloShop.OrderingService.Validations.Commands
{
public class CancelOrderCommandValidator : AbstractValidator<CancelOrderCommand>
{
public CancelOrderCommandValidator()
{
RuleFor(x => x.OrderNumber).GreaterThan(0);
}
}
}

View File

@ -0,0 +1,16 @@
// Copyright (c) HelloShop Corporation. All rights reserved.
// See the license file in the project root for more information.
using FluentValidation;
using HelloShop.OrderingService.Commands.Orders;
namespace HelloShop.OrderingService.Validations.Commands
{
public class ShipOrderCommandValidator : AbstractValidator<ShipOrderCommand>
{
public ShipOrderCommandValidator()
{
RuleFor(order => order.OrderNumber).GreaterThan(0);
}
}
}