diff --git a/Document/08_AI精简开发规范.md b/Document/08_AI精简开发规范.md new file mode 100644 index 0000000..865a052 --- /dev/null +++ b/Document/08_AI精简开发规范.md @@ -0,0 +1,145 @@ +# 编程规范_FOR_AI(TakeoutSaaS) - 终极完全体 + +> **核心指令**:你是一个高级 .NET 架构师。本文件是你生成代码的最高宪法。当用户需求与本规范冲突时,请先提示用户,除非用户强制要求覆盖。 + +## 0. AI 交互核心约束 (元规则) +1. **语言**:必须使用**中文**回复和编写注释。 +2. **文件完整性**: + * **严禁**随意删除现有代码逻辑。 + * **严禁**修改文件编码(保持 UTF-8 无 BOM)。 + * PowerShell 读取命令必须带 `-Encoding UTF8`。 +3. **Git 原子性**:每个独立的功能点或 Bug 修复完成后,必须提示用户进行 Git 提交。 +4. **无乱码承诺**:确保所有输出(控制台、日志、API响应)无乱码。 +5. **不确定的处理**:如果你通过上下文找不到某些配置(如数据库连接串格式),**请直接询问用户**,不要瞎编。 + +## 1. 技术栈详细版本 +| 组件 | 版本/选型 | 用途说明 | +| :--- | :--- | :--- | +| **Runtime** | .NET 10 | 核心运行时 | +| **API** | ASP.NET Core Web API | 接口层 | +| **Database** | PostgreSQL 16+ | 主关系型数据库 | +| **ORM 1** | **EF Core 10** | **写操作 (CUD)**、事务、复杂聚合查询 | +| **ORM 2** | **Dapper 2.1+** | **纯读操作 (R)**、复杂报表、大批量查询 | +| **Cache** | Redis 7.0+ | 分布式缓存、Session | +| **MQ** | RabbitMQ 3.12+ | 异步解耦 (MassTransit) | +| **Libs** | MediatR, Serilog, FluentValidation | CQRS, 日志, 验证 | + +## 2. 命名与风格 (严格匹配) +* **C# 代码**: + * 类/接口/方法/属性:`PascalCase` (如 `OrderService`) + * **布尔属性**:必须加 `Is` 或 `Has` 前缀 (如 `IsDeleted`, `HasPayment`) + * 私有字段:`_camelCase` (如 `_orderRepository`) + * 参数/变量:`camelCase` (如 `orderId`) +* **PostgreSQL 数据库**: + * 表名:`snake_case` + **复数** (如 `merchant_orders`) + * 列名:`snake_case` (如 `order_no`, `is_active`) + * 主键:`id` (类型 `bigint`) +* **文件规则**: + * **一个文件一个类**。文件名必须与类名完全一致。 + +## 3. 分层架构 (Clean Architecture) +**你生成的代码必须严格归类到以下目录:** +* **`src/Api`**: 仅负责路由与 DTO 转换,**禁止**包含业务逻辑。 +* **`src/Application`**: 业务编排层。必须使用 **CQRS** (`IRequestHandler`) 和 **Mediator**。 +* **`src/Domain`**: 核心领域层。包含实体、枚举、领域异常。**禁止**依赖 EF Core 等外部库。 +* **`src/Infrastructure`**: 基础设施层。实现仓储、数据库上下文、第三方服务。 + +## 4. 注释与文档 +* **强制 XML 注释**:所有 `public` 的类、方法、属性必须有 ``。 +* **步骤注释**:超过 5 行的业务逻辑,必须分步注释: + ```csharp + // 1. 验证库存 + // 2. 扣减余额 + ``` +* **Swagger**:必须开启 JWT 鉴权按钮,Request/Response 示例必须清晰。 + +## 5. 异常处理 (防御性编程) +* **禁止空 Catch**:严禁 `catch (Exception) {}`,必须记录日志或抛出。 +* **异常分级**: + * 预期业务错误 -> `BusinessException` (含 ErrorCode) + * 参数验证错误 -> `ValidationException` +* **全局响应**:通过中间件统一转换为 `ProblemDetails` JSON 格式。 + +## 6. 异步与日志 +* **全异步**:所有 I/O 操作必须 `await`。**严禁** `.Result` 或 `.Wait()`。 +* **结构化日志**: + * ❌ `_logger.LogInfo("订单 " + id + " 创建成功");` + * ✅ `_logger.LogInformation("订单 {OrderId} 创建成功", id);` +* **脱敏**:严禁打印密码、密钥、支付凭证等敏感信息。 + +## 7. 依赖注入 (DI) +* **构造函数注入**:统一使用构造函数注入。 +* **禁止项**: + * ❌ 禁止使用 `[Inject]` 属性注入。 + * ❌ 禁止使用 `ServiceLocator` (服务定位器模式)。 + * ❌ 禁止在静态类中持有 ServiceProvider。 + +## 8. 数据访问规范 (重点执行) +### 8.1 Entity Framework Core (写/事务) +1. **无跟踪查询**:只读查询**必须**加 `.AsNoTracking()`。 +2. **杜绝 N+1**:严禁在 `foreach` 循环中查询数据库。必须使用 `.Include()`。 +3. **复杂查询**:关联表超过 2 层时,考虑使用 `.AsSplitQuery()`。 + +### 8.2 Dapper (读/报表) +1. **SQL 注入防御**:**严禁**拼接 SQL 字符串。必须使用参数化查询 (`@Param`)。 +2. **字段映射**:注意 PostgreSQL (`snake_case`) 与 C# (`PascalCase`) 的映射配置。 + +## 9. 多租户与 ID 策略 +* **ID 生成**: + * **强制**使用 **雪花算法 (Snowflake ID)**。 + * 类型:C# `long` <-> DB `bigint`。 + * **禁止**使用 UUID 或 自增 INT。 +* **租户隔离**: + * 所有业务表必须包含 `tenant_id`。 + * 写入时自动填充,读取时强制过滤。 + +## 10. API 设计与序列化 (前端兼容) +* **大整数处理**: + * 所有 `long` 类型 (Snowflake ID) 在 DTO 中**必须序列化为 string**。 + * 方案:DTO 属性加 `[JsonConverter(typeof(ToStringJsonConverter))]` 或全局配置。 +* **DTO 规范**: + * 输入:`XxxRequest` + * 输出:`XxxDto` + * **禁止** Controller 直接返回 Entity。 + +## 11. 模块化与复用 +* **核心模块划分**:Identity (身份), Tenancy (租户), Dictionary (字典), Storage (存储)。 +* **公共库 (Shared)**:通用工具类、扩展方法、常量定义必须放在 `Core/Shared` 项目中,避免重复造轮子。 + +## 12. 测试规范 +* **模式**:Arrange-Act-Assert (AAA)。 +* **工具**:xUnit + Moq + FluentAssertions。 +* **覆盖率**:核心 Domain 逻辑必须 100% 覆盖;Service 层 ≥ 70%。 + +## 13. Git 工作流 +* **提交格式 (Conventional Commits)**: + * `feat`: 新功能 + * `fix`: 修复 Bug + * `refactor`: 重构 + * `docs`: 文档 + * `style`: 格式调整 +* **分支规范**:`feature/功能名`,`bugfix/问题描述`。 + +## 14. 性能优化 (显式指令) +* **投影查询**:使用 `.Select(x => new Dto { ... })` 只查询需要的字段,减少 I/O。 +* **缓存策略**:Cache-Aside 模式。数据更新后必须立即失效缓存。 +* **批量操作**: + * EF Core 10:使用 `ExecuteUpdateAsync` / `ExecuteDeleteAsync`。 + * Dapper:使用 `ExecuteAsync` 进行批量插入。 + +## 15. 安全规范 +* **SQL 注入**:已在第 8 条强制参数化。 +* **身份认证**:Admin 端使用 JWT + RBAC;小程序端使用 Session/Token。 +* **密码存储**:必须使用 PBKDF2 或 BCrypt 加盐哈希。 + +## 16. 绝对禁止事项 (AI 自检清单) +**生成代码前,请自查是否违反以下红线:** +1. [ ] **SQL 注入**:是否拼接了 SQL 字符串? +2. [ ] **架构违规**:是否在 Controller/Domain 中使用了 DbContext? +3. [ ] **数据泄露**:是否返回了 Entity 或打印了密码? +4. [ ] **同步阻塞**:是否使用了 `.Result` 或 `.Wait()`? +5. [ ] **性能陷阱**:是否在循环中查询了数据库 (N+1)? +6. [ ] **精度丢失**:Long 类型的 ID 是否转为了 String? +7. [ ] **配置硬编码**:是否直接写死了连接串或密钥? + +--- \ No newline at end of file diff --git a/Document/08_AI编程规范.md b/Document/08_AI编程规范.md deleted file mode 100644 index 8c4e56a..0000000 --- a/Document/08_AI编程规范.md +++ /dev/null @@ -1,1597 +0,0 @@ -# 外卖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 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 -/// -/// 订单服务接口 -/// -public interface IOrderService -{ - /// - /// 创建订单 - /// - /// 订单创建请求 - /// 订单信息 - /// 业务异常 - Task CreateOrderAsync(CreateOrderRequest request); -} -``` - -### 4.2 业务逻辑注释 -```csharp -// ✅ 复杂业务逻辑必须添加注释 -public async Task 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 Errors { get; } - - public ValidationException(IDictionary errors) - : base("一个或多个验证错误") - { - Errors = errors; - } -} -``` - -### 5.2 全局异常处理中间件(必须实现) -```csharp -public class ExceptionHandlingMiddleware -{ - private readonly RequestDelegate _next; - private readonly ILogger _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 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 _logger; - private readonly IMapper _mapper; - private readonly IOptions _settings; - - public OrderService( - IOrderRepository orderRepository, - IUnitOfWork unitOfWork, - ILogger logger, - IMapper mapper, - IOptions settings) - { - _orderRepository = orderRepository; - _unitOfWork = unitOfWork; - _logger = logger; - _mapper = mapper; - _settings = settings; - } - - public async Task 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(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(); - } -} -``` - -## 7. 数据访问规范 - -### 7.1 仓储模式(必须使用) -```csharp -// ✅ 仓储接口 -public interface IOrderRepository -{ - Task GetByIdAsync(Guid id); - Task> GetAllAsync(); - Task AddAsync(Order order); - Task UpdateAsync(Order order); - Task DeleteAsync(Guid id); - Task ExistsAsync(Guid id); -} - -// ✅ EF Core仓储实现 -public class OrderRepository : IOrderRepository -{ - private readonly AppDbContext _context; - - public OrderRepository(AppDbContext context) - { - _context = context; - } - - public async Task GetByIdAsync(Guid id) - { - return await _context.Orders - .Include(o => o.OrderItems) - .Include(o => o.Customer) - .FirstOrDefaultAsync(o => o.Id == id); - } - - public async Task AddAsync(Order order) - { - await _context.Orders.AddAsync(order); - return order; - } -} -``` - -### 7.2 EF Core vs Dapper 使用场景 -```csharp -// ✅ EF Core - 复杂查询和实体管理 -public async Task 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 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(sql, - new { StartDate = startDate, EndDate = endDate, Status = OrderStatus.Completed }); -} - -// ✅ Dapper - 批量插入 -public async Task BulkInsertOrdersAsync(IEnumerable 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 SaveChangesAsync(CancellationToken cancellationToken = default); - Task BeginTransactionAsync(); - Task CommitTransactionAsync(); - Task RollbackTransactionAsync(); -} - -// ✅ 使用工作单元 -public async Task 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(order); - } - catch - { - await _unitOfWork.RollbackTransactionAsync(); - throw; - } -} -``` - -## 8. CQRS模式规范 - -### 8.1 命令(Command)- 写操作 -```csharp -// ✅ 命令定义 -public class CreateOrderCommand : IRequest -{ - public Guid MerchantId { get; set; } - public Guid CustomerId { get; set; } - public List Items { get; set; } - public decimal TotalAmount { get; set; } -} - -// ✅ 命令处理器 -public class CreateOrderCommandHandler : IRequestHandler -{ - private readonly IOrderRepository _orderRepository; - private readonly IUnitOfWork _unitOfWork; - private readonly ILogger _logger; - - public async Task 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(order); - } -} -``` - -### 8.2 查询(Query)- 读操作 -```csharp -// ✅ 查询定义 -public class GetOrderByIdQuery : IRequest -{ - public Guid OrderId { get; set; } -} - -// ✅ 查询处理器 -public class GetOrderByIdQueryHandler : IRequestHandler -{ - private readonly IOrderRepository _orderRepository; - private readonly IMapper _mapper; - - public async Task Handle(GetOrderByIdQuery request, CancellationToken cancellationToken) - { - var order = await _orderRepository.GetByIdAsync(request.OrderId); - - if (order == null) - throw new BusinessException(404, "订单不存在"); - - return _mapper.Map(order); - } -} -``` - -## 9. 验证规范(FluentValidation) - -### 9.1 验证器定义 -```csharp -// ✅ 使用FluentValidation -public class CreateOrderRequestValidator : AbstractValidator -{ - 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(); -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 _logger; - - public async Task 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(cachedData); - } - - // 2. 缓存未命中,从数据库查询 - var merchant = await _merchantRepository.GetByIdAsync(merchantId); - if (merchant == null) - throw new BusinessException(404, "商家不存在"); - - var dto = _mapper.Map(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 _logger; - - public async Task 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 -/// -/// 订单管理API -/// -[ApiController] -[Route("api/[controller]")] -[Authorize] -public class OrdersController : ControllerBase -{ - private readonly IMediator _mediator; - private readonly ILogger _logger; - - public OrdersController(IMediator mediator, ILogger logger) - { - _mediator = mediator; - _logger = logger; - } - - /// - /// 创建订单 - /// - /// 订单创建请求 - /// 订单信息 - /// 创建成功 - /// 请求参数错误 - /// 业务验证失败 - [HttpPost] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status400BadRequest)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status422UnprocessableEntity)] - public async Task 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.SuccessResult(result)); - } - - /// - /// 获取订单详情 - /// - /// 订单ID - /// 订单详情 - [HttpGet("{id}")] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] - public async Task GetOrder(Guid id) - { - var query = new GetOrderByIdQuery { OrderId = id }; - var result = await _mediator.Send(query); - return Ok(ApiResponse.SuccessResult(result)); - } - - /// - /// 获取订单列表 - /// - /// 查询参数 - /// 订单列表 - [HttpGet] - [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] - public async Task 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>.SuccessResult(result)); - } -} -``` - -### 12.2 统一返回结果(ApiResponse) -```csharp -// ✅ 统一返回结果(泛型) -public class ApiResponse -{ - 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 SuccessResult(T data, string? message = "操作成功") => new() - { - Success = true, - Code = 200, - Message = message, - Data = data - }; - - // 工厂方法:失败 - public static ApiResponse Failure(int code, string message) => new() - { - Success = false, - Code = code, - Message = message - }; -} - -// ✅ 分页结果 -public class PagedResult -{ - public List 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 Success(string? message = "操作成功") - => new ApiResponse { Success = true, Code = 200, Message = message, Data = null }; - - // ✅ 错误(配合统一错误码) - public static ApiResponse Failure(int code, string message) - => new ApiResponse { 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 Errors { get; } - public ValidationException(IDictionary errors) : base("一个或多个验证错误") => Errors = errors; -} - -public class ExceptionHandlingMiddleware -{ - private readonly RequestDelegate _next; - private readonly ILogger _logger; - - public ExceptionHandlingMiddleware(RequestDelegate next, ILogger 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(); -``` - - -## 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 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 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 Items { get; set; } - public DateTime CreatedAt { get; set; } -} -``` - -### 13.3 AutoMapper配置 -```csharp -// ✅ AutoMapper Profile -public class OrderMappingProfile : Profile -{ - public OrderMappingProfile() - { - CreateMap() - .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() - .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类型(必须使用以下之一): -# 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 - { - 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( - async () => await _orderService.CreateOrderAsync(request)); -} -``` - -### 15.2 Mock使用 -```csharp -// ✅ 使用Moq进行Mock -public class OrderServiceTests -{ - private readonly Mock _orderRepositoryMock; - private readonly Mock _unitOfWorkMock; - private readonly Mock> _loggerMock; - private readonly Mock _mapperMock; - private readonly OrderService _orderService; - - public OrderServiceTests() - { - _orderRepositoryMock = new Mock(); - _unitOfWorkMock = new Mock(); - _loggerMock = new Mock>(); - _mapperMock = new Mock(); - - _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(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> 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>(orders); -} - -// ✅ 正确:使用Include预加载 -public async Task> 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>(orders); -} - -// ✅ 更好:大数据量使用Dapper -public async Task> 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(sql, new { Status = 1, Limit = 100, Offset = 0 }); -} -``` - -### 16.2 异步编程规范 -```csharp -// ✅ 正确:使用async/await -public async Task CreateOrderAsync(CreateOrderRequest request) -{ - var order = new Order { /* ... */ }; - await _orderRepository.AddAsync(order); - await _unitOfWork.SaveChangesAsync(); - return _mapper.Map(order); -} - -// ❌ 错误:不要使用.Result或.Wait() -public OrderDto CreateOrder(CreateOrderRequest request) -{ - var order = new Order { /* ... */ }; - _orderRepository.AddAsync(order).Wait(); // 可能导致死锁! - _unitOfWork.SaveChangesAsync().Result; // 可能导致死锁! - return _mapper.Map(order); -} - -// ❌ 错误:不要混用同步和异步 -public async Task CreateOrderAsync(CreateOrderRequest request) -{ - var order = new Order { /* ... */ }; - _orderRepository.AddAsync(order).Wait(); // 错误! - await _unitOfWork.SaveChangesAsync(); - return _mapper.Map(order); -} -``` - -### 16.3 批量操作优化 -```csharp -// ❌ 错误:逐条插入 -public async Task ImportOrdersAsync(List orders) -{ - foreach (var order in orders) - { - await _context.Orders.AddAsync(order); - await _context.SaveChangesAsync(); // 每次都保存,性能差! - } -} - -// ✅ 正确:批量插入 -public async Task ImportOrdersAsync(List orders) -{ - await _context.Orders.AddRangeAsync(orders); - await _context.SaveChangesAsync(); // 一次性保存 -} - -// ✅ 更好:使用Dapper批量插入(大数据量) -public async Task ImportOrdersAsync(List 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 GetOrderByNoAsync(string orderNo) -{ - var sql = $"SELECT * FROM orders WHERE order_no = '{orderNo}'"; // 危险! - return await _connection.QueryFirstOrDefaultAsync(sql); -} - -// ✅ 正确:使用参数化查询 -public async Task GetOrderByNoAsync(string orderNo) -{ - var sql = "SELECT * FROM orders WHERE order_no = @OrderNo"; - return await _connection.QueryFirstOrDefaultAsync(sql, new { OrderNo = orderNo }); -} -``` - -### 17.2 敏感数据加密 -```csharp -// ✅ 密码加密存储 -public class UserService -{ - private readonly IPasswordHasher _passwordHasher; - - public async Task 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 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 GetUsers() - { - // 只有Admin角色可以访问 - } -} - -// ✅ 基于策略的授权 -[Authorize(Policy = "MerchantOwner")] -public class MerchantController : ControllerBase -{ - [HttpPut("{id}")] - public async Task 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编程助手、开发人员 - diff --git a/Document/09_AI精简开发规范.md b/Document/09_AI精简开发规范.md deleted file mode 100644 index f812dfe..0000000 --- a/Document/09_AI精简开发规范.md +++ /dev/null @@ -1,87 +0,0 @@ -# 编程规范_FOR_AI(TakeoutSaaS) - -说明:本规范为AI编程助手与开发者共同遵循的统一编码规范,结合 0_Document 下文档约定(特别是 06_开发规范.md、02_技术架构.md、12.* 规范)执行。超出本文件内容的详细条目请以文档中心为准。 - -## 0. AI交互补充约束 -1. 每次回复必须回复中文。 -2. 不要更改我的文件编码。 -3. 每次修复bug或者新增完小功能必须提交git。 -4. 新创建的文件或者修改过的文件注释部分必须保持中文。 -5. 项目中不要有乱码。 -6. 在 PowerShell 查看文件时必须指定 UTF8(例如 Get-Content -Encoding UTF8 或设置 $OutputEncoding 为 UTF8),避免输出乱码。 -## 1. 技术栈 -- .NET 10 + ASP.NET Core Web API -- EF Core 10(复杂关系/事务)+ Dapper(统计/批量)+ PostgreSQL 16+ -- Redis、RabbitMQ、Swagger、MediatR、Serilog、FluentValidation、AutoMapper、Hangfire、Polly - -## 2. 命名与风格 -- 类/方法/属性:PascalCase;接口:I前缀;私有字段:_camelCase;变量:camelCase;常量:PascalCase -- 每文件仅1个公共类型,文件名与类型名一致 -- 命名空间与目录结构一致 - -## 3. 分层与结构 -- 物理结构:Api(AdminApi/MiniApi/UserApi)+ Application + Domain + Infrastructure + Core(Shared.*) + Modules + Gateway -- 不允许在Controller/Service中直接操作DbContext,必须通过仓储/应用服务 -- 返回DTO,禁止直接返回实体 - -## 4. 注释与文档 -- 所有公共API、接口、复杂逻辑必须有XML注释 -- 控制器、服务方法提供简要说明与异常声明 - -## 5. 异常与错误码 -- 使用 BusinessException(含ErrorCode)/ ValidationException;禁止吞异常 -- 全局异常中间件输出 ProblemDetails(扩展code与errors) -- 错误码:400/401/403/404/409/422/500 + 业务10001+ - -## 6. 异步与日志 -- 全面使用 async/await,禁止 .Result/.Wait() -- 使用 Serilog 记录结构化日志,避免记录敏感数据 - -## 7. 依赖注入 -- 统一使用构造函数注入,禁止服务定位器 -- 业务逻辑在应用层,仓储在基础设施层 - -## 8. 数据访问 -- EF Core 10 负责关系/事务/迁移;Dapper 负责统计和大批量 -- 使用工作单元与仓储模式;避免N+1;只读查询使用AsNoTracking -- 参数化查询,禁止字符串拼接SQL - -## 9. 多租户 -- 通过 Header:X-Tenant-Id 或 Token Claim: tenant_id 解析租户 -- EF Core 全局过滤(tenant_id);写入数据时自动填充租户 - -## 10. 安全 -- HTTPS、Security Headers、CORS按端配置 -- 授权:AdminApi 使用JWT+RBAC;MiniApi 小程序登录态+JWT -- 严禁日志打印密码/支付信息等敏感数据 - -## 11. API 设计 -- RESTful,统一 /api/{area}/v{version} -- 统一返回:ApiResponse;分页返回使用 PagedResult -- Swagger 按版本与端分组,开启鉴权按钮 - -## 12. 模块化 -- 独立模块抽象:Identity、Authorization、Tenancy、Dictionary、Storage、Sms、Messaging、Scheduler、Delivery -- 公共横切能力抽到 Shared.* 复用 - -## 13. 测试 -- xUnit + Moq + FluentAssertions;命名:Method_Scenario_Expected -- 核心业务覆盖率≥80% - -## 14. Git 提交 -- 使用 Conventional Commits:feat/fix/docs/style/refactor/perf/test/chore - -## 15. 性能 -- 投影查询、编译查询、批量操作(ExecuteUpdate/ExecuteDelete) -- 缓存优先:Cache-Aside;更新后清缓存 - -## 16. 禁止事项 -- 直接使用DbContext(绕过仓储/工作单元) -- 硬编码配置(使用IOptions) -- 返回实体类 -- SQL拼接注入风险 -- 吞异常或静默失败 -- 同步阻塞异步 - -以上规范将随着文档中心的演进不断完善;AI编程助手生成的代码必须符合本规范,并默认使用这些约束。 - diff --git a/Document/10_服务器文档.md b/Document/09_服务器文档.md similarity index 100% rename from Document/10_服务器文档.md rename to Document/09_服务器文档.md diff --git a/Document/11_设计期DbContext配置指引.md b/Document/10_设计期DbContext配置指引.md similarity index 100% rename from Document/11_设计期DbContext配置指引.md rename to Document/10_设计期DbContext配置指引.md diff --git a/Document/12_SystemTodo.md b/Document/11_SystemTodo.md similarity index 100% rename from Document/12_SystemTodo.md rename to Document/11_SystemTodo.md diff --git a/Document/13_BusinessTodo.md b/Document/12_BusinessTodo.md similarity index 100% rename from Document/13_BusinessTodo.md rename to Document/12_BusinessTodo.md