1598 lines
46 KiB
Markdown
1598 lines
46 KiB
Markdown
# 外卖SaaS系统 - AI编程规范汇总
|
||
|
||
> 本文档专门为AI编程助手准备,汇总了所有编码规范和规则,确保代码质量和一致性。
|
||
|
||
## 0. AI交互补充约束
|
||
1. 每次回复必须回复中文。
|
||
2. 不要更改我的文件编码。
|
||
3. 每次修复bug或者新增完小功能必须提交git。
|
||
4. 新创建的文件或者修改过的文件注释部分必须保持中文。
|
||
5. 项目中不要有乱码。
|
||
6. 在 PowerShell 查看文件时必须指定 UTF8(例如 Get-Content -Encoding UTF8 或设置 $OutputEncoding 为 UTF8),避免输出乱码。
|
||
## 1. 技术栈要求
|
||
|
||
### 1.1 核心技术
|
||
- **.NET 10** + **ASP.NET Core Web API**
|
||
- **Entity Framework Core 10**(复杂查询和实体管理)
|
||
- **Dapper 2.1+**(高性能查询和批量操作)
|
||
- **PostgreSQL 16+**(主数据库)
|
||
- **Redis 7.0+**(缓存和会话)
|
||
- **RabbitMQ 3.12+**(消息队列)
|
||
|
||
### 1.2 必用框架和库
|
||
- **AutoMapper**:对象映射
|
||
- **FluentValidation**:数据验证
|
||
- **Serilog**:结构化日志
|
||
- **MediatR**:CQRS和中介者模式
|
||
- **Hangfire**:后台任务调度
|
||
- **Polly**:弹性和瞬态故障处理
|
||
- **Swagger/Swashbuckle**:API文档
|
||
|
||
### 1.3 测试框架
|
||
- **xUnit**:单元测试
|
||
- **Moq**:Mock框架
|
||
- **FluentAssertions**:断言库
|
||
|
||
## 2. 命名规范(严格遵守)
|
||
|
||
### 2.1 C#命名规范
|
||
```csharp
|
||
// ✅ 类名:PascalCase
|
||
public class OrderService { }
|
||
|
||
// ✅ 接口:I + PascalCase
|
||
public interface IOrderRepository { }
|
||
|
||
// ✅ 方法:PascalCase,异步方法以Async结尾
|
||
public async Task<Order> CreateOrderAsync() { }
|
||
|
||
// ✅ 私有字段:_camelCase(下划线前缀)
|
||
private readonly IOrderRepository _orderRepository;
|
||
|
||
// ✅ 公共属性:PascalCase
|
||
public string OrderNo { get; set; }
|
||
|
||
// ✅ 局部变量:camelCase
|
||
var orderTotal = 100.00m;
|
||
|
||
// ✅ 常量:PascalCase
|
||
public const int MaxOrderItems = 50;
|
||
|
||
// ✅ 枚举:PascalCase,枚举值也是PascalCase
|
||
public enum OrderStatus
|
||
{
|
||
Pending = 1,
|
||
Confirmed = 2,
|
||
Completed = 3
|
||
}
|
||
```
|
||
|
||
### 2.2 数据库命名规范
|
||
```sql
|
||
-- ✅ 表名:小写,下划线分隔,复数形式
|
||
orders
|
||
order_items
|
||
merchant_stores
|
||
|
||
-- ✅ 字段名:小写,下划线分隔
|
||
order_no
|
||
created_at
|
||
total_amount
|
||
|
||
-- ✅ 索引:idx_表名_字段名
|
||
idx_orders_merchant_id
|
||
idx_orders_created_at
|
||
|
||
-- ✅ 外键:fk_表名_引用表名
|
||
fk_orders_merchants
|
||
```
|
||
|
||
## 3. 项目结构规范
|
||
|
||
### 3.1 分层架构(DDD + Clean Architecture)
|
||
```
|
||
TakeoutSaaS/
|
||
├── src/
|
||
│ ├── Api/ # API层(表现层)
|
||
│ │ ├── Controllers/ # 控制器
|
||
│ │ ├── Filters/ # 过滤器
|
||
│ │ ├── Middleware/ # 中间件
|
||
│ │ └── Models/ # DTO模型
|
||
│ ├── Application/ # 应用层
|
||
│ │ ├── Services/ # 应用服务
|
||
│ │ ├── DTOs/ # 数据传输对象
|
||
│ │ ├── Interfaces/ # 服务接口
|
||
│ │ ├── Validators/ # FluentValidation验证器
|
||
│ │ ├── Mappings/ # AutoMapper配置
|
||
│ │ └── Commands/Queries/ # CQRS命令和查询
|
||
│ ├── Domain/ # 领域层(核心业务)
|
||
│ │ ├── Entities/ # 实体
|
||
│ │ ├── ValueObjects/ # 值对象
|
||
│ │ ├── Enums/ # 枚举
|
||
│ │ ├── Events/ # 领域事件
|
||
│ │ └── Interfaces/ # 仓储接口
|
||
│ ├── Infrastructure/ # 基础设施层
|
||
│ │ ├── Data/ # 数据访问
|
||
│ │ │ ├── EFCore/ # EF Core实现
|
||
│ │ │ ├── Dapper/ # Dapper实现
|
||
│ │ │ └── Repositories/ # 仓储实现
|
||
│ │ ├── Cache/ # 缓存实现
|
||
│ │ ├── MessageQueue/ # 消息队列
|
||
│ │ └── ExternalServices/ # 外部服务
|
||
│ ├── Core/ # 核心共享层
|
||
│ │ ├── Constants/ # 常量
|
||
│ │ ├── Exceptions/ # 异常
|
||
│ │ ├── Extensions/ # 扩展方法
|
||
│ │ └── Results/ # 统一返回结果
|
||
│ └── Modules/ # 模块化(可选)
|
||
└── tests/
|
||
├── UnitTests/ # 单元测试
|
||
├── IntegrationTests/ # 集成测试
|
||
└── PerformanceTests/ # 性能测试
|
||
```
|
||
|
||
### 3.2 文件组织规则
|
||
- 每个文件只包含一个公共类/接口
|
||
- 文件名与类名保持一致
|
||
- 相关的类放在同一个文件夹
|
||
- 使用命名空间反映文件夹结构
|
||
|
||
## 4. 代码注释规范
|
||
|
||
### 4.1 XML文档注释(必须)
|
||
```csharp
|
||
/// <summary>
|
||
/// 订单服务接口
|
||
/// </summary>
|
||
public interface IOrderService
|
||
{
|
||
/// <summary>
|
||
/// 创建订单
|
||
/// </summary>
|
||
/// <param name="request">订单创建请求</param>
|
||
/// <returns>订单信息</returns>
|
||
/// <exception cref="BusinessException">业务异常</exception>
|
||
Task<OrderDto> CreateOrderAsync(CreateOrderRequest request);
|
||
}
|
||
```
|
||
|
||
### 4.2 业务逻辑注释
|
||
```csharp
|
||
// ✅ 复杂业务逻辑必须添加注释
|
||
public async Task<decimal> CalculateOrderAmount(Order order)
|
||
{
|
||
// 1. 计算菜品总金额
|
||
var dishAmount = order.Items.Sum(x => x.Price * x.Quantity);
|
||
|
||
// 2. 计算配送费(距离 > 3km,每公里加收2元)
|
||
var deliveryFee = CalculateDeliveryFee(order.Distance);
|
||
|
||
// 3. 应用优惠券折扣
|
||
var discount = await ApplyCouponDiscountAsync(order.CouponId, dishAmount);
|
||
|
||
// 4. 计算最终金额
|
||
return dishAmount + deliveryFee - discount;
|
||
}
|
||
```
|
||
|
||
## 5. 异常处理规范
|
||
|
||
### 5.1 自定义异常
|
||
```csharp
|
||
// ✅ 业务异常
|
||
public class BusinessException : Exception
|
||
{
|
||
public int ErrorCode { get; }
|
||
|
||
public BusinessException(int errorCode, string message)
|
||
: base(message)
|
||
{
|
||
ErrorCode = errorCode;
|
||
}
|
||
}
|
||
|
||
// ✅ 验证异常
|
||
public class ValidationException : Exception
|
||
{
|
||
public IDictionary<string, string[]> Errors { get; }
|
||
|
||
public ValidationException(IDictionary<string, string[]> errors)
|
||
: base("一个或多个验证错误")
|
||
{
|
||
Errors = errors;
|
||
}
|
||
}
|
||
```
|
||
|
||
### 5.2 全局异常处理中间件(必须实现)
|
||
```csharp
|
||
public class ExceptionHandlingMiddleware
|
||
{
|
||
private readonly RequestDelegate _next;
|
||
private readonly ILogger<ExceptionHandlingMiddleware> _logger;
|
||
|
||
public async Task InvokeAsync(HttpContext context)
|
||
{
|
||
try
|
||
{
|
||
await _next(context);
|
||
}
|
||
catch (BusinessException ex)
|
||
{
|
||
_logger.LogWarning(ex, "业务异常:{Message}", ex.Message);
|
||
await HandleBusinessExceptionAsync(context, ex);
|
||
}
|
||
catch (ValidationException ex)
|
||
{
|
||
_logger.LogWarning(ex, "验证异常:{Errors}", ex.Errors);
|
||
await HandleValidationExceptionAsync(context, ex);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "系统异常:{Message}", ex.Message);
|
||
await HandleSystemExceptionAsync(context, ex);
|
||
}
|
||
}
|
||
|
||
private static Task HandleBusinessExceptionAsync(HttpContext context, BusinessException ex)
|
||
{
|
||
context.Response.StatusCode = StatusCodes.Status422UnprocessableEntity;
|
||
return context.Response.WriteAsJsonAsync(new
|
||
{
|
||
success = false,
|
||
code = ex.ErrorCode,
|
||
message = ex.Message
|
||
});
|
||
}
|
||
}
|
||
```
|
||
|
||
### 5.3 异常使用示例
|
||
```csharp
|
||
// ✅ 正确的异常抛出
|
||
public async Task<Order> GetOrderAsync(Guid orderId)
|
||
{
|
||
var order = await _orderRepository.GetByIdAsync(orderId);
|
||
if (order == null)
|
||
{
|
||
throw new BusinessException(404, "订单不存在");
|
||
}
|
||
return order;
|
||
}
|
||
|
||
// ❌ 错误:不要吞掉异常
|
||
try
|
||
{
|
||
// ...
|
||
}
|
||
catch (Exception)
|
||
{
|
||
// 什么都不做 - 这是错误的!
|
||
}
|
||
```
|
||
|
||
## 6. 服务层编码规范
|
||
|
||
### 6.1 服务类结构(标准模板)
|
||
```csharp
|
||
// ✅ 好的服务实现
|
||
public class OrderService : IOrderService
|
||
{
|
||
private readonly IOrderRepository _orderRepository;
|
||
private readonly IUnitOfWork _unitOfWork;
|
||
private readonly ILogger<OrderService> _logger;
|
||
private readonly IMapper _mapper;
|
||
private readonly IOptions<OrderSettings> _settings;
|
||
|
||
public OrderService(
|
||
IOrderRepository orderRepository,
|
||
IUnitOfWork unitOfWork,
|
||
ILogger<OrderService> logger,
|
||
IMapper mapper,
|
||
IOptions<OrderSettings> settings)
|
||
{
|
||
_orderRepository = orderRepository;
|
||
_unitOfWork = unitOfWork;
|
||
_logger = logger;
|
||
_mapper = mapper;
|
||
_settings = settings;
|
||
}
|
||
|
||
public async Task<OrderDto> CreateOrderAsync(CreateOrderRequest request)
|
||
{
|
||
try
|
||
{
|
||
// 1. 参数验证(FluentValidation会自动验证,这里是额外检查)
|
||
if (request == null)
|
||
throw new ArgumentNullException(nameof(request));
|
||
|
||
// 2. 记录日志
|
||
_logger.LogInformation("创建订单:{@Request}", request);
|
||
|
||
// 3. 业务逻辑
|
||
var order = new Order
|
||
{
|
||
OrderNo = GenerateOrderNo(),
|
||
TotalAmount = request.TotalAmount,
|
||
DeliveryFee = _settings.Value.DefaultDeliveryFee,
|
||
CreatedAt = DateTime.UtcNow
|
||
};
|
||
|
||
// 4. 数据持久化
|
||
await _orderRepository.AddAsync(order);
|
||
await _unitOfWork.SaveChangesAsync();
|
||
|
||
// 5. 记录成功日志
|
||
_logger.LogInformation("订单创建成功:{OrderId}", order.Id);
|
||
|
||
// 6. 返回DTO
|
||
return _mapper.Map<OrderDto>(order);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "创建订单失败:{@Request}", request);
|
||
throw;
|
||
}
|
||
}
|
||
}
|
||
|
||
// ❌ 错误的服务实现
|
||
public class BadOrderService
|
||
{
|
||
// ❌ 直接注入DbContext而不是仓储
|
||
private readonly AppDbContext _dbContext;
|
||
|
||
// ❌ 没有日志
|
||
// ❌ 硬编码配置
|
||
public Order CreateOrder(CreateOrderRequest request)
|
||
{
|
||
var order = new Order();
|
||
order.DeliveryFee = 5.0m; // ❌ 硬编码
|
||
_dbContext.Orders.Add(order);
|
||
_dbContext.SaveChanges(); // ❌ 同步方法
|
||
return order; // ❌ 返回实体而不是DTO
|
||
}
|
||
}
|
||
```
|
||
|
||
### 6.2 依赖注入规则
|
||
```csharp
|
||
// ✅ 使用构造函数注入
|
||
public class OrderService
|
||
{
|
||
private readonly IOrderRepository _orderRepository;
|
||
|
||
public OrderService(IOrderRepository orderRepository)
|
||
{
|
||
_orderRepository = orderRepository;
|
||
}
|
||
}
|
||
|
||
// ❌ 不要使用属性注入
|
||
public class BadOrderService
|
||
{
|
||
[Inject]
|
||
public IOrderRepository OrderRepository { get; set; }
|
||
}
|
||
|
||
// ❌ 不要使用服务定位器模式
|
||
public class BadOrderService
|
||
{
|
||
public void DoSomething()
|
||
{
|
||
var repository = ServiceLocator.GetService<IOrderRepository>();
|
||
}
|
||
}
|
||
```
|
||
|
||
## 7. 数据访问规范
|
||
|
||
### 7.1 仓储模式(必须使用)
|
||
```csharp
|
||
// ✅ 仓储接口
|
||
public interface IOrderRepository
|
||
{
|
||
Task<Order> GetByIdAsync(Guid id);
|
||
Task<IEnumerable<Order>> GetAllAsync();
|
||
Task<Order> AddAsync(Order order);
|
||
Task UpdateAsync(Order order);
|
||
Task DeleteAsync(Guid id);
|
||
Task<bool> ExistsAsync(Guid id);
|
||
}
|
||
|
||
// ✅ EF Core仓储实现
|
||
public class OrderRepository : IOrderRepository
|
||
{
|
||
private readonly AppDbContext _context;
|
||
|
||
public OrderRepository(AppDbContext context)
|
||
{
|
||
_context = context;
|
||
}
|
||
|
||
public async Task<Order> GetByIdAsync(Guid id)
|
||
{
|
||
return await _context.Orders
|
||
.Include(o => o.OrderItems)
|
||
.Include(o => o.Customer)
|
||
.FirstOrDefaultAsync(o => o.Id == id);
|
||
}
|
||
|
||
public async Task<Order> AddAsync(Order order)
|
||
{
|
||
await _context.Orders.AddAsync(order);
|
||
return order;
|
||
}
|
||
}
|
||
```
|
||
|
||
### 7.2 EF Core vs Dapper 使用场景
|
||
```csharp
|
||
// ✅ EF Core - 复杂查询和实体管理
|
||
public async Task<Order> GetOrderWithDetailsAsync(Guid orderId)
|
||
{
|
||
return await _dbContext.Orders
|
||
.Include(o => o.OrderItems)
|
||
.ThenInclude(oi => oi.Dish)
|
||
.Include(o => o.Customer)
|
||
.Include(o => o.Merchant)
|
||
.FirstOrDefaultAsync(o => o.Id == orderId);
|
||
}
|
||
|
||
// ✅ Dapper - 高性能统计查询
|
||
public async Task<OrderStatistics> GetOrderStatisticsAsync(DateTime startDate, DateTime endDate)
|
||
{
|
||
var sql = @"
|
||
SELECT
|
||
COUNT(*) as TotalOrders,
|
||
SUM(total_amount) as TotalAmount,
|
||
AVG(total_amount) as AvgAmount,
|
||
MAX(total_amount) as MaxAmount,
|
||
MIN(total_amount) as MinAmount
|
||
FROM orders
|
||
WHERE created_at BETWEEN @StartDate AND @EndDate
|
||
AND status = @Status";
|
||
|
||
return await _connection.QueryFirstOrDefaultAsync<OrderStatistics>(sql,
|
||
new { StartDate = startDate, EndDate = endDate, Status = OrderStatus.Completed });
|
||
}
|
||
|
||
// ✅ Dapper - 批量插入
|
||
public async Task<int> BulkInsertOrdersAsync(IEnumerable<Order> orders)
|
||
{
|
||
var sql = @"
|
||
INSERT INTO orders (id, order_no, merchant_id, total_amount, created_at)
|
||
VALUES (@Id, @OrderNo, @MerchantId, @TotalAmount, @CreatedAt)";
|
||
|
||
return await _connection.ExecuteAsync(sql, orders);
|
||
}
|
||
```
|
||
|
||
### 7.3 工作单元模式(必须使用)
|
||
```csharp
|
||
// ✅ 工作单元接口
|
||
public interface IUnitOfWork : IDisposable
|
||
{
|
||
Task<int> SaveChangesAsync(CancellationToken cancellationToken = default);
|
||
Task BeginTransactionAsync();
|
||
Task CommitTransactionAsync();
|
||
Task RollbackTransactionAsync();
|
||
}
|
||
|
||
// ✅ 使用工作单元
|
||
public async Task<OrderDto> CreateOrderWithItemsAsync(CreateOrderRequest request)
|
||
{
|
||
await _unitOfWork.BeginTransactionAsync();
|
||
|
||
try
|
||
{
|
||
// 1. 创建订单
|
||
var order = new Order { /* ... */ };
|
||
await _orderRepository.AddAsync(order);
|
||
|
||
// 2. 创建订单项
|
||
foreach (var item in request.Items)
|
||
{
|
||
var orderItem = new OrderItem { /* ... */ };
|
||
await _orderItemRepository.AddAsync(orderItem);
|
||
}
|
||
|
||
// 3. 提交事务
|
||
await _unitOfWork.SaveChangesAsync();
|
||
await _unitOfWork.CommitTransactionAsync();
|
||
|
||
return _mapper.Map<OrderDto>(order);
|
||
}
|
||
catch
|
||
{
|
||
await _unitOfWork.RollbackTransactionAsync();
|
||
throw;
|
||
}
|
||
}
|
||
```
|
||
|
||
## 8. CQRS模式规范
|
||
|
||
### 8.1 命令(Command)- 写操作
|
||
```csharp
|
||
// ✅ 命令定义
|
||
public class CreateOrderCommand : IRequest<OrderDto>
|
||
{
|
||
public Guid MerchantId { get; set; }
|
||
public Guid CustomerId { get; set; }
|
||
public List<OrderItemDto> Items { get; set; }
|
||
public decimal TotalAmount { get; set; }
|
||
}
|
||
|
||
// ✅ 命令处理器
|
||
public class CreateOrderCommandHandler : IRequestHandler<CreateOrderCommand, OrderDto>
|
||
{
|
||
private readonly IOrderRepository _orderRepository;
|
||
private readonly IUnitOfWork _unitOfWork;
|
||
private readonly ILogger<CreateOrderCommandHandler> _logger;
|
||
|
||
public async Task<OrderDto> Handle(CreateOrderCommand request, CancellationToken cancellationToken)
|
||
{
|
||
_logger.LogInformation("处理创建订单命令:{@Command}", request);
|
||
|
||
var order = new Order
|
||
{
|
||
MerchantId = request.MerchantId,
|
||
CustomerId = request.CustomerId,
|
||
TotalAmount = request.TotalAmount
|
||
};
|
||
|
||
await _orderRepository.AddAsync(order);
|
||
await _unitOfWork.SaveChangesAsync(cancellationToken);
|
||
|
||
return _mapper.Map<OrderDto>(order);
|
||
}
|
||
}
|
||
```
|
||
|
||
### 8.2 查询(Query)- 读操作
|
||
```csharp
|
||
// ✅ 查询定义
|
||
public class GetOrderByIdQuery : IRequest<OrderDto>
|
||
{
|
||
public Guid OrderId { get; set; }
|
||
}
|
||
|
||
// ✅ 查询处理器
|
||
public class GetOrderByIdQueryHandler : IRequestHandler<GetOrderByIdQuery, OrderDto>
|
||
{
|
||
private readonly IOrderRepository _orderRepository;
|
||
private readonly IMapper _mapper;
|
||
|
||
public async Task<OrderDto> Handle(GetOrderByIdQuery request, CancellationToken cancellationToken)
|
||
{
|
||
var order = await _orderRepository.GetByIdAsync(request.OrderId);
|
||
|
||
if (order == null)
|
||
throw new BusinessException(404, "订单不存在");
|
||
|
||
return _mapper.Map<OrderDto>(order);
|
||
}
|
||
}
|
||
```
|
||
|
||
## 9. 验证规范(FluentValidation)
|
||
|
||
### 9.1 验证器定义
|
||
```csharp
|
||
// ✅ 使用FluentValidation
|
||
public class CreateOrderRequestValidator : AbstractValidator<CreateOrderRequest>
|
||
{
|
||
public CreateOrderRequestValidator()
|
||
{
|
||
RuleFor(x => x.MerchantId)
|
||
.NotEmpty().WithMessage("商家ID不能为空");
|
||
|
||
RuleFor(x => x.CustomerId)
|
||
.NotEmpty().WithMessage("客户ID不能为空");
|
||
|
||
RuleFor(x => x.Items)
|
||
.NotEmpty().WithMessage("订单项不能为空")
|
||
.Must(items => items.Count <= 50).WithMessage("订单项不能超过50个");
|
||
|
||
RuleFor(x => x.TotalAmount)
|
||
.GreaterThan(0).WithMessage("订单金额必须大于0");
|
||
|
||
RuleFor(x => x.DeliveryAddress)
|
||
.NotEmpty().WithMessage("配送地址不能为空")
|
||
.MaximumLength(200).WithMessage("配送地址不能超过200个字符");
|
||
}
|
||
}
|
||
```
|
||
|
||
### 9.2 验证器注册
|
||
```csharp
|
||
// ✅ 在Program.cs中注册
|
||
builder.Services.AddValidatorsFromAssemblyContaining<CreateOrderRequestValidator>();
|
||
builder.Services.AddFluentValidationAutoValidation();
|
||
```
|
||
|
||
## 10. 缓存策略规范
|
||
|
||
|
||
### 10.1 缓存时间策略
|
||
```csharp
|
||
// ✅ 缓存时间常量
|
||
public static class CacheTimeouts
|
||
{
|
||
public static readonly TimeSpan MerchantInfo = TimeSpan.FromMinutes(30);
|
||
public static readonly TimeSpan DishInfo = TimeSpan.FromMinutes(15);
|
||
public static readonly TimeSpan UserSession = TimeSpan.FromHours(2);
|
||
public static readonly TimeSpan ConfigInfo = TimeSpan.FromHours(1);
|
||
public static readonly TimeSpan HotData = TimeSpan.FromMinutes(5);
|
||
}
|
||
```
|
||
|
||
### 10.2 缓存使用示例
|
||
```csharp
|
||
// ✅ 使用分布式缓存(Redis)
|
||
public class MerchantService : IMerchantService
|
||
{
|
||
private readonly IMerchantRepository _merchantRepository;
|
||
private readonly IDistributedCache _cache;
|
||
private readonly ILogger<MerchantService> _logger;
|
||
|
||
public async Task<MerchantDto> GetMerchantAsync(Guid merchantId)
|
||
{
|
||
var cacheKey = $"merchant:{merchantId}";
|
||
|
||
// 1. 尝试从缓存获取
|
||
var cachedData = await _cache.GetStringAsync(cacheKey);
|
||
if (!string.IsNullOrEmpty(cachedData))
|
||
{
|
||
_logger.LogDebug("从缓存获取商家信息:{MerchantId}", merchantId);
|
||
return JsonSerializer.Deserialize<MerchantDto>(cachedData);
|
||
}
|
||
|
||
// 2. 缓存未命中,从数据库查询
|
||
var merchant = await _merchantRepository.GetByIdAsync(merchantId);
|
||
if (merchant == null)
|
||
throw new BusinessException(404, "商家不存在");
|
||
|
||
var dto = _mapper.Map<MerchantDto>(merchant);
|
||
|
||
// 3. 写入缓存
|
||
var cacheOptions = new DistributedCacheEntryOptions
|
||
{
|
||
AbsoluteExpirationRelativeToNow = CacheTimeouts.MerchantInfo
|
||
};
|
||
await _cache.SetStringAsync(cacheKey, JsonSerializer.Serialize(dto), cacheOptions);
|
||
|
||
_logger.LogDebug("商家信息已缓存:{MerchantId}", merchantId);
|
||
return dto;
|
||
}
|
||
|
||
public async Task UpdateMerchantAsync(Guid merchantId, UpdateMerchantRequest request)
|
||
{
|
||
// 更新数据库
|
||
var merchant = await _merchantRepository.GetByIdAsync(merchantId);
|
||
// ... 更新逻辑
|
||
await _unitOfWork.SaveChangesAsync();
|
||
|
||
// ✅ 更新后清除缓存
|
||
var cacheKey = $"merchant:{merchantId}";
|
||
await _cache.RemoveAsync(cacheKey);
|
||
_logger.LogDebug("已清除商家缓存:{MerchantId}", merchantId);
|
||
}
|
||
}
|
||
```
|
||
|
||
## 11. 日志规范(Serilog)
|
||
|
||
### 11.1 日志级别使用
|
||
```csharp
|
||
// ✅ 正确的日志级别使用
|
||
public class OrderService
|
||
{
|
||
private readonly ILogger<OrderService> _logger;
|
||
|
||
public async Task<OrderDto> CreateOrderAsync(CreateOrderRequest request)
|
||
{
|
||
// Trace: 非常详细的调试信息(生产环境不记录)
|
||
_logger.LogTrace("进入CreateOrderAsync方法");
|
||
|
||
// Debug: 调试信息(生产环境不记录)
|
||
_logger.LogDebug("订单请求参数:{@Request}", request);
|
||
|
||
// Information: 一般信息(重要业务流程)
|
||
_logger.LogInformation("开始创建订单,商家ID:{MerchantId},客户ID:{CustomerId}",
|
||
request.MerchantId, request.CustomerId);
|
||
|
||
try
|
||
{
|
||
// ... 业务逻辑
|
||
|
||
// Information: 成功完成
|
||
_logger.LogInformation("订单创建成功:{OrderId},订单号:{OrderNo}",
|
||
order.Id, order.OrderNo);
|
||
|
||
return dto;
|
||
}
|
||
catch (BusinessException ex)
|
||
{
|
||
// Warning: 业务异常(预期内的错误)
|
||
_logger.LogWarning(ex, "创建订单失败(业务异常):{Message}", ex.Message);
|
||
throw;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
// Error: 系统异常(非预期错误)
|
||
_logger.LogError(ex, "创建订单失败(系统异常):{@Request}", request);
|
||
throw;
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### 11.2 结构化日志
|
||
```csharp
|
||
// ✅ 使用结构化日志(推荐)
|
||
_logger.LogInformation("用户 {UserId} 创建了订单 {OrderId},金额 {Amount}",
|
||
userId, orderId, amount);
|
||
|
||
// ✅ 记录对象(使用@符号)
|
||
_logger.LogInformation("订单详情:{@Order}", order);
|
||
|
||
// ❌ 不要使用字符串拼接
|
||
_logger.LogInformation("用户 " + userId + " 创建了订单 " + orderId);
|
||
```
|
||
|
||
### 11.3 敏感信息处理
|
||
```csharp
|
||
// ✅ 不要记录敏感信息
|
||
public class PaymentService
|
||
{
|
||
public async Task ProcessPaymentAsync(PaymentRequest request)
|
||
{
|
||
// ❌ 错误:记录了密码、支付密码等敏感信息
|
||
_logger.LogInformation("支付请求:{@Request}", request);
|
||
|
||
// ✅ 正确:只记录非敏感信息
|
||
_logger.LogInformation("处理支付,订单ID:{OrderId},金额:{Amount}",
|
||
request.OrderId, request.Amount);
|
||
}
|
||
}
|
||
```
|
||
|
||
## 12. API控制器规范
|
||
|
||
### 12.1 控制器结构(标准模板)
|
||
```csharp
|
||
/// <summary>
|
||
/// 订单管理API
|
||
/// </summary>
|
||
[ApiController]
|
||
[Route("api/[controller]")]
|
||
[Authorize]
|
||
public class OrdersController : ControllerBase
|
||
{
|
||
private readonly IMediator _mediator;
|
||
private readonly ILogger<OrdersController> _logger;
|
||
|
||
public OrdersController(IMediator mediator, ILogger<OrdersController> logger)
|
||
{
|
||
_mediator = mediator;
|
||
_logger = logger;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 创建订单
|
||
/// </summary>
|
||
/// <param name="request">订单创建请求</param>
|
||
/// <returns>订单信息</returns>
|
||
/// <response code="200">创建成功</response>
|
||
/// <response code="400">请求参数错误</response>
|
||
/// <response code="422">业务验证失败</response>
|
||
[HttpPost]
|
||
[ProducesResponseType(typeof(ApiResponse<OrderDto>), StatusCodes.Status200OK)]
|
||
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status400BadRequest)]
|
||
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status422UnprocessableEntity)]
|
||
public async Task<IActionResult> CreateOrder([FromBody] CreateOrderRequest request)
|
||
{
|
||
_logger.LogInformation("API调用:创建订单");
|
||
|
||
var command = new CreateOrderCommand
|
||
{
|
||
MerchantId = request.MerchantId,
|
||
CustomerId = request.CustomerId,
|
||
Items = request.Items,
|
||
TotalAmount = request.TotalAmount
|
||
};
|
||
|
||
var result = await _mediator.Send(command);
|
||
|
||
return Ok(ApiResponse<OrderDto>.SuccessResult(result));
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取订单详情
|
||
/// </summary>
|
||
/// <param name="id">订单ID</param>
|
||
/// <returns>订单详情</returns>
|
||
[HttpGet("{id}")]
|
||
[ProducesResponseType(typeof(ApiResponse<OrderDto>), StatusCodes.Status200OK)]
|
||
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status404NotFound)]
|
||
public async Task<IActionResult> GetOrder(Guid id)
|
||
{
|
||
var query = new GetOrderByIdQuery { OrderId = id };
|
||
var result = await _mediator.Send(query);
|
||
return Ok(ApiResponse<OrderDto>.SuccessResult(result));
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取订单列表
|
||
/// </summary>
|
||
/// <param name="request">查询参数</param>
|
||
/// <returns>订单列表</returns>
|
||
[HttpGet]
|
||
[ProducesResponseType(typeof(ApiResponse<PagedResult<OrderDto>>), StatusCodes.Status200OK)]
|
||
public async Task<IActionResult> GetOrders([FromQuery] GetOrdersRequest request)
|
||
{
|
||
var query = new GetOrdersQuery
|
||
{
|
||
PageIndex = request.PageIndex,
|
||
PageSize = request.PageSize,
|
||
Status = request.Status
|
||
};
|
||
|
||
var result = await _mediator.Send(query);
|
||
return Ok(ApiResponse<PagedResult<OrderDto>>.SuccessResult(result));
|
||
}
|
||
}
|
||
```
|
||
|
||
### 12.2 统一返回结果(ApiResponse)
|
||
```csharp
|
||
// ✅ 统一返回结果(泛型)
|
||
public class ApiResponse<T>
|
||
{
|
||
public bool Success { get; set; }
|
||
public int Code { get; set; } = 200;
|
||
public string? Message { get; set; }
|
||
public T? Data { get; set; }
|
||
public DateTime Timestamp { get; set; } = DateTime.UtcNow;
|
||
|
||
// 工厂方法:成功
|
||
public static ApiResponse<T> SuccessResult(T data, string? message = "操作成功") => new()
|
||
{
|
||
Success = true,
|
||
Code = 200,
|
||
Message = message,
|
||
Data = data
|
||
};
|
||
|
||
// 工厂方法:失败
|
||
public static ApiResponse<T> Failure(int code, string message) => new()
|
||
{
|
||
Success = false,
|
||
Code = code,
|
||
Message = message
|
||
};
|
||
}
|
||
|
||
// ✅ 分页结果
|
||
public class PagedResult<T>
|
||
{
|
||
public List<T> Items { get; set; }
|
||
public int TotalCount { get; set; }
|
||
public int PageIndex { get; set; }
|
||
public int PageSize { get; set; }
|
||
public int TotalPages => (int)Math.Ceiling(TotalCount / (double)PageSize);
|
||
public bool HasPreviousPage => PageIndex > 1;
|
||
public bool HasNextPage => PageIndex < TotalPages;
|
||
}
|
||
```
|
||
|
||
### 12.3 非泛型便捷封装(推荐在仅返回消息时使用)
|
||
```csharp
|
||
public static class ApiResponse
|
||
{
|
||
// ✅ 仅返回成功/消息(无数据载荷)
|
||
public static ApiResponse<object> Success(string? message = "操作成功")
|
||
=> new ApiResponse<object> { Success = true, Code = 200, Message = message, Data = null };
|
||
|
||
// ✅ 错误(配合统一错误码)
|
||
public static ApiResponse<object> Failure(int code, string message)
|
||
=> new ApiResponse<object> { Success = false, Code = code, Message = message, Data = null };
|
||
}
|
||
```
|
||
|
||
### 12.4 统一错误码(ErrorCodes)
|
||
```csharp
|
||
public static class ErrorCodes
|
||
{
|
||
public const int BadRequest = 400;
|
||
public const int Unauthorized = 401;
|
||
public const int Forbidden = 403;
|
||
public const int NotFound = 404;
|
||
public const int Conflict = 409;
|
||
public const int ValidationFailed = 422; // 业务/验证不通过
|
||
public const int InternalServerError = 500;
|
||
|
||
// 业务自定义区间(10000+)
|
||
public const int BusinessError = 10001;
|
||
}
|
||
```
|
||
|
||
### 12.5 错误响应的ProblemDetails映射(全局异常处理中间件)
|
||
```csharp
|
||
public class BusinessException : Exception
|
||
{
|
||
public int ErrorCode { get; }
|
||
public BusinessException(int errorCode, string message) : base(message) => ErrorCode = errorCode;
|
||
}
|
||
|
||
public class ValidationException : Exception
|
||
{
|
||
public IDictionary<string, string[]> Errors { get; }
|
||
public ValidationException(IDictionary<string, string[]> errors) : base("一个或多个验证错误") => Errors = errors;
|
||
}
|
||
|
||
public class ExceptionHandlingMiddleware
|
||
{
|
||
private readonly RequestDelegate _next;
|
||
private readonly ILogger<ExceptionHandlingMiddleware> _logger;
|
||
|
||
public ExceptionHandlingMiddleware(RequestDelegate next, ILogger<ExceptionHandlingMiddleware> logger)
|
||
{ _next = next; _logger = logger; }
|
||
|
||
public async Task InvokeAsync(HttpContext context)
|
||
{
|
||
try
|
||
{
|
||
await _next(context);
|
||
}
|
||
catch (BusinessException ex)
|
||
{
|
||
await WriteProblemDetailsAsync(context, StatusCodes.Status422UnprocessableEntity, "业务异常", ex.Message, ex.ErrorCode);
|
||
}
|
||
catch (ValidationException ex)
|
||
{
|
||
await WriteProblemDetailsAsync(context, StatusCodes.Status422UnprocessableEntity, "验证异常", ex.Message, ErrorCodes.ValidationFailed, ex.Errors);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "系统异常");
|
||
await WriteProblemDetailsAsync(context, StatusCodes.Status500InternalServerError, "系统异常", "服务器发生错误,请稍后重试", ErrorCodes.InternalServerError);
|
||
}
|
||
}
|
||
|
||
private static Task WriteProblemDetailsAsync(HttpContext context, int status, string title, string detail, int code, object? errors = null)
|
||
{
|
||
var problem = new ProblemDetails
|
||
{
|
||
Status = status,
|
||
Title = title,
|
||
Detail = detail,
|
||
Instance = context.Request.Path,
|
||
Type = $"https://httpstatuses.com/{status}"
|
||
};
|
||
problem.Extensions["code"] = code;
|
||
if (errors != null) problem.Extensions["errors"] = errors;
|
||
|
||
context.Response.StatusCode = status;
|
||
context.Response.ContentType = "application/problem+json";
|
||
return context.Response.WriteAsJsonAsync(problem);
|
||
}
|
||
}
|
||
|
||
// Program.cs 中注册
|
||
app.UseMiddleware<ExceptionHandlingMiddleware>();
|
||
```
|
||
|
||
|
||
## 13. 实体和DTO规范
|
||
|
||
### 13.1 实体类(Domain Entity)
|
||
```csharp
|
||
// ✅ 领域实体
|
||
public class Order : BaseEntity
|
||
{
|
||
public Guid Id { get; private set; }
|
||
public string OrderNo { get; private set; }
|
||
public Guid MerchantId { get; private set; }
|
||
public Guid CustomerId { get; private set; }
|
||
public decimal TotalAmount { get; private set; }
|
||
public OrderStatus Status { get; private set; }
|
||
public DateTime CreatedAt { get; private set; }
|
||
public DateTime? UpdatedAt { get; private set; }
|
||
|
||
// 导航属性
|
||
public virtual Merchant Merchant { get; private set; }
|
||
public virtual Customer Customer { get; private set; }
|
||
public virtual ICollection<OrderItem> OrderItems { get; private set; }
|
||
|
||
// 私有构造函数(用于EF Core)
|
||
private Order() { }
|
||
|
||
// 工厂方法
|
||
public static Order Create(Guid merchantId, Guid customerId, decimal totalAmount)
|
||
{
|
||
return new Order
|
||
{
|
||
Id = Guid.NewGuid(),
|
||
OrderNo = GenerateOrderNo(),
|
||
MerchantId = merchantId,
|
||
CustomerId = customerId,
|
||
TotalAmount = totalAmount,
|
||
Status = OrderStatus.Pending,
|
||
CreatedAt = DateTime.UtcNow
|
||
};
|
||
}
|
||
|
||
// 业务方法
|
||
public void Confirm()
|
||
{
|
||
if (Status != OrderStatus.Pending)
|
||
throw new BusinessException(400, "只有待确认的订单才能确认");
|
||
|
||
Status = OrderStatus.Confirmed;
|
||
UpdatedAt = DateTime.UtcNow;
|
||
}
|
||
|
||
public void Cancel(string reason)
|
||
{
|
||
if (Status == OrderStatus.Completed)
|
||
throw new BusinessException(400, "已完成的订单不能取消");
|
||
|
||
Status = OrderStatus.Cancelled;
|
||
UpdatedAt = DateTime.UtcNow;
|
||
}
|
||
|
||
private static string GenerateOrderNo()
|
||
{
|
||
return $"ORD{DateTime.UtcNow:yyyyMMddHHmmss}{Random.Shared.Next(1000, 9999)}";
|
||
}
|
||
}
|
||
```
|
||
|
||
### 13.2 DTO(数据传输对象)
|
||
```csharp
|
||
// ✅ 请求DTO
|
||
public class CreateOrderRequest
|
||
{
|
||
public Guid MerchantId { get; set; }
|
||
public Guid CustomerId { get; set; }
|
||
public List<OrderItemDto> Items { get; set; }
|
||
public decimal TotalAmount { get; set; }
|
||
public string DeliveryAddress { get; set; }
|
||
public string ContactPhone { get; set; }
|
||
public string Remark { get; set; }
|
||
}
|
||
|
||
// ✅ 响应DTO
|
||
public class OrderDto
|
||
{
|
||
public Guid Id { get; set; }
|
||
public string OrderNo { get; set; }
|
||
public Guid MerchantId { get; set; }
|
||
public string MerchantName { get; set; }
|
||
public Guid CustomerId { get; set; }
|
||
public string CustomerName { get; set; }
|
||
public decimal TotalAmount { get; set; }
|
||
public OrderStatus Status { get; set; }
|
||
public string StatusText { get; set; }
|
||
public List<OrderItemDto> Items { get; set; }
|
||
public DateTime CreatedAt { get; set; }
|
||
}
|
||
```
|
||
|
||
### 13.3 AutoMapper配置
|
||
```csharp
|
||
// ✅ AutoMapper Profile
|
||
public class OrderMappingProfile : Profile
|
||
{
|
||
public OrderMappingProfile()
|
||
{
|
||
CreateMap<Order, OrderDto>()
|
||
.ForMember(dest => dest.MerchantName, opt => opt.MapFrom(src => src.Merchant.Name))
|
||
.ForMember(dest => dest.CustomerName, opt => opt.MapFrom(src => src.Customer.Name))
|
||
.ForMember(dest => dest.StatusText, opt => opt.MapFrom(src => src.Status.ToString()));
|
||
|
||
CreateMap<CreateOrderRequest, Order>()
|
||
.ForMember(dest => dest.Id, opt => opt.Ignore())
|
||
.ForMember(dest => dest.OrderNo, opt => opt.Ignore())
|
||
.ForMember(dest => dest.CreatedAt, opt => opt.Ignore());
|
||
}
|
||
}
|
||
```
|
||
|
||
|
||
## 14. Git工作流规范
|
||
|
||
### 14.1 分支管理
|
||
```
|
||
main # 主分支,生产环境代码
|
||
├── develop # 开发分支
|
||
│ ├── feature/order-management # 功能分支
|
||
│ ├── feature/payment-integration # 功能分支
|
||
│ └── bugfix/order-calculation # 修复分支
|
||
└── hotfix/critical-bug # 紧急修复分支
|
||
```
|
||
|
||
### 14.2 分支命名规范
|
||
- **功能分支**:`feature/功能名称`(如:`feature/order-management`)
|
||
- **修复分支**:`bugfix/问题描述`(如:`bugfix/order-calculation`)
|
||
- **紧急修复**:`hotfix/问题描述`(如:`hotfix/payment-error`)
|
||
- **发布分支**:`release/版本号`(如:`release/v1.0.0`)
|
||
|
||
### 14.3 提交信息规范(严格遵守)
|
||
```bash
|
||
# 格式:<type>(<scope>): <subject>
|
||
|
||
# type类型(必须使用以下之一):
|
||
# feat: 新功能
|
||
# fix: 修复bug
|
||
# docs: 文档更新
|
||
# style: 代码格式调整(不影响代码运行)
|
||
# refactor: 重构(既不是新功能也不是修复bug)
|
||
# perf: 性能优化
|
||
# test: 测试相关
|
||
# chore: 构建/工具相关
|
||
|
||
# 示例(必须遵循):
|
||
git commit -m "feat(order): 添加订单创建功能"
|
||
git commit -m "fix(payment): 修复支付回调处理错误"
|
||
git commit -m "docs(api): 更新API文档"
|
||
git commit -m "refactor(service): 重构订单服务"
|
||
git commit -m "perf(query): 优化订单查询性能"
|
||
```
|
||
|
||
## 15. 单元测试规范
|
||
|
||
### 15.1 测试命名规范
|
||
```csharp
|
||
// ✅ 测试命名格式:MethodName_Scenario_ExpectedResult
|
||
[Fact]
|
||
public async Task CreateOrder_ValidRequest_ReturnsOrderDto()
|
||
{
|
||
// Arrange(准备)
|
||
var request = new CreateOrderRequest
|
||
{
|
||
MerchantId = Guid.NewGuid(),
|
||
CustomerId = Guid.NewGuid(),
|
||
Items = new List<OrderItemDto>
|
||
{
|
||
new OrderItemDto { DishId = Guid.NewGuid(), Quantity = 2, Price = 50.00m }
|
||
},
|
||
TotalAmount = 100.00m
|
||
};
|
||
|
||
// Act(执行)
|
||
var result = await _orderService.CreateOrderAsync(request);
|
||
|
||
// Assert(断言)
|
||
result.Should().NotBeNull();
|
||
result.OrderNo.Should().NotBeNullOrEmpty();
|
||
result.TotalAmount.Should().Be(100.00m);
|
||
}
|
||
|
||
[Fact]
|
||
public async Task CreateOrder_InvalidMerchantId_ThrowsBusinessException()
|
||
{
|
||
// Arrange
|
||
var request = new CreateOrderRequest
|
||
{
|
||
MerchantId = Guid.Empty, // 无效的商家ID
|
||
CustomerId = Guid.NewGuid(),
|
||
TotalAmount = 100.00m
|
||
};
|
||
|
||
// Act & Assert
|
||
await Assert.ThrowsAsync<BusinessException>(
|
||
async () => await _orderService.CreateOrderAsync(request));
|
||
}
|
||
```
|
||
|
||
### 15.2 Mock使用
|
||
```csharp
|
||
// ✅ 使用Moq进行Mock
|
||
public class OrderServiceTests
|
||
{
|
||
private readonly Mock<IOrderRepository> _orderRepositoryMock;
|
||
private readonly Mock<IUnitOfWork> _unitOfWorkMock;
|
||
private readonly Mock<ILogger<OrderService>> _loggerMock;
|
||
private readonly Mock<IMapper> _mapperMock;
|
||
private readonly OrderService _orderService;
|
||
|
||
public OrderServiceTests()
|
||
{
|
||
_orderRepositoryMock = new Mock<IOrderRepository>();
|
||
_unitOfWorkMock = new Mock<IUnitOfWork>();
|
||
_loggerMock = new Mock<ILogger<OrderService>>();
|
||
_mapperMock = new Mock<IMapper>();
|
||
|
||
_orderService = new OrderService(
|
||
_orderRepositoryMock.Object,
|
||
_unitOfWorkMock.Object,
|
||
_loggerMock.Object,
|
||
_mapperMock.Object,
|
||
Options.Create(new OrderSettings())
|
||
);
|
||
}
|
||
|
||
[Fact]
|
||
public async Task GetOrder_ExistingId_ReturnsOrder()
|
||
{
|
||
// Arrange
|
||
var orderId = Guid.NewGuid();
|
||
var order = new Order { Id = orderId, OrderNo = "ORD001" };
|
||
var orderDto = new OrderDto { Id = orderId, OrderNo = "ORD001" };
|
||
|
||
_orderRepositoryMock
|
||
.Setup(x => x.GetByIdAsync(orderId))
|
||
.ReturnsAsync(order);
|
||
|
||
_mapperMock
|
||
.Setup(x => x.Map<OrderDto>(order))
|
||
.Returns(orderDto);
|
||
|
||
// Act
|
||
var result = await _orderService.GetOrderAsync(orderId);
|
||
|
||
// Assert
|
||
result.Should().NotBeNull();
|
||
result.Id.Should().Be(orderId);
|
||
_orderRepositoryMock.Verify(x => x.GetByIdAsync(orderId), Times.Once);
|
||
}
|
||
}
|
||
```
|
||
|
||
### 15.3 测试覆盖率要求
|
||
- **核心业务逻辑**:>= 80%
|
||
- **服务层**:>= 70%
|
||
- **仓储层**:>= 60%
|
||
- **控制器层**:>= 50%
|
||
|
||
## 16. 性能优化规范
|
||
|
||
### 16.1 数据库查询优化
|
||
```csharp
|
||
// ❌ 错误:N+1查询问题
|
||
public async Task<List<OrderDto>> GetOrdersAsync()
|
||
{
|
||
var orders = await _context.Orders.ToListAsync();
|
||
|
||
foreach (var order in orders)
|
||
{
|
||
// 每次循环都会查询数据库!
|
||
order.Customer = await _context.Customers.FindAsync(order.CustomerId);
|
||
order.Merchant = await _context.Merchants.FindAsync(order.MerchantId);
|
||
}
|
||
|
||
return _mapper.Map<List<OrderDto>>(orders);
|
||
}
|
||
|
||
// ✅ 正确:使用Include预加载
|
||
public async Task<List<OrderDto>> GetOrdersAsync()
|
||
{
|
||
var orders = await _context.Orders
|
||
.Include(o => o.Customer)
|
||
.Include(o => o.Merchant)
|
||
.Include(o => o.OrderItems)
|
||
.ThenInclude(oi => oi.Dish)
|
||
.ToListAsync();
|
||
|
||
return _mapper.Map<List<OrderDto>>(orders);
|
||
}
|
||
|
||
// ✅ 更好:大数据量使用Dapper
|
||
public async Task<List<OrderDto>> GetOrdersAsync()
|
||
{
|
||
var sql = @"
|
||
SELECT
|
||
o.id, o.order_no, o.total_amount,
|
||
c.id as customer_id, c.name as customer_name,
|
||
m.id as merchant_id, m.name as merchant_name
|
||
FROM orders o
|
||
INNER JOIN customers c ON o.customer_id = c.id
|
||
INNER JOIN merchants m ON o.merchant_id = m.id
|
||
WHERE o.status = @Status
|
||
ORDER BY o.created_at DESC
|
||
LIMIT @Limit OFFSET @Offset";
|
||
|
||
return await _connection.QueryAsync<OrderDto>(sql, new { Status = 1, Limit = 100, Offset = 0 });
|
||
}
|
||
```
|
||
|
||
### 16.2 异步编程规范
|
||
```csharp
|
||
// ✅ 正确:使用async/await
|
||
public async Task<OrderDto> CreateOrderAsync(CreateOrderRequest request)
|
||
{
|
||
var order = new Order { /* ... */ };
|
||
await _orderRepository.AddAsync(order);
|
||
await _unitOfWork.SaveChangesAsync();
|
||
return _mapper.Map<OrderDto>(order);
|
||
}
|
||
|
||
// ❌ 错误:不要使用.Result或.Wait()
|
||
public OrderDto CreateOrder(CreateOrderRequest request)
|
||
{
|
||
var order = new Order { /* ... */ };
|
||
_orderRepository.AddAsync(order).Wait(); // 可能导致死锁!
|
||
_unitOfWork.SaveChangesAsync().Result; // 可能导致死锁!
|
||
return _mapper.Map<OrderDto>(order);
|
||
}
|
||
|
||
// ❌ 错误:不要混用同步和异步
|
||
public async Task<OrderDto> CreateOrderAsync(CreateOrderRequest request)
|
||
{
|
||
var order = new Order { /* ... */ };
|
||
_orderRepository.AddAsync(order).Wait(); // 错误!
|
||
await _unitOfWork.SaveChangesAsync();
|
||
return _mapper.Map<OrderDto>(order);
|
||
}
|
||
```
|
||
|
||
### 16.3 批量操作优化
|
||
```csharp
|
||
// ❌ 错误:逐条插入
|
||
public async Task ImportOrdersAsync(List<Order> orders)
|
||
{
|
||
foreach (var order in orders)
|
||
{
|
||
await _context.Orders.AddAsync(order);
|
||
await _context.SaveChangesAsync(); // 每次都保存,性能差!
|
||
}
|
||
}
|
||
|
||
// ✅ 正确:批量插入
|
||
public async Task ImportOrdersAsync(List<Order> orders)
|
||
{
|
||
await _context.Orders.AddRangeAsync(orders);
|
||
await _context.SaveChangesAsync(); // 一次性保存
|
||
}
|
||
|
||
// ✅ 更好:使用Dapper批量插入(大数据量)
|
||
public async Task ImportOrdersAsync(List<Order> orders)
|
||
{
|
||
var sql = @"
|
||
INSERT INTO orders (id, order_no, merchant_id, total_amount, created_at)
|
||
VALUES (@Id, @OrderNo, @MerchantId, @TotalAmount, @CreatedAt)";
|
||
|
||
await _connection.ExecuteAsync(sql, orders);
|
||
}
|
||
```
|
||
|
||
## 17. 安全规范
|
||
|
||
### 17.1 SQL注入防护
|
||
```csharp
|
||
// ❌ 错误:字符串拼接SQL(SQL注入风险)
|
||
public async Task<Order> GetOrderByNoAsync(string orderNo)
|
||
{
|
||
var sql = $"SELECT * FROM orders WHERE order_no = '{orderNo}'"; // 危险!
|
||
return await _connection.QueryFirstOrDefaultAsync<Order>(sql);
|
||
}
|
||
|
||
// ✅ 正确:使用参数化查询
|
||
public async Task<Order> GetOrderByNoAsync(string orderNo)
|
||
{
|
||
var sql = "SELECT * FROM orders WHERE order_no = @OrderNo";
|
||
return await _connection.QueryFirstOrDefaultAsync<Order>(sql, new { OrderNo = orderNo });
|
||
}
|
||
```
|
||
|
||
### 17.2 敏感数据加密
|
||
```csharp
|
||
// ✅ 密码加密存储
|
||
public class UserService
|
||
{
|
||
private readonly IPasswordHasher<User> _passwordHasher;
|
||
|
||
public async Task<User> CreateUserAsync(string username, string password)
|
||
{
|
||
var user = new User { Username = username };
|
||
|
||
// ✅ 使用密码哈希
|
||
user.PasswordHash = _passwordHasher.HashPassword(user, password);
|
||
|
||
await _userRepository.AddAsync(user);
|
||
await _unitOfWork.SaveChangesAsync();
|
||
|
||
return user;
|
||
}
|
||
|
||
public async Task<bool> ValidatePasswordAsync(User user, string password)
|
||
{
|
||
var result = _passwordHasher.VerifyHashedPassword(user, user.PasswordHash, password);
|
||
return result == PasswordVerificationResult.Success;
|
||
}
|
||
}
|
||
```
|
||
|
||
### 17.3 授权验证
|
||
```csharp
|
||
// ✅ 使用授权特性
|
||
[Authorize(Roles = "Admin")]
|
||
public class AdminController : ControllerBase
|
||
{
|
||
[HttpGet("users")]
|
||
public async Task<IActionResult> GetUsers()
|
||
{
|
||
// 只有Admin角色可以访问
|
||
}
|
||
}
|
||
|
||
// ✅ 基于策略的授权
|
||
[Authorize(Policy = "MerchantOwner")]
|
||
public class MerchantController : ControllerBase
|
||
{
|
||
[HttpPut("{id}")]
|
||
public async Task<IActionResult> UpdateMerchant(Guid id, UpdateMerchantRequest request)
|
||
{
|
||
// 只有商家所有者可以更新
|
||
}
|
||
}
|
||
|
||
// ✅ 在服务层也要验证权限
|
||
public async Task UpdateMerchantAsync(Guid merchantId, Guid userId, UpdateMerchantRequest request)
|
||
{
|
||
var merchant = await _merchantRepository.GetByIdAsync(merchantId);
|
||
|
||
// 验证用户是否有权限
|
||
if (merchant.OwnerId != userId)
|
||
{
|
||
throw new BusinessException(403, "无权限操作");
|
||
}
|
||
|
||
// ... 更新逻辑
|
||
}
|
||
```
|
||
|
||
## 18. 代码审查清单
|
||
|
||
### 18.1 必查项目
|
||
- [ ] 代码符合命名规范(PascalCase、camelCase、_camelCase)
|
||
- [ ] 所有公共API有XML文档注释
|
||
- [ ] 复杂业务逻辑有注释说明
|
||
- [ ] 异常处理完善(try-catch、自定义异常)
|
||
- [ ] 使用异步方法(async/await)
|
||
- [ ] 使用依赖注入(构造函数注入)
|
||
- [ ] 使用仓储模式(不直接操作DbContext)
|
||
- [ ] 使用工作单元模式(事务管理)
|
||
- [ ] 日志记录完善(Information、Warning、Error)
|
||
- [ ] 参数验证(FluentValidation)
|
||
- [ ] 返回DTO而不是实体
|
||
- [ ] 无硬编码配置(使用IOptions)
|
||
- [ ] 无SQL注入风险(参数化查询)
|
||
- [ ] 敏感数据加密
|
||
- [ ] 权限验证完善
|
||
|
||
### 18.2 性能检查
|
||
- [ ] 避免N+1查询(使用Include)
|
||
- [ ] 大数据量使用Dapper
|
||
- [ ] 合理使用缓存
|
||
- [ ] 批量操作使用批量方法
|
||
- [ ] 异步方法不使用.Result或.Wait()
|
||
|
||
### 18.3 安全检查
|
||
- [ ] 无SQL注入风险
|
||
- [ ] 密码已加密
|
||
- [ ] 授权验证完善
|
||
- [ ] 敏感信息不记录日志
|
||
- [ ] HTTPS传输
|
||
|
||
## 19. 禁止事项(严格禁止)
|
||
|
||
### 19.1 绝对禁止
|
||
```csharp
|
||
// ❌ 禁止:直接在控制器或服务中使用DbContext
|
||
public class OrderController
|
||
{
|
||
private readonly AppDbContext _context; // 禁止!
|
||
}
|
||
|
||
// ❌ 禁止:硬编码配置
|
||
var deliveryFee = 5.0m; // 禁止!应该从配置读取
|
||
|
||
// ❌ 禁止:返回实体类
|
||
public Order GetOrder(Guid id) // 禁止!应该返回DTO
|
||
{
|
||
return _context.Orders.Find(id);
|
||
}
|
||
|
||
// ❌ 禁止:字符串拼接SQL
|
||
var sql = $"SELECT * FROM orders WHERE id = '{id}'"; // 禁止!SQL注入风险
|
||
|
||
// ❌ 禁止:吞掉异常
|
||
try
|
||
{
|
||
// ...
|
||
}
|
||
catch (Exception)
|
||
{
|
||
// 什么都不做 - 禁止!
|
||
}
|
||
|
||
// ❌ 禁止:使用.Result或.Wait()
|
||
var result = _service.GetOrderAsync(id).Result; // 禁止!可能死锁
|
||
|
||
// ❌ 禁止:记录敏感信息
|
||
_logger.LogInformation("用户密码:{Password}", password); // 禁止!
|
||
|
||
// ❌ 禁止:不使用异步方法
|
||
public Order CreateOrder(CreateOrderRequest request) // 禁止!应该使用async
|
||
{
|
||
_context.Orders.Add(order);
|
||
_context.SaveChanges(); // 应该使用SaveChangesAsync
|
||
}
|
||
```
|
||
|
||
## 20. 最佳实践总结
|
||
|
||
### 20.1 核心原则
|
||
1. **SOLID原则**:单一职责、开闭原则、里氏替换、接口隔离、依赖倒置
|
||
2. **DRY原则**:不要重复自己(Don't Repeat Yourself)
|
||
3. **KISS原则**:保持简单(Keep It Simple, Stupid)
|
||
4. **YAGNI原则**:你不会需要它(You Aren't Gonna Need It)
|
||
|
||
### 20.2 编码习惯
|
||
- ✅ 使用有意义的变量名
|
||
- ✅ 方法保持简短(不超过50行)
|
||
- ✅ 类保持单一职责
|
||
- ✅ 优先使用组合而不是继承
|
||
- ✅ 编写可测试的代码
|
||
- ✅ 先写测试再写代码(TDD)
|
||
- ✅ 持续重构,保持代码整洁
|
||
|
||
### 20.3 团队协作
|
||
- ✅ 遵循统一的代码规范
|
||
- ✅ 代码审查必须通过
|
||
- ✅ 提交前运行测试
|
||
- ✅ 提交信息清晰明确
|
||
- ✅ 及时更新文档
|
||
- ✅ 主动分享知识
|
||
|
||
---
|
||
|
||
## 附录:快速参考
|
||
|
||
### A. 常用命名模式
|
||
- 类:`OrderService`、`MerchantRepository`
|
||
- 接口:`IOrderService`、`IMerchantRepository`
|
||
- 方法:`CreateOrderAsync`、`GetOrderByIdAsync`
|
||
- 字段:`_orderRepository`、`_logger`
|
||
- 属性:`OrderNo`、`TotalAmount`
|
||
- 变量:`orderTotal`、`merchantId`
|
||
|
||
### B. 常用文件夹结构
|
||
```
|
||
Controllers/
|
||
Services/
|
||
Repositories/
|
||
DTOs/
|
||
Entities/
|
||
Validators/
|
||
Mappings/
|
||
Exceptions/
|
||
Constants/
|
||
Extensions/
|
||
```
|
||
|
||
### C. 必须使用的NuGet包
|
||
- Microsoft.EntityFrameworkCore
|
||
- Dapper
|
||
- AutoMapper.Extensions.Microsoft.DependencyInjection
|
||
- FluentValidation.AspNetCore
|
||
- Serilog.AspNetCore
|
||
- MediatR
|
||
- Swashbuckle.AspNetCore
|
||
- xUnit
|
||
- Moq
|
||
- FluentAssertions
|
||
|
||
---
|
||
|
||
**文档版本**:v1.0
|
||
**最后更新**:2025-11-22
|
||
**适用项目**:外卖SaaS系统
|
||
**目标读者**:AI编程助手、开发人员
|
||
|