异常处理
MiCake 提供了统一的异常处理机制,包括领域异常、业务异常等,并能自动将异常转换为统一的 API 响应格式。
MiCake 异常体系
Section titled “MiCake 异常体系”MiCakeException - 基础异常
Section titled “MiCakeException - 基础异常”所有 MiCake 异常的基类:
using MiCake.Core;
public class MiCakeException : Exception{ // 异常代码 public virtual string? Code { get; set; }
// 异常详情 public virtual object? Details { get; set; }
public MiCakeException(string message, string? details = null, string? code = null) : base(message) { Code = code; Details = details; }
public MiCakeException( string message, Exception innerException, string? details = null, string? code = null) : base(message, innerException) { Code = code; Details = details; }}DomainException - 领域异常
Section titled “DomainException - 领域异常”用于表示违反领域规则的异常:
using MiCake.DDD.Domain;
public class DomainException : MiCakeException{ public DomainException(string message) : base(message, code: "DOMAIN_ERROR") { }
public DomainException(string message, string code) : base(message, code: code) { }}public class Order : AggregateRoot<int>{ public OrderStatus Status { get; private set; } public decimal TotalAmount { get; private set; }
public void Confirm() { if (Status != OrderStatus.Draft) throw new DomainException("只能确认草稿状态的订单");
if (TotalAmount <= 0) throw new DomainException("订单金额必须大于零");
if (!Items.Any()) throw new DomainException("订单至少需要一个商品项");
Status = OrderStatus.Confirmed; RaiseDomainEvent(new OrderConfirmedEvent(Id)); }
public void Cancel(string reason) { if (Status == OrderStatus.Shipped || Status == OrderStatus.Completed) throw new DomainException( "已发货或已完成的订单不能取消", code: "ORDER_CANCEL_NOT_ALLOWED" );
Status = OrderStatus.Cancelled; RaiseDomainEvent(new OrderCancelledEvent(Id, reason)); }}// 业务异常public class BusinessException : MiCakeException{ public BusinessException(string message, string? code = null) : base(message, code: code ?? "BUSINESS_ERROR") { }}全局异常处理
Section titled “全局异常处理”MiCake 提供了全局异常处理机制,自动将异常转换为统一的 API 响应。
异常响应格式
Section titled “异常响应格式”当发生异常时,API 会返回以下格式:
{ "code": "DOMAIN_ERROR", "message": "只能确认草稿状态的订单", "errors": null}异常最佳实践
Section titled “异常最佳实践”1. 使用特定的异常类型
Section titled “1. 使用特定的异常类型”// ✅ 正确:使用特定的异常类型public async Task<Order> GetOrder(int orderId){ var order = await _orderRepository.FindAsync(orderId); if (order == null) throw new NotFoundException("Order", orderId);
return order;}
// ❌ 错误:使用通用异常public async Task<Order> GetOrder(int orderId){ var order = await _orderRepository.FindAsync(orderId); if (order == null) throw new Exception("Order not found");
return order;}2. 提供清晰的错误消息
Section titled “2. 提供清晰的错误消息”// ✅ 正确:清晰的错误消息throw new DomainException("只能确认草稿状态的订单");throw new ValidationException("邮箱格式不正确");throw new BusinessException("库存不足,无法完成订单");
// ❌ 错误:模糊的错误消息throw new Exception("Error");throw new Exception("Invalid");throw new Exception("Failed");3. 使用错误代码
Section titled “3. 使用错误代码”// ✅ 正确:使用错误代码throw new DomainException("订单不能取消", code: "ORDER_CANCEL_NOT_ALLOWED");throw new BusinessException("库存不足", code: "INSUFFICIENT_STOCK");
// 客户端可以根据错误代码进行国际化或特殊处理4. 包含有用的上下文信息
Section titled “4. 包含有用的上下文信息”// ✅ 正确:包含上下文信息throw new DomainException( $"产品 '{product.Name}' 的库存不足。当前库存:{product.Stock},需求数量:{quantity}", code: "INSUFFICIENT_STOCK");
// ❌ 错误:缺少上下文throw new DomainException("库存不足");5. 不要吞噬异常
Section titled “5. 不要吞噬异常”// ❌ 错误:吞噬异常try{ await ProcessOrder(order);}catch (Exception){ // 什么都不做}
// ✅ 正确:记录并重新抛出try{ await ProcessOrder(order);}catch (Exception ex){ _logger.LogError(ex, "处理订单失败: OrderId={OrderId}", order.Id); throw;}
// ✅ 或者转换为更具体的异常try{ await ProcessOrder(order);}catch (DbUpdateException ex){ _logger.LogError(ex, "保存订单失败: OrderId={OrderId}", order.Id); throw new BusinessException("保存订单失败,请稍后重试");}在控制器中处理异常
Section titled “在控制器中处理异常”虽然 MiCake 提供了全局异常处理,但您仍然可以在控制器中进行特定的异常处理:
[ApiController][Route("api/[controller]")]public class OrderController : ControllerBase{ private readonly IOrderService _orderService;
[HttpPost] public async Task<IActionResult> CreateOrder([FromBody] CreateOrderDto dto) { try { var orderId = await _orderService.CreateOrder(dto); return Ok(new { orderId }); } catch (ValidationException ex) { // 特殊处理验证异常 return BadRequest(new { code = ex.Code, message = ex.Message, errors = ex.Errors }); } catch (BusinessException ex) { // 记录业务异常 _logger.LogWarning(ex, "创建订单业务异常"); throw; // 重新抛出,由全局处理 } // 其他异常由全局异常处理器处理 }
[HttpGet("{id}")] public async Task<IActionResult> GetOrder(int id) { try { var order = await _orderService.GetOrder(id); return Ok(order); } catch (NotFoundException) { return NotFound(new { message = $"订单 {id} 不存在" }); } }}- 使用特定异常:创建具体的异常类型而不是使用通用
Exception - 提供清晰消息:异常消息应该清晰地说明问题
- 使用错误代码:便于客户端进行特殊处理和国际化
- 不要过度捕获:只捕获您能处理的异常
- 记录异常:在重新抛出前记录异常信息