From d9d4c7d844be0cf881a3bb34f7433d4d6d57af83 Mon Sep 17 00:00:00 2001 From: zpc Date: Tue, 3 Feb 2026 17:52:36 +0800 Subject: [PATCH] 32 --- .kiro/settings/mcp.json | 34 +- .kiro/steering/development-standards.md | 476 ++++++++++ .../1-编程规约/1.1-命名风格.md | 740 ++++++++++++++++ .../1-编程规约/1.2-代码风格.md | 824 ++++++++++++++++++ .../1-编程规约/1.3-注释规约.md | 476 ++++++++++ .../1.4-多线程与异步规范.md | 184 ++++ .../1-编程规约/1.5-OOP规范.md | 799 +++++++++++++++++ .../2-异常日志/2.1-日志规约.md | 45 + .../2-异常日志/2.2-异常处理.md | 122 +++ .../src/MiAssessment.Admin/appsettings.json | 12 +- .../src/MiAssessment.Api/appsettings.json | 22 +- 11 files changed, 3716 insertions(+), 18 deletions(-) create mode 100644 .kiro/steering/development-standards.md create mode 100644 docs/开发规范/1-编程规约/1.1-命名风格.md create mode 100644 docs/开发规范/1-编程规约/1.2-代码风格.md create mode 100644 docs/开发规范/1-编程规约/1.3-注释规约.md create mode 100644 docs/开发规范/1-编程规约/1.4-多线程与异步规范.md create mode 100644 docs/开发规范/1-编程规约/1.5-OOP规范.md create mode 100644 docs/开发规范/2-异常日志/2.1-日志规约.md create mode 100644 docs/开发规范/2-异常日志/2.2-异常处理.md diff --git a/.kiro/settings/mcp.json b/.kiro/settings/mcp.json index dda36b1..1cbd4f7 100644 --- a/.kiro/settings/mcp.json +++ b/.kiro/settings/mcp.json @@ -18,6 +18,38 @@ "create_design_system_rules", "whoami" ] + }, + "admin-sqlserver": { + "command": "node", + "args": [ + "C:/mcp/mssql-mcp-server/index.js" + ], + "env": { + "MSSQL_SERVER": "192.168.195.15", + "MSSQL_PORT": "1433", + "MSSQL_USER": "sa", + "MSSQL_PASSWORD": "Dbt@com@123", + "MSSQL_DATABASE": "MiAssessment_Admin" + }, + "autoApprove": [ + "execute_sql" + ] + }, + "api-sqlserver": { + "command": "node", + "args": [ + "C:/mcp/mssql-mcp-server/index.js" + ], + "env": { + "MSSQL_SERVER": "192.168.195.15", + "MSSQL_PORT": "1433", + "MSSQL_USER": "sa", + "MSSQL_PASSWORD": "Dbt@com@123", + "MSSQL_DATABASE": "MiAssessment_Business" + }, + "autoApprove": [ + "execute_sql" + ] } } -} +} \ No newline at end of file diff --git a/.kiro/steering/development-standards.md b/.kiro/steering/development-standards.md new file mode 100644 index 0000000..b6aa188 --- /dev/null +++ b/.kiro/steering/development-standards.md @@ -0,0 +1,476 @@ +# 学业邑规划 - 开发规范 + +本文档定义了 MiAssessment 项目的开发规范和编码标准,所有开发工作必须遵循这些规范。 + +## 一、技术栈 + +| 层级 | 技术 | +|---|---| +| 后端框架 | .NET 10 Web API (C#) | +| 数据库 | SQL Server 2022 | +| ORM | Entity Framework Core | +| 缓存 | Redis | +| 接口风格 | RPC 风格(仅 GET / POST 请求) | +| 前端 | UniApp + Vue 3 + TypeScript | + +## 二、项目结构 + +``` +server/MiAssessment/src/ +├── MiAssessment.Api/ # 小程序 API 接口 +├── MiAssessment.Admin/ # 后台管理系统基础模块 +├── MiAssessment.Admin.Business/ # 后台业务模块(主要开发区域) +├── MiAssessment.Core/ # 核心业务逻辑 +├── MiAssessment.Infrastructure/ # 基础设施 +└── MiAssessment.Model/ # 数据模型 +``` + +### Admin.Business 项目结构 + +``` +MiAssessment.Admin.Business/ +├── Controllers/ # 控制器(API 入口) +├── Services/ # 业务服务 +│ └── Interfaces/ # 服务接口 +├── Models/ # 请求/响应模型(DTO) +│ ├── Common/ # 通用模型 +│ └── {Module}/ # 各模块模型 +├── Entities/ # 数据库实体类 +├── Data/ # 数据库上下文 +└── Attributes/ # 自定义特性 +``` + +## 三、命名规范 + +### 3.1 数据库命名 + +| 类型 | 规范 | 示例 | +|---|---|---| +| 表名 | snake_case(小写下划线) | `users`, `assessment_records` | +| 字段名 | PascalCase | `UserId`, `CreateTime` | +| 主键 | `Id` (bigint 自增) | `Id` | +| 外键 | `{表名}Id` | `UserId`, `OrderId` | +| 时间字段 | `CreateTime`, `UpdateTime` | - | +| 状态字段 | `Status` | - | +| 软删除 | `IsDeleted` (bit) | - | + +### 3.2 C# 代码命名 + +| 类型 | 规范 | 示例 | +|---|---|---| +| 命名空间 | PascalCase | `MiAssessment.Admin.Business.Services` | +| 类名 | PascalCase | `UserService`, `OrderController` | +| 接口 | I + PascalCase | `IUserService`, `IOrderService` | +| 方法 | PascalCase + Async 后缀 | `GetUserListAsync`, `CreateOrderAsync` | +| 属性 | PascalCase | `UserId`, `CreateTime` | +| 私有字段 | _camelCase | `_userService`, `_dbContext` | +| 参数 | camelCase | `userId`, `request` | +| 常量 | PascalCase | `MaxPageSize`, `DefaultStatus` | + +### 3.3 文件命名 + +| 类型 | 规范 | 示例 | +|---|---|---| +| 实体类 | 单数形式 | `User.cs`, `Order.cs` | +| 服务接口 | I + 服务名 | `IUserService.cs` | +| 服务实现 | 服务名 | `UserService.cs` | +| 控制器 | 模块名 + Controller | `UserController.cs` | +| DTO 模型 | 功能 + Request/Dto | `CreateUserRequest.cs`, `UserDto.cs` | + +## 四、代码规范 + +### 4.1 实体类规范 + +```csharp +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace MiAssessment.Admin.Business.Entities; + +/// +/// 用户表 +/// +[Table("users")] +public class User +{ + /// + /// 主键ID + /// + [Key] + public long Id { get; set; } + + /// + /// 用户UID + /// + [Required] + [MaxLength(6)] + public string Uid { get; set; } = null!; + + /// + /// 创建时间 + /// + public DateTime CreateTime { get; set; } + + /// + /// 更新时间 + /// + public DateTime UpdateTime { get; set; } + + /// + /// 软删除标记 + /// + public bool IsDeleted { get; set; } +} +``` + +**要点**: +- 使用 `[Table("表名")]` 指定数据库表名 +- 使用 `[Key]` 标记主键 +- 使用 `[Required]` 标记必填字段 +- 使用 `[MaxLength(n)]` 限制字符串长度 +- 所有属性必须有 XML 注释 +- 字符串属性使用 `= null!` 初始化 + +### 4.2 服务接口规范 + +```csharp +namespace MiAssessment.Admin.Business.Services.Interfaces; + +/// +/// 用户服务接口 +/// +public interface IUserService +{ + /// + /// 获取用户列表 + /// + Task> GetUserListAsync(UserQueryRequest request); + + /// + /// 获取用户详情 + /// + Task GetUserDetailAsync(long id); + + /// + /// 更新用户状态 + /// + Task UpdateUserStatusAsync(long id, int status); +} +``` + +### 4.3 服务实现规范 + +```csharp +namespace MiAssessment.Admin.Business.Services; + +/// +/// 用户服务实现 +/// +public class UserService : IUserService +{ + private readonly AdminBusinessDbContext _dbContext; + private readonly ILogger _logger; + + public UserService( + AdminBusinessDbContext dbContext, + ILogger logger) + { + _dbContext = dbContext; + _logger = logger; + } + + /// + public async Task> GetUserListAsync(UserQueryRequest request) + { + var query = _dbContext.Users + .Where(u => !u.IsDeleted); + + // 条件筛选 + if (!string.IsNullOrEmpty(request.Phone)) + { + query = query.Where(u => u.Phone.Contains(request.Phone)); + } + + // 获取总数 + var total = await query.CountAsync(); + + // 分页查询 + var items = await query + .OrderByDescending(u => u.CreateTime) + .Skip(request.Skip) + .Take(request.PageSize) + .Select(u => new UserDto + { + Id = u.Id, + Uid = u.Uid, + // ... + }) + .ToListAsync(); + + return new PagedResult(items, total, request.Page, request.PageSize); + } +} +``` + +### 4.4 控制器规范 + +```csharp +namespace MiAssessment.Admin.Business.Controllers; + +/// +/// 用户管理控制器 +/// +[Route("api/admin/user")] +public class UserController : BusinessControllerBase +{ + private readonly IUserService _userService; + + public UserController(IUserService userService) + { + _userService = userService; + } + + /// + /// 获取用户列表 + /// + [HttpGet("getList")] + public async Task GetList([FromQuery] UserQueryRequest request) + { + var result = await _userService.GetUserListAsync(request); + return Ok(result); + } + + /// + /// 更新用户状态 + /// + [HttpPost("updateStatus")] + public async Task UpdateStatus([FromBody] UpdateStatusRequest request) + { + var success = await _userService.UpdateUserStatusAsync(request.Id, request.Status); + return success ? Ok() : Error(ErrorCodes.UserNotFound, "用户不存在"); + } +} +``` + +## 五、API 接口规范 + +### 5.1 接口风格 + +- **仅使用 GET 和 POST 请求** +- GET:查询操作,参数通过 Query String 传递 +- POST:新增、修改、删除操作,参数通过 Request Body (JSON) 传递 + +### 5.2 路由规范 + +``` +/api/admin/{module}/{action} +``` + +| 操作 | 方法 | 路由示例 | +|---|---|---| +| 获取列表 | GET | `/api/admin/user/getList` | +| 获取详情 | GET | `/api/admin/user/getDetail?id=1` | +| 创建 | POST | `/api/admin/user/create` | +| 更新 | POST | `/api/admin/user/update` | +| 删除 | POST | `/api/admin/user/delete` | +| 更新状态 | POST | `/api/admin/user/updateStatus` | +| 更新排序 | POST | `/api/admin/user/updateSort` | + +### 5.3 响应格式 + +```json +{ + "code": 0, + "message": "success", + "data": {} +} +``` + +- `code`: 0 表示成功,非 0 表示错误 +- `message`: 提示信息 +- `data`: 业务数据 + +### 5.4 分页响应 + +```json +{ + "code": 0, + "message": "success", + "data": { + "items": [], + "total": 100, + "page": 1, + "pageSize": 20, + "totalPages": 5 + } +} +``` + +## 六、错误码规范 + +| 范围 | 说明 | +|---|---| +| 0 | 成功 | +| 1000-1999 | 通用错误 | +| 2000-2999 | 业务错误 | +| 3000-3099 | 配置模块错误 | +| 3100-3199 | 内容模块错误 | +| 3200-3299 | 测评模块错误 | +| 3300-3399 | 用户模块错误 | +| 3400-3499 | 订单模块错误 | +| 3500-3599 | 规划师模块错误 | +| 3600-3699 | 分销模块错误 | +| 5000-5999 | 系统错误 | + +## 七、注释规范 + +### 7.1 XML 注释 + +所有公开的类、方法、属性必须有 XML 注释: + +```csharp +/// +/// 获取用户列表 +/// +/// 查询参数 +/// 分页用户列表 +public async Task> GetUserListAsync(UserQueryRequest request) +``` + +### 7.2 代码注释 + +- 使用中文注释 +- 复杂逻辑需要添加说明注释 +- 避免无意义的注释 + +## 八、数据库操作规范 + +### 8.1 查询规范 + +```csharp +// 始终过滤软删除记录 +var query = _dbContext.Users.Where(u => !u.IsDeleted); + +// 使用 AsNoTracking 提高只读查询性能 +var users = await _dbContext.Users + .AsNoTracking() + .Where(u => !u.IsDeleted) + .ToListAsync(); +``` + +### 8.2 软删除 + +```csharp +// 删除操作使用软删除 +public async Task DeleteAsync(long id) +{ + var entity = await _dbContext.Users.FindAsync(id); + if (entity == null) return false; + + entity.IsDeleted = true; + entity.UpdateTime = DateTime.Now; + await _dbContext.SaveChangesAsync(); + return true; +} +``` + +### 8.3 更新时间 + +```csharp +// 更新操作自动设置 UpdateTime +entity.UpdateTime = DateTime.Now; +await _dbContext.SaveChangesAsync(); +``` + +## 九、状态值定义 + +### 9.1 通用状态 + +| 值 | 说明 | +|---|---| +| 0 | 禁用/下线 | +| 1 | 启用/正常 | + +### 9.2 订单状态 + +| 值 | 说明 | +|---|---| +| 1 | 待支付 | +| 2 | 已支付 | +| 3 | 已完成 | +| 4 | 退款中 | +| 5 | 已退款 | +| 6 | 已取消 | + +### 9.3 用户等级 + +| 值 | 说明 | +|---|---| +| 1 | 普通用户 | +| 2 | 合伙人 | +| 3 | 渠道合伙人 | + +## 十、测试规范 + +### 10.1 测试框架 + +- 单元测试:xUnit + Moq +- 属性测试:FsCheck + +### 10.2 测试命名 + +```csharp +[Fact] +public async Task GetUserListAsync_WithValidRequest_ReturnsPagedResult() +{ + // Arrange + // Act + // Assert +} +``` + +### 10.3 属性测试注释 + +```csharp +/// +/// Property: 分页查询返回的记录数不超过 PageSize +/// **Validates: Requirements 9.1** +/// +[Property] +public Property PaginationReturnsCorrectCount() +{ + // ... +} +``` + +## 十一、Git 提交规范 + +### 11.1 提交信息格式 + +``` +(): + + +``` + +### 11.2 Type 类型 + +| Type | 说明 | +|---|---| +| feat | 新功能 | +| fix | 修复 bug | +| docs | 文档更新 | +| style | 代码格式 | +| refactor | 重构 | +| test | 测试 | +| chore | 构建/工具 | + +### 11.3 示例 + +``` +feat(user): 添加用户列表分页查询功能 + +- 实现 GetUserListAsync 方法 +- 支持按手机号、昵称筛选 +- 添加分页参数验证 +``` diff --git a/docs/开发规范/1-编程规约/1.1-命名风格.md b/docs/开发规范/1-编程规约/1.1-命名风格.md new file mode 100644 index 0000000..cae4d2b --- /dev/null +++ b/docs/开发规范/1-编程规约/1.1-命名风格.md @@ -0,0 +1,740 @@ +# 1.1 命名风格 + +--- + +## 1. 通用命名原则 +- 使用有意义且可读的名称 + ```csharp + // ❌ 不建议:缩写和无意义的名称 + var d = DateTime.Now; + var usr = new User(); + int n = 100; + + // ✅ 建议:清晰表达意图 + var currentDate = DateTime.Now; + var newUser = new User(); + int maxRetryCount = 100; + ``` + +- 不建议使用拼音和中文 + ```csharp + // ❌ 不建议 + var yongHu = new User(); + string xingMing = "张三"; + var keHuMingCheng = "张三"; + var 订单数量 = 10; + + // ✅ 建议 + var user = new User(); + string fullName = "张三"; + ``` + +- 不建议过度缩写 + ```csharp + // ❌ 不建议 + var cSvc = new CustomerService(); + var ordMgr = new OrderManager(); + + // ✅ 建议 + var customerService = new CustomerService(); + var orderManager = new OrderManager(); + + // ✅ 可接受含义广为人知的缩写 + var customerId = 1; // Id + var htmlContent = ""; // Html + var xmlDocument = ""; // Xml + var urlAddress = ""; // Url + var httpClient = new HttpClient(); // Http + ``` + +- 不建议使用下划线开头(除私有字段外) + ```csharp + // ❌ 不建议 + public string _customerName; + public void _ProcessOrder() { } + + // ✅ 建议 + public string CustomerName { get; set; } + private string _customerName; // 私有字段可以使用 + public void ProcessOrder() { } + ``` +## 2. 命名约定 +### 2.1 类 +- 使用 PascalCase(帕斯卡命名法) + ```csharp + // ✅ 建议的类命名 + public class Customer { } + public class OrderService { } + public class ProductRepository { } + public class PaymentProcessor { } + ``` + +- 使用名词或名词短语 + ```csharp + // ✅ 建议:名词形式 + public class Invoice { } + public class ShoppingCart { } + public class UserProfile { } + public class EmailValidator { } + public class UserAuthentication { } + + // ❌ 不建议:动词形式 + public class ProcessOrder { } // 应该是 OrderProcessor + public class ValidateUser { } // 应该是 UserValidator + public class Calculate { } // 适用于方法命名 + ``` + +- 建议的命名方式 + ```csharp + // 服务类 + // ✅ 以 Service 结尾 + public class CustomerService { } + public class EmailService { } + // ✅ 以职责命名 + public class OrderProcessor { } + public class EmailSender { } + public class PaymentGateway { } + public class CacheManager { } + + // 仓储类 + // ✅ 以 Repository 结尾 + public class OrderRepository { } + public class ProductRepository { } + + // ✅ Provider + public class AuthenticationProvider { } + public class DataProvider { } + + // ✅ Helper/Utility + public class StringHelper { } + public class DateTimeUtility { } + + // 异常类 + // ✅ 以 Exception 结尾 + public class OrderNotFoundException { } + public class InvalidOperationException { } + + // ✅ Attribute + public class ValidateAttribute { } + public class AuthorizeAttribute { } + + // ✅ DTO/ViewModel + public class CustomerDto { } + public class OrderViewModel { } + public class CreateProductRequest { } + public class ProductResponse { } + public class DoSomethingInput { } + public class DoSomethingOutput { } + + // ✅ Entity/Model + public class Order { } + public class Product { } + public class Customer { } + + // 验证器 + // ✅ 以 Validator 结尾 + public class OrderValidator { } + public class EmailValidator { } + public class CreateOrderRequestValidator { } + ``` +- 不建议的命名方式 + ```csharp + // ❌ 不建议:使用下划线或特殊字符 + public class Order_Service { } + public class Product$Manager { } + + // ❌ 不建议:使用前缀 + public class COrder { } + public class TCustomer { } + + // ❌ 不建议:过于通用 + public class Data { } + public class Info { } + public class Manager { } // 太宽泛,应该是具体的 xxxManager + ``` +### 2.2 接口 +- 使用 `I` 前缀 + PascalCase + ```csharp + // ✅ 正确的接口命名 + public interface ICustomerService { } + public interface IOrderRepository { } + public interface IPaymentProcessor { } + public interface ILogger { } + ``` + +- 使用名词、名词短语或形容词 + ```csharp + // ✅ 建议:名词 (一般表示为业务接口) + public interface IRepository { } + public interface IValidator { } + public interface IOrderService { } + public interface ILogger { } + public interface ICache { } + + // ✅ 建议:形容词(一般表示为能力接口) + public interface IComparable { } + public interface IDisposable { } + public interface IEnumerable { } + public interface IQueryable { } + + // ✅ 建议:名词短语 + public interface IDataAccess { } + public interface IUserAuthentication { } + ``` + +- 避免使用 Interface 后缀 + ```csharp + // ❌ 不建议 + public interface IOrderServiceInterface { } + + // ✅ 建议 + public interface IOrderService { } + ``` +### 2.3 方法 +- 使用 PascalCase + ```csharp + public class CustomerService + { + // ✅ 建议 + public void CreateCustomer() { } + public Customer GetCustomerById(int id) { } + public void UpdateCustomerProfile() { } + public void DeleteCustomer() { } + } + ``` + +- 使用动词或动词短语 + ```csharp + // ✅ 建议:明确、清晰的动作 + public void CreateOrder() { } + public void UpdateCustomer() { } + public void DeleteProduct() { } + public Order GetOrderById(int id) { } + public List GetOrdersByCustomer(int customerId) { } + public bool ValidateEmail(string email) { } + public decimal CalculateDiscount(Order order) { } + + // ❌ 不建议:名词形式 + public void Order() { } // 应该是 CreateOrder 或 ProcessOrder + public void Customer() { } + ``` + +- 常用动词前缀 + ```csharp + // Get - 获取数据(通常有返回值) + public Customer GetCustomerById(int id) { } + public List GetOrders() { } + + // Find - 查找数据(可能返回 null) + public Customer? FindCustomerByEmail(string email) { } + + // Create/Add - 创建新对象 + public void CreateOrder(Order order) { } + public void AddProduct(Product product) { } + + // Update/Modify - 更新现有对象 + public void UpdateCustomer(Customer customer) { } + public void ModifyOrderStatus(int orderId, OrderStatus status) { } + + // Delete/Remove - 删除对象 + public void DeleteOrder(int orderId) { } + public void RemoveProduct(int productId) { } + + // Save - 保存(创建或更新) + public void SaveCustomer(Customer customer) { } + + // Validate - 验证 + public bool ValidateOrder(Order order) { } + public ValidationResult ValidateInput(string input) { } + + // Calculate/Compute - 计算 + public decimal CalculateTotal(List items) { } + public int ComputeAge(DateTime birthDate) { } + + // Process/Handle - 处理 + public void ProcessOrder(Order order) { } + public void ProcessPayment(Payment payment) { } + + // 布尔判断 + // ✅ 建议: 使用 Is、Has、Can、Should 等前缀更能提高可读性 + public bool IsValid() { } + public bool IsActive() { } + public bool HasPermission() { } + public bool CanProcess() { } + public bool ShouldRetry() { } + public bool Exists(int id) { } + public bool Contains(string value) { } + // ❌ 不建议:不清晰的命名 + public bool Valid() { } + public bool Active() { } + public bool Permission() { } + + // Convert/Transform - 转换 + public OrderDto ConvertToDto(Order order) { } + public string TransformToJson(object obj) { } + + // Send/Receive - 发送/接收 + public void SendNotification(string message) { } + public Message ReceiveMessage() { } + `` + +- 常用查询方法命名方式 + ```csharp + // ✅ 单个对象 + public Customer GetCustomerById(int id) { } + public Order FindOrderByNumber(string orderNumber) { } + + // ✅ 集合 + public List GetAllCustomers() { } + public IEnumerable GetOrdersByCustomer(int customerId) { } + public List FindProductsByCategory(string category) { } + + // ✅ 条件查询 + public List GetActiveOrders() { } + public List GetVipCustomers() { } + + // ✅ 分页查询 + public PagedResult GetProducts(int pageIndex, int pageSize) { } + public List GetOrdersByPage(int pageNumber, int pageSize) { } + ``` + +- 私有方法也应该使用 PascalCase + ```csharp + public class OrderProcessor + { + // ✅ 私有方法也使用 PascalCase + private void ValidateOrderItems(Order order) { } + private decimal CalculateItemTotal(OrderItem item) { } + private bool CheckInventory(int productId) { } + } + ``` +### 2.4 属性与字段 +- 属性使用 PascalCase + ```csharp + public class Customer + { + // ✅ 建议的属性命名 + public int Id { get; set; } + public string Name { get; set; } + public string Email { get; set; } + public DateTime CreatedDate { get; set; } + public bool IsActive { get; set; } + public decimal TotalAmount { get; set; } + } + ``` + +- 私有字段使用 _camelCase + ```csharp + public class OrderService + { + // ✅ 建议的私有字段命名 + private readonly IOrderRepository _orderRepository; + private readonly ILogger _logger; + private readonly IEmailService _emailService; + private int _retryCount; + private bool _isProcessing; + } + ``` + +- 只读字段 + ```csharp + public class Configuration + { + // ✅ 公共只读字段使用 PascalCase + public readonly string DefaultCulture = "zh-CN"; //(公共只读字段建议改为属性方式使用) + + // ✅ 私有只读字段使用 _camelCase + private readonly string _connectionString; + private readonly int _maxRetryCount; + } + ``` + +- 静态字段 + ```csharp + public class AppConstants + { + // ✅ 公共静态字段使用 PascalCase + public static readonly int MaxPageSize = 100; + public static readonly string DefaultDateFormat = "yyyy-MM-dd"; + + // ✅ 私有静态字段使用 _camelCase + private static readonly object _lockObject = new object(); + } + ``` + +- 布尔属性 + ```csharp + public class Order + { + // ✅ 建议使用 Is、Has、Can 等前缀提高可读性 + public bool IsCompleted { get; set; } + public bool IsPaid { get; set; } + public bool HasDiscount { get; set; } + public bool CanBeCancelled { get; set; } + + // ❌ 不建议 + public bool Completed { get; set; } + public bool Paid { get; set; } + } + ``` + +- 集合属性 + ```csharp + public class Order + { + // ✅ 建议:使用复数形式 + public List Items { get; set; } + public ICollection Payments { get; set; } + public IEnumerable Comments { get; set; } + + // ❌ 不建议:使用单数 + public List Item { get; set; } + } + ``` +### 2.5 变量与参数 +- 局部变量使用 camelCase + ```csharp + public void ProcessOrder(int orderId) + { + // ✅ 建议的局部变量命名 + var order = GetOrder(orderId); + var totalAmount = CalculateTotal(order); + var discountRate = GetDiscountRate(order); + var finalPrice = totalAmount * (1 - discountRate); + var isValid = ValidateOrder(order); + } + ``` + +- 方法参数使用 camelCase + ```csharp + // ✅ 建议的参数命名 + public Order CreateOrder(int customerId, List items, decimal discountAmount, string shippingAddress) + { + // ...existing code... + } + + // ✅ 布尔参数命名 + public List GetOrders(int customerId, bool includeCompleted, bool includeCancelled) + { + // ...existing code... + } + ``` + +- 循环变量命名 + ```csharp + // ✅ 简单循环可以使用单字母 + for (int i = 0; i < orders.Count; i++) + { + // ...existing code... + } + + // ✅ 有意义的循环变量 + foreach (var order in orders) + { + ProcessOrder(order); + } + + foreach (var item in order.Items) + { + ValidateItem(item); + } + + // ❌ 不建议:不清晰的变量名 + foreach (var o in orders) // o 不如 order 清晰 + { + // ...existing code... + } + ``` + +- LINQ 查询变量命名 + ```csharp + // ✅ 建议 + var activeCustomers = customers + .Where(c => c.IsActive) + .ToList(); + + var customerOrders = orders + .Where(o => o.CustomerId == customerId) + .OrderByDescending(o => o.CreatedDate) + .ToList(); + + // ✅ 建议:复杂查询使用有意义的变量 + var highValueOrders = orders + .Where(order => order.TotalAmount > 1000) + .Select(order => new + { + OrderId = order.Id, + CustomerName = order.Customer.Name, + TotalAmount = order.TotalAmount + }) + .ToList(); + ``` + +- 临时变量命名 + ```csharp + public decimal CalculateOrderTotal(Order order) + { + // ✅ 建议:清晰的临时变量 + var subtotal = order.Items.Sum(item => item.Price * item.Quantity); + var taxAmount = subtotal * 0.1m; + var shippingFee = CalculateShippingFee(order); + var discountAmount = CalculateDiscount(order, subtotal); + var total = subtotal + taxAmount + shippingFee - discountAmount; + + return total; + } + ``` + +- 避免使用无意义的变量名 + ```csharp + // ❌ 不建议 + var temp = GetCustomer(); + var data = ProcessData(); + var obj = CreateObject(); + var result = DoSomething(); + + // ✅ 建议 + var customer = GetCustomer(); + var processedOrders = ProcessOrders(); + var paymentRecord = CreatePaymentRecord(); + var validationResult = ValidateInput(); + ``` +### 2.6 常量与枚举 +- 常量命名 + ```csharp + // ✅ 方式一:PascalCase(推荐) + public class OrderConstants + { + public const int MaxItemsPerOrder = 100; + public const decimal MinOrderAmount = 10.0m; + public const string DefaultCurrency = "CNY"; + } + + // ✅ 方式二:UPPER_CASE(C# 中较少使用) + public class AppSettings + { + public const int MAX_RETRY_COUNT = 3; + public const string DEFAULT_CULTURE = "zh-CN"; + } + + // ✅ 私有常量 + private const int MaxRetryAttempts = 3; + private const string ErrorMessageTemplate = "处理订单 {0} 时发生错误"; + ``` + +- 枚举命名 + ```csharp + // ✅ 枚举类型使用 PascalCase,单数形式 + public enum OrderStatus + { + Pending, // 枚举值使用 PascalCase + Processing, + Completed, + Cancelled, + Refunded + } + + // ✅ 标志枚举使用复数 + [Flags] + public enum UserPermissions + { + None = 0, + Read = 1, + Write = 2, + Delete = 4, + Admin = 8 + } + + // ❌ 不建议:使用复数(非标志枚举) + public enum OrderStatuses { } + + // ❌ 不建议:枚举值使用前缀 + public enum OrderStatus + { + OrderStatusPending, // 不需要前缀 + OrderStatusCompleted + } + ``` + +- 常量类组织 + ```csharp + // ✅ 按功能组织常量 + public static class ValidationConstants + { + public const int MinPasswordLength = 8; + public const int MaxPasswordLength = 50; + public const int MinUsernameLength = 3; + public const int MaxUsernameLength = 20; + } + + public static class CacheKeys + { + public const string CustomerPrefix = "customer_"; + public const string OrderPrefix = "order_"; + public const string ProductPrefix = "product_"; + } + + public static class ErrorCodes + { + public const string OrderNotFound = "ORDER_NOT_FOUND"; + public const string InsufficientStock = "INSUFFICIENT_STOCK"; + public const string PaymentFailed = "PAYMENT_FAILED"; + } + ``` +### 2.7 布尔类型 +- 使用肯定的命名方式 + ```csharp + // ✅ 建议:肯定形式 + public bool IsActive { get; set; } + public bool IsEnabled { get; set; } + public bool HasPermission { get; set; } + public bool CanEdit { get; set; } + + // ❌ 不建议:否定形式(增加理解难度) + public bool IsNotActive { get; set; } + public bool IsDisabled { get; set; } + ``` + +- 常用布尔命名前缀 + ```csharp + public class Order + { + // Is - 状态判断 + public bool IsCompleted { get; set; } + public bool IsValid { get; set; } + public bool IsPaid { get; set; } + + // Has - 拥有判断 + public bool HasDiscount { get; set; } + public bool HasShipped { get; set; } + + // Can - 能力判断 + public bool CanBeCancelled { get; set; } + public bool CanBeRefunded { get; set; } + + // Should - 应该判断 + public bool ShouldSendEmail { get; set; } + public bool ShouldApplyDiscount { get; set; } + } + ``` + +- 布尔方法命名 + ```csharp + public class OrderValidator + { + // ✅ 建议 + public bool IsValidOrder(Order order) { } + public bool HasSufficientStock(int productId, int quantity) { } + public bool CanProcessPayment(Payment payment) { } + public bool Exists(int orderId) { } + + // ✅ 验证方法也可以使用 Validate 开头,返回 bool + public bool ValidateOrderItems(Order order) { } + } + ``` +### 2.8 集合类型 +- 使用复数形式 + ```csharp + public class Customer + { + // ✅ 建议:使用复数 + public List Orders { get; set; } + public ICollection
Addresses { get; set; } + public IEnumerable Payments { get; set; } + + // ❌ 不建议 + public List OrderList { get; set; } // 不需要 List 后缀 + public List Order { get; set; } // 应使用复数 + } + ``` + +- 集合局部变量命名 + ```csharp + public void ProcessOrders() + { + // ✅ 建议 + var orders = GetOrders(); + var activeOrders = orders.Where(o => o.IsActive).ToList(); + var orderIds = orders.Select(o => o.Id).ToList(); + var customerNames = customers.Select(c => c.Name).ToList(); + + // ❌ 不建议 + var orderDictionary = orders.ToDictionary(o => o.Id); + var customersByEmail = customers.ToDictionary(c => c.Email); + } + ``` + +- 特殊集合命名 + ```csharp + // ✅ 字典 + private readonly Dictionary _orderCache; + private readonly ConcurrentDictionary _customerLookup; + + // ✅ 队列 + private readonly Queue _orderQueue; + private readonly ConcurrentQueue _taskQueue; + + // ✅ 堆栈 + private readonly Stack _commandHistory; + ``` +### 2.9 异步方法 +- 使用 `Async` 后缀 + ```csharp + // ✅ 异步方法使用 Async 后缀 + public async Task GetOrderAsync(int orderId) + { + return await _repository.GetByIdAsync(orderId); + } + + public async Task> GetOrdersAsync(int customerId) + { + return await _repository.GetOrdersByCustomerAsync(customerId); + } + + public async Task CreateOrderAsync(CreateOrderRequest request) + { + await _repository.AddAsync(MapToOrder(request)); + } + + public async Task UpdateOrderAsync(Order order) + { + await _repository.UpdateAsync(order); + } + + public async Task DeleteOrderAsync(int orderId) + { + await _repository.DeleteAsync(orderId); + } + ``` + +- 异步方法返回类型 + ```csharp + // ✅ 返回 Task + public async Task SendEmailAsync(string to, string subject) + { + await _emailService.SendAsync(to, subject); + } + + // ✅ 返回 Task + public async Task ValidateOrderAsync(Order order) + { + return await _validator.ValidateAsync(order); + } + + // ✅ 返回 ValueTask(性能敏感场景) + public async ValueTask GetCachedCountAsync() + { + if (_cache.TryGetValue("count", out int count)) + { + return count; + } + + return await _repository.GetCountAsync(); + } + + // ❌ 错误:避免使用 async void(除事件处理程序外) + public async void ProcessOrderAsync(int orderId) // 错误! + { + await _service.ProcessAsync(orderId); + } + ``` \ No newline at end of file diff --git a/docs/开发规范/1-编程规约/1.2-代码风格.md b/docs/开发规范/1-编程规约/1.2-代码风格.md new file mode 100644 index 0000000..a43bb25 --- /dev/null +++ b/docs/开发规范/1-编程规约/1.2-代码风格.md @@ -0,0 +1,824 @@ +# 1.2 代码风格 + +--- + +## 1. 文件组织结构 +- 类成员组织顺序 + ```csharp + public class OrderService : IOrderService + { + // 1. 常量 + private const int MaxRetryCount = 3; + public const string DefaultCurrency = "CNY"; + + // 2. 静态字段 + private static readonly object _lockObject = new object(); + + // 3. 私有字段(只读字段在前) + private readonly IOrderRepository _orderRepository; + private readonly ILogger _logger; + private readonly IMapper _mapper; + private int _processCount; + + // 4. 构造函数 + public OrderService( + IOrderRepository orderRepository, + ILogger logger, + IMapper mapper) + { + _orderRepository = orderRepository; + _logger = logger; + _mapper = mapper; + } + + // 5. 属性 + public int ProcessedCount => _processCount; + + // 6. 公共方法(按功能分组) + public async Task GetOrderAsync(int orderId) + { + // ...existing code... + } + + public async Task CreateOrderAsync(CreateOrderRequest request) + { + // ...existing code... + } + + // 7. 受保护方法 + protected virtual bool ValidateOrder(Order order) + { + // ...existing code... + } + + // 8. 私有方法 + private decimal CalculateDiscount(Order order) + { + // ...existing code... + } + + private void LogOrderCreation(Order order) + { + // ...existing code... + } + + // 9. 嵌套类型(如果有) + private class OrderValidationContext + { + public Order Order { get; set; } + public List Errors { get; set; } + } + } + ``` + +- 一个文件一个主要类原则 + ```csharp + // ✅ 建议:一个文件一个主要类 + // filepath: Models/Order.cs + public class Order + { + // ...existing code... + } + + // ❌ 不建议:一个文件多个不相关的类 + // filepath: Models.cs + public class Order { } + public class Customer { } + public class Product { } + + // ✅ 例外:紧密相关的小类可以放在一起 + // filepath: Models/OrderEnums.cs + public enum OrderStatus + { + Pending, + Completed + } + + public enum PaymentStatus + { + Unpaid, + Paid + } + ``` +## 2. 缩进与空格 +- 使用 4 个空格缩进 + ```csharp + public class OrderService + { + public void ProcessOrder(Order order) + { + if (order != null) + { + var total = CalculateTotal(order); + order.TotalAmount = total; + } + } + } + ``` + +- 运算符周围使用空格 + ```csharp + // ✅ 建议 + var total = price * quantity; + var discount = total > 1000 ? 0.1m : 0; + var result = (a + b) * c; + var isValid = count > 0 && amount < maxAmount; + + // ❌ 不建议 + var total=price*quantity; + var result=(a+b)*c; + ``` + +- 逗号后使用空格 + ```csharp + // ✅ 建议 + public void CreateOrder(int customerId, decimal amount, string address) + { + var items = new List { 1, 2, 3, 4 }; + ProcessOrder(customerId, amount, address); + } + + // ❌ 不建议 + public void CreateOrder(int customerId,decimal amount,string address) + { + var items = new List { 1,2,3,4 }; + } + ``` + +- 方法参数对齐 + ```csharp + // ✅ 短参数列表:单行 + public Order CreateOrder(int customerId, decimal amount) + { + // ...existing code... + } + + // ✅ 长参数列表:每个参数一行 + public Order CreateOrder( + int customerId, + string customerName, + List items, + string shippingAddress, + PaymentMethod paymentMethod, + decimal discountAmount) + { + // ...existing code... + } + + // ✅ LINQ 链式调用对齐 + var result = orders + .Where(o => o.IsActive) + .OrderByDescending(o => o.CreatedDate) + .Select(o => new OrderDto + { + OrderId = o.Id, + CustomerName = o.Customer.Name, + TotalAmount = o.TotalAmount + }) + .ToList(); + ``` +## 3. 大括号 +- 大括号独占一行(Allman 风格) + ```csharp + // ✅ 建议:大括号独占一行 + public void ProcessOrder(Order order) + { + if (order != null) + { + ValidateOrder(order); + SaveOrder(order); + } + else + { + throw new ArgumentNullException(nameof(order)); + } + } + + // ❌ 不建议:K&R 风格(不推荐在 C# 中使用) + public void ProcessOrder(Order order) { + if (order != null) { + ValidateOrder(order); + } + } + ``` + +- 单行语句也使用大括号 + ```csharp + // ✅ 建议:即使单行也使用大括号 + if (order.IsValid) + { + ProcessOrder(order); + } + + for (int i = 0; i < count; i++) + { + ProcessItem(i); + } + + // ❌ 不建议:省略大括号(易出错、可读性差) + if (order.IsValid) + ProcessOrder(order); + LogOrder(order); // 这行不在 if 块内! + ``` +## 4. 空行 +- 方法之间使用空行 + ```csharp + public class OrderService + { + public Order GetOrder(int id) + { + return _repository.GetById(id); + } + // 空行分隔方法 + public void CreateOrder(Order order) + { + _repository.Add(order); + } + // 空行分隔方法 + public void UpdateOrder(Order order) + { + _repository.Update(order); + } + } + ``` + +- 逻辑块之间使用空行 + ```csharp + public async Task CreateOrderAsync(CreateOrderRequest request) + { + // 验证输入 + if (request == null) + { + throw new ArgumentNullException(nameof(request)); + } + + ValidateRequest(request); + // 空行分隔逻辑块 + // 创建订单 + var order = new Order + { + CustomerId = request.CustomerId, + CreatedDate = DateTime.Now + }; + // 空行分隔逻辑块 + // 保存并返回 + await _repository.AddAsync(order); + await _unitOfWork.CommitAsync(); + + return order; + } + ``` + +- 不要使用多个连续空行 + ```csharp + // ✅ 建议:使用单个空行 + public void Method1() + { + // ...existing code... + } + + public void Method2() + { + // ...existing code... + } + + // ❌ 不建议:多个连续空行 + + + + + public void Method3() + { + // ...existing code... + } + ``` +## 5. 语句换行 +- 长条件表达式换行 + ```csharp + // ✅ 建议:在逻辑运算符前换行 + if (customer.IsActive + && customer.TotalOrders > 10 + && customer.TotalAmount > 10000 + && !customer.IsBlacklisted) + { + ApplyVipDiscount(customer); + } + + // ✅ 复杂条件时可提取为变量 + var isEligibleForDiscount = customer.IsActive + && customer.TotalOrders > 10 + && customer.TotalAmount > 10000 + && !customer.IsBlacklisted; + + if (isEligibleForDiscount) + { + ApplyVipDiscount(customer); + } + ``` + +- LINQ 查询换行 + ```csharp + // ✅ 建议:每个操作符一行 + var result = orders + .Where(o => o.IsActive) + .Where(o => o.TotalAmount > 1000) + .OrderByDescending(o => o.CreatedDate) + .Select(o => new OrderDto + { + Id = o.Id, + CustomerName = o.Customer.Name, + TotalAmount = o.TotalAmount + }) + .ToList(); + ``` +## 6. using 指令组织 +- 移除未使用的 `using` + ```csharp + // ✅ 建议:只保留使用的命名空间 + using System; + using System.Linq; + using CompanyName.ProjectName.Core.Entities; + + // ❌ 不建议:包含未使用的命名空间 + using System; + using System.Collections.Generic; // 未使用 + using System.Text; // 未使用 + using System.Linq; + ``` + +- 使用 `global using` (`C#` 10 +) + ```csharp + global using System; + global using System.Collections.Generic; + global using System.Linq; + global using System.Threading.Tasks; + global using Microsoft.Extensions.Logging; + ``` +- `using static` 使用 + ```csharp + // ✅ 建议:简化静态成员调用 + using static System.Math; + + public class Calculator + { + public double CalculateArea(double radius) + { + return PI * Pow(radius, 2); // 无需 Math.PI 和 Math.Pow + } + } + + // ❌ 不建议:过度使用会降低可读性 + using static System.Console; // 不推荐,WriteLine 来源不明确 + ``` +## 7. 变量与数据类型 +### 7.1 变量声明原则 +- 就近声明原则 + ```csharp + // ✅ 建议:在使用前声明 + public void ProcessOrder(int orderId) + { + var order = GetOrder(orderId); + if (order == null) + { + return; + } + + // 在需要时才声明 + var discount = CalculateDiscount(order); + order.DiscountAmount = discount; + + // 在循环中声明 + foreach (var item in order.Items) + { + var itemTotal = item.Price * item.Quantity; + ValidateItem(item, itemTotal); + } + } + + // ❌ 不建议:过早声明 + public void ProcessOrder(int orderId) + { + var order = GetOrder(orderId); + var discount = 0m; // 过早声明 + var itemTotal = 0m; // 过早声明 + + if (order == null) + { + return; + } + + discount = CalculateDiscount(order); + // ...existing code... + } + ``` + +- 最小作用域原则 + ```csharp + // ✅ 建议:限制变量作用域 + public decimal CalculateTotal(Order order) + { + decimal total = 0; + { + var taxRate = GetTaxRate(); + var tax = order.Subtotal * taxRate; + total = order.Subtotal + tax; + } + + // taxRate 和 tax 在此处不可访问 + return total; + } + + // ✅ 在 if 语句中声明 + if (TryGetOrder(orderId, out var order)) + { + ProcessOrder(order); + } + // order 在此处不可访问 + ``` + +- 初始化时声明 + ```csharp + // ✅ 建议:声明时初始化 + var customerName = "张三"; + var orderCount = 0; + var items = new List(); + var isValid = ValidateInput(); + + // ❌ 不建议:分离声明和初始化 + string customerName; + int orderCount; + List items; + + customerName = "张三"; + orderCount = 0; + items = new List(); + ``` +### 7.2 `null` 处理 +- 使用可空类型 + ```csharp + // ✅ 建议:使用可空类型表示可能为 null 的值 + public class Order + { + public int Id { get; set; } + public DateTime? CompletedDate { get; set; } // 可能未完成 + public decimal? DiscountAmount { get; set; } // 可能无折扣 + public string? Note { get; set; } // 可能无备注 (C# 8.0+) + } + ``` + +- `null` 检查 + ```csharp + // ✅ 建议:参数 null 检查 + public void ProcessOrder(Order order) + { + if (order == null) + { + throw new ArgumentNullException(nameof(order)); + } + + // 或使用 C# 11+ 参数 null 检查 + public void ProcessOrder(Order order!!) + { + // order 不为 null + } + } + + // ✅ 返回值 null 检查 + var order = GetOrder(orderId); + if (order == null) + { + _logger.LogWarning("订单 {OrderId} 不存在", orderId); + return; + } + + // ✅ 使用模式匹配 + if (GetOrder(orderId) is Order order) + { + ProcessOrder(order); + } + ``` + +- 使用 `?.` 和 `??` 操作符 + ```csharp + // ✅ null 条件运算符 ?. + var customerName = order?.Customer?.Name; + var itemCount = order?.Items?.Count ?? 0; + + // ✅ null 合并运算符 ?? + var discountAmount = order.DiscountAmount ?? 0m; + var shippingAddress = order.ShippingAddress ?? order.Customer.DefaultAddress; + + // ✅ null 合并赋值运算符 ??= + order.Note ??= "无备注"; + _cache ??= new Dictionary(); + + // ✅ 链式使用 + var cityName = order?.Customer?.Address?.City ?? "未知"; + ``` + +- 避免返回 `null` + ```csharp + // ❌ 不建议:返回 null + public List GetOrders(int customerId) + { + var orders = _repository.GetByCustomerId(customerId); + return orders; // 可能返回 null + } + + // ✅ 建议:返回空集合 + public List GetOrders(int customerId) + { + var orders = _repository.GetByCustomerId(customerId); + return orders ?? new List(); + } + + // ✅ 建议:使用 LINQ + public IEnumerable GetOrders(int customerId) + { + return _repository.GetByCustomerId(customerId) + ?? Enumerable.Empty(); + } + ``` + +- 使用 `nullable reference types` (`C#` 8.0+) + ```csharp + #nullable enable + + public class OrderService + { + // 不可为 null + private readonly IOrderRepository _repository; + + // 可为 null + private ILogger? _logger; + + public OrderService(IOrderRepository repository) + { + _repository = repository; // 必须赋值 + } + + public Order? GetOrder(int id) // 明确返回可能为 null + { + return _repository.GetById(id); + } + + public void ProcessOrder(Order order) // 参数不可为 null + { + // order 保证不为 null + var total = order.TotalAmount; + } + } + ``` +### 7.3 字符串操作 +- 字符转拼接 + ```csharp + // ❌ 不建议:循环中使用 + 拼接 + string result = ""; + for (int i = 0; i < 1000; i++) + { + result += i.ToString(); // 性能差,产生大量临时对象 + } + + // ✅ 建议:使用 StringBuilder + var sb = new StringBuilder(); + for (int i = 0; i < 1000; i++) + { + sb.Append(i); + } + string result = sb.ToString(); + + // ✅ 少量拼接可以使用字符串插值 + string name = "张三"; + int age = 25; + string message = $"姓名:{name},年龄:{age}"; // 推荐 + + // ✅ 使用 string.Join + var items = new[] { "苹果", "香蕉", "橙子" }; + string result = string.Join(", ", items); // "苹果, 香蕉, 橙子" + ``` + +- 字符串比较 + ```csharp + // ✅ 建议:使用 StringComparison + if (string.Equals(str1, str2, StringComparison.OrdinalIgnoreCase)) + { + // 忽略大小写比较 + } + + // ✅ 检查 null 或空 + if (string.IsNullOrEmpty(customerName)) + { + throw new ArgumentException("客户名称不能为空"); + } + + if (string.IsNullOrWhiteSpace(note)) + { + note = "无备注"; + } + + // ❌ 不建议:使用 == 比较可能为 null 的字符串 + if (str1 == str2) // 如果都为 null 会返回 true + { + // ...existing code... + } + ``` + +- 字符串格式化 + ```csharp + // ✅ 推荐使用字符串插值 + decimal price = 99.99m; + string message = $"价格:{price:C}"; // "价格:¥99.99" + + // ✅ 格式化数字 + int number = 1234567; + string formatted = $"{number:N0}"; // "1,234,567" + string hex = $"{number:X}"; // "12D687" + + // ✅ 格式化日期 + DateTime now = DateTime.Now; + string dateStr = $"{now:yyyy-MM-dd HH:mm:ss}"; // "2025-11-26 14:30:00" + string shortDate = $"{now:d}"; // "2025/11/26" + + // ✅ 对齐和填充 + string name = "张三"; + string aligned = $"{name, 10}"; // 右对齐,总宽度10 + string leftAligned = $"{name, -10}"; // 左对齐 + ``` +### 7.4 集合 +- 集合初始化 + ```csharp + // ✅ 集合初始化器 + var numbers = new List { 1, 2, 3, 4, 5 }; + + var dict = new Dictionary + { + ["apple"] = 1, + ["banana"] = 2, + ["orange"] = 3 + }; + + // ✅ 指定初始容量(已知大小时) + var largeList = new List(1000); // 避免多次扩容 + + // ✅ 数组初始化 + int[] scores = { 85, 90, 78, 92 }; + int[] grades = new int[5]; // 固定大小 + ``` + +- 集合选择 + ```csharp + // ✅ List - 需要按索引访问和频繁添加 + var customers = new List(); + + // ✅ HashSet - 需要唯一性检查和快速查找 + var uniqueIds = new HashSet(); + uniqueIds.Add(1); + uniqueIds.Add(1); // 不会重复添加 + + // ✅ Dictionary - 键值对存储和快速查找 + var customerCache = new Dictionary(); + + // ✅ Queue - 先进先出 + var taskQueue = new Queue(); + + // ✅ Stack - 后进先出 + var history = new Stack(); + + // ✅ ImmutableList - 不可变集合(线程安全) + var immutableList = ImmutableList.Create(1, 2, 3); + ``` +### 7.5 常量与魔法数字处理 +- 常量定义 + ```csharp + // ✅ 定义常量类 + public static class OrderConstants + { + public const int MaxItemsPerOrder = 100; + public const decimal MinOrderAmount = 50.0m; + public const int OrderTimeoutMinutes = 30; + + // 字符串常量 + public const string DefaultCurrency = "CNY"; + public const string DateFormat = "yyyy-MM-dd"; + } + + // ✅ 在类中使用私有常量 + public class OrderService + { + private const int MaxRetryCount = 3; + private const string LogPrefix = "[OrderService]"; + + public void ProcessOrder(Order order) + { + if (order.Items.Count > OrderConstants.MaxItemsPerOrder) + { + throw new BusinessException($"订单商品数量不能超过{OrderConstants.MaxItemsPerOrder}"); + } + } + } + ``` + +- 避免魔法数字 + ```csharp + // ❌ 不建议:魔法数字 + public decimal CalculateDiscount(decimal amount) + { + if (amount > 1000) + { + return amount * 0.1m; // 0.1 是什么? + } + + if (amount > 500) + { + return amount * 0.05m; // 0.05 是什么? + } + + return 0; + } + + // ✅ 建议:使用命名常量 + public class DiscountCalculator + { + private const decimal HighAmountThreshold = 1000m; + private const decimal MediumAmountThreshold = 500m; + private const decimal HighDiscountRate = 0.1m; + private const decimal MediumDiscountRate = 0.05m; + + public decimal CalculateDiscount(decimal amount) + { + if (amount > HighAmountThreshold) + { + return amount * HighDiscountRate; + } + + if (amount > MediumAmountThreshold) + { + return amount * MediumDiscountRate; + } + + return 0; + } + } + ``` +### 7.6 枚举类型 +- + ```csharp + public class OrderService + { + // ✅ 使用枚举而非字符串或数字 + public void UpdateOrderStatus(int orderId, OrderStatus status) + { + // 类型安全,编译时检查 + switch (status) + { + case OrderStatus.Pending: + // 处理待处理状态 + break; + case OrderStatus.Confirmed: + // 处理已确认状态 + break; + default: + throw new ArgumentException($"不支持的订单状态:{status}"); + } + } + + // ✅ 枚举转换字符串 + public string GetStatusName(OrderStatus status) + { + return status.ToString(); // "Pending" + } + + // ✅ 字符串转枚举(安全) + public OrderStatus ParseStatus(string statusText) + { + if (Enum.TryParse(statusText, out var status)) + { + return status; + } + + throw new ArgumentException($"无效的订单状态:{statusText}"); + } + + // ✅ 获取所有枚举值 + public IEnumerable GetAllStatuses() + { + return Enum.GetValues(); + } + + // ✅ 标志枚举操作 + public void ManagePermissions() + { + var permissions = FilePermissions.None; + + // 添加权限 + permissions |= FilePermissions.Read; + permissions |= FilePermissions.Write; + + // 检查权限 + bool canRead = permissions.HasFlag(FilePermissions.Read); + + // 移除权限 + permissions &= ~FilePermissions.Write; + } + } + ``` \ No newline at end of file diff --git a/docs/开发规范/1-编程规约/1.3-注释规约.md b/docs/开发规范/1-编程规约/1.3-注释规约.md new file mode 100644 index 0000000..3c2e5c9 --- /dev/null +++ b/docs/开发规范/1-编程规约/1.3-注释规约.md @@ -0,0 +1,476 @@ +# 1.3 注释规约 + +--- + +## 1. XML 文档注释 +- 类注释 + ```csharp + /// + /// 订单服务,提供订单管理相关业务逻辑 + /// + /// + /// 此服务负责: + /// - 订单创建、查询、更新、删除 + /// - 订单状态流转 + /// - 订单金额计算 + /// + public class OrderService : IOrderService + { + // ...existing code... + } + ``` + +- 接口注释 + ```csharp + /// + /// 订单仓储接口,定义订单数据访问操作 + /// + public interface IOrderRepository + { + /// + /// 根据订单ID获取订单详情 + /// + /// 订单ID + /// 订单实体,如果不存在返回 null + Task GetByIdAsync(int orderId); + } + ``` + +- 方法注释 + ```csharp + /// + /// 创建新订单 + /// + /// 订单创建请求对象 + /// 创建成功的订单实体 + /// 当 request 为 null 时抛出 + /// 当订单数据验证失败时抛出 + /// 当库存不足时抛出 + /// + /// 此方法执行以下步骤: + /// 1. 验证请求数据 + /// 2. 检查库存 + /// 3. 计算订单金额 + /// 4. 创建订单记录 + /// 5. 发送确认邮件 + /// + /// + /// + /// var request = new CreateOrderRequest + /// { + /// CustomerId = 123, + /// Items = new List<OrderItemDto> + /// { + /// new() { ProductId = 1, Quantity = 2 } + /// } + /// }; + /// var order = await orderService.CreateOrderAsync(request); + /// + /// + public async Task CreateOrderAsync(CreateOrderRequest request) + { + // ...existing code... + } + ``` + +- 属性注释 + ```csharp + /// + /// 获取或设置订单ID + /// + public int Id { get; set; } + + /// + /// 获取或设置客户ID + /// + public int CustomerId { get; set; } + + /// + /// 获取或设置订单总金额(含税) + /// + /// 订单总金额,单位:元 + public decimal TotalAmount { get; set; } + + /// + /// 获取或设置订单是否已完成 + /// + /// + /// true 表示订单已完成;否则为 false + /// + public bool IsCompleted { get; set; } + ``` + +- 枚举注释 + ```csharp + /// + /// 订单状态枚举 + /// + public enum OrderStatus + { + /// + /// 待处理 + /// + Pending = 0, + + /// + /// 处理中 + /// + Processing = 1, + + /// + /// 已完成 + /// + Completed = 2, + + /// + /// 已取消 + /// + Cancelled = 3 + } + ``` + +- 泛型参数注释 + ```csharp + /// + /// 通用仓储接口 + /// + /// 实体类型,必须是引用类型 + public interface IRepository where T : class + { + /// + /// 根据ID获取实体 + /// + /// 实体ID + /// 实体对象,如果不存在返回 null + Task GetByIdAsync(int id); + } + ``` +## 2. 行内注释使用场景 +- 解释复杂业务逻辑 + ```csharp + public decimal CalculateDiscount(Order order) + { + // 根据业务规则BR-2023-001: + // VIP客户订单金额超过1000元享受9折优惠 + // 新客户首单享受95折优惠 + // 优惠不可叠加,取最优惠方案 + + if (order.Customer.IsVip && order.TotalAmount > 1000) + { + return order.TotalAmount * 0.1m; + } + + if (order.Customer.IsNewCustomer && order.IsFirstOrder) + { + return order.TotalAmount * 0.05m; + } + + return 0; + } + ``` + +- 解释不明显的代码意图 + ```csharp + public void ProcessOrder(Order order) + { + // 将订单状态设置为处理中,防止并发处理同一订单 + order.Status = OrderStatus.Processing; + order.LastModifiedDate = DateTime.UtcNow; + + // 使用UTC时间避免时区问题 + var processStartTime = DateTime.UtcNow; + + // 临时解决方案:忽略已删除的订单项 + // TODO: 后续需要从数据库层面过滤 + var activeItems = order.Items.Where(i => !i.IsDeleted).ToList(); + } + ``` + +- 标记临时代码或待优化代码 + ```csharp + public async Task> GetOrdersAsync(int customerId) + { + // HACK: 临时解决方案,直接加载所有订单 + // 性能问题:当订单量大时会导致内存占用过高 + // TODO: 实现分页加载 + var orders = await _context.Orders + .Where(o => o.CustomerId == customerId) + .ToListAsync(); + + return orders; + } + ``` + +- 解释算法或公式 + ```csharp + public decimal CalculateShippingFee(Order order) + { + // 运费计算公式: + // 基础运费 = 10元 + // 重量费用 = 总重量 * 2元/kg + // 距离费用 = 距离 * 0.5元/km + // 如果订单金额超过200元,免运费 + + if (order.TotalAmount >= 200) + { + return 0; + } + + var baseFee = 10m; + var weightFee = order.TotalWeight * 2m; + var distanceFee = order.DeliveryDistance * 0.5m; + + return baseFee + weightFee + distanceFee; + } + ``` +## 3. `TODO`、`FIXME`、`HACK`、`NOTE`/`IMPORTANT` 标记使用 +- `TODO` - 待实现功能 + ```csharp + public class OrderService + { + public async Task CreateOrderAsync(Order order) + { + await _repository.AddAsync(order); + + // TODO: 实现订单创建后发送确认邮件 + // TODO: 实现库存扣减逻辑 + // TODO: 集成支付网关 + } + + // TODO: [张三] 2024-01-15 实现退款功能 + public Task RefundOrderAsync(int orderId) + { + throw new NotImplementedException(); + } + } + ``` + +- `FIXME` - 已知问题需要修复 + ```csharp + public async Task> GetOrdersAsync(int pageIndex, int pageSize) + { + // FIXME: 当pageSize过大时会导致性能问题 + // FIXME: 未处理pageIndex为负数的情况 + + return await _context.Orders + .Skip(pageIndex * pageSize) + .Take(pageSize) + .ToListAsync(); + } + + public decimal CalculateTotal(Order order) + { + // FIXME: 并发情况下可能导致重复计算 + // 需要添加锁机制或使用数据库事务 + + return order.Items.Sum(i => i.Price * i.Quantity); + } + ``` + +- `HACK` - 临时解决方案 + ```csharp + public async Task ProcessOrderAsync(int orderId) + { + // HACK: 临时方案,硬编码重试3次 + // 应该从配置文件读取重试次数 + for (int i = 0; i < 3; i++) + { + try + { + await ProcessAsync(orderId); + break; + } + catch (Exception ex) + { + if (i == 2) + { + throw; + } + await Task.Delay(1000); + } + } + } + ``` + +- `NOTE`/`IMPORTANT` - 重要说明 + ```csharp + public class PaymentService + { + // NOTE: 此方法会修改订单状态,调用前请确保已获取锁 + // IMPORTANT: 必须在事务中调用此方法 + public async Task ProcessPaymentAsync(Payment payment) + { + // ...existing code... + } + } + ``` +## 4. 复杂业务逻辑注释要求 +- 业务规则注释 + ```csharp + public bool CanApplyDiscount(Order order, Customer customer) + { + // 业务规则 BR-2024-001: 折扣适用条件 + // 1. 客户必须是激活状态 + // 2. 订单金额必须大于等于100元 + // 3. 客户当月订单数量不超过10个 + // 4. 产品不在促销黑名单中 + + if (!customer.IsActive) + { + return false; + } + + if (order.TotalAmount < 100) + { + return false; + } + + var monthlyOrderCount = GetMonthlyOrderCount(customer.Id); + if (monthlyOrderCount > 10) + { + return false; + } + + var hasBlacklistedProducts = order.Items.Any(i => IsProductBlacklisted(i.ProductId)); + + return !hasBlacklistedProducts; + } + ``` + +- 状态机转换注释 + ```csharp + public void UpdateOrderStatus(Order order, OrderStatus newStatus) + { + // 订单状态转换规则: + // Pending -> Processing (允许) + // Pending -> Cancelled (允许) + // Processing -> Completed (允许) + // Processing -> Cancelled (不允许,必须先退款) + // Completed -> Refunded (允许,需要走退款流程) + // Cancelled -> * (不允许任何转换) + + var allowedTransitions = new Dictionary> + { + { OrderStatus.Pending, new List { OrderStatus.Processing, OrderStatus.Cancelled } }, + { OrderStatus.Processing, new List { OrderStatus.Completed } }, + { OrderStatus.Completed, new List { OrderStatus.Refunded } } + }; + + if (!allowedTransitions.ContainsKey(order.Status) || !allowedTransitions[order.Status].Contains(newStatus)) + { + throw new InvalidOperationException($"不允许从 {order.Status} 转换到 {newStatus}"); + } + + order.Status = newStatus; + } + ``` + +- 复杂计算注释 + ```csharp + public decimal CalculateCommission(Order order, Salesperson salesperson) + { + // 佣金计算规则(2024年Q1版本): + // + // 基础佣金率: + // - 初级销售:订单金额的 3% + // - 中级销售:订单金额的 5% + // - 高级销售:订单金额的 8% + // + // 阶梯奖励(累加): + // - 月度销售额超过 10万,额外 +1% + // - 月度销售额超过 50万,额外 +2% + // - 月度销售额超过100万,额外 +3% + // + // 特殊产品加成: + // - 高端产品类别,佣金率 *1.5 + + var baseRate = salesperson.Level switch + { + SalesLevel.Junior => 0.03m, + SalesLevel.Intermediate => 0.05m, + SalesLevel.Senior => 0.08m, + _ => 0.03m + }; + + var monthlySales = GetMonthlySales(salesperson.Id); + var bonusRate = monthlySales switch + { + >= 1000000 => 0.03m, + >= 500000 => 0.02m, + >= 100000 => 0.01m, + _ => 0m + }; + + var finalRate = baseRate + bonusRate; + + var hasHighEndProducts = order.Items.Any(i => i.Product.Category == ProductCategory.HighEnd); + + if (hasHighEndProducts) + { + finalRate *= 1.5m; + } + + return order.TotalAmount * finalRate; + } + ``` +## 5. 注释的维护与更新 +- 注释应与代码同步更新 + ```csharp + // ❌ 错误:注释已过时 + // 返回所有订单(实际代码已改为返回激活订单) + public List GetOrders() + { + return _context.Orders + .Where(o => o.IsActive) // 代码已修改,但注释未更新 + .ToList(); + } + + // ✅ 正确:更新注释 + /// + /// 获取所有激活状态的订单 + /// + public List GetActiveOrders() + { + return _context.Orders + .Where(o => o.IsActive) + .ToList(); + } + ``` + +- 删除无用注释 + ```csharp + // ❌ 错误:保留已注释的旧代码 + public void ProcessOrder(Order order) + { + // var oldDiscount = order.TotalAmount * 0.05m; + // order.DiscountAmount = oldDiscount; + + // 新的折扣计算逻辑 + var newDiscount = CalculateDiscount(order); + order.DiscountAmount = newDiscount; + } + + // ✅ 正确:删除已注释代码,使用版本控制系统管理历史 + public void ProcessOrder(Order order) + { + var discount = CalculateDiscount(order); + order.DiscountAmount = discount; + } + ``` + +- 避免显而易见的注释 + ```csharp + // ❌ 错误:显而易见的注释 + // 设置客户名称 + customer.Name = "张三"; + + // 增加计数器 + counter ++; + + // ✅ 正确:只注释需要解释的内容 + // 使用UTC时间避免时区转换问题 + order.CreatedDate = DateTime.UtcNow; + + // 预留30天的订单保留期 + var retentionDays = 30; + ``` \ No newline at end of file diff --git a/docs/开发规范/1-编程规约/1.4-多线程与异步规范.md b/docs/开发规范/1-编程规约/1.4-多线程与异步规范.md new file mode 100644 index 0000000..09d6b1c --- /dev/null +++ b/docs/开发规范/1-编程规约/1.4-多线程与异步规范.md @@ -0,0 +1,184 @@ +# 1.4 多线程与异步规范 + +本规范旨在指导 .NET 中的多线程与异步编程,确保代码的高效性、稳定性和可维护性,避免常见的死锁、线程饥饿和竞态条件问题。 + +## 1. 异步编程 (Async/Await) + +### 1.1 避免 `async void` + +* **规则**:严禁使用 `async void`,唯一的例外是事件处理程序 (Event Handlers)。 +* **理由**: + * `async void` 方法无法被等待 (await)。 + * `async void` 方法中抛出的异常无法被调用方捕获,会导致进程崩溃(除非在 SynchronizationContext 中捕获)。 + * 难以测试和组合。 +* **反例** : + ```csharp + // ❌ [不推荐] 使用 async void,异常无法被外部捕获 + private async void ExecuteAsync(object obj) + { + try + { + await _jobPerformer.Perform(...); + } + catch (Exception e) + { + // 必须在内部吞掉所有异常,否则进程崩溃 + Console.WriteLine(e); + } + } + ``` +* **正例**: + ```csharp + // ✅ [推荐] 返回 Task,允许调用方等待和处理异常 + private async Task ExecuteAsync(object obj) + { + try + { + await _jobPerformer.Perform(...); + } + catch (Exception e) + { + _logger.LogError(e, "Error occurred"); + } + } + ``` + +### 1.2 异步全链路 (Async All the Way) + +* **规则**:一旦开始使用异步,应在整个调用链路中保持异步。 +* **理由**:避免同步/异步混合导致的死锁(Sync-over-Async)和线程池饥饿。 +* **反例**: + ```csharp + // ❌ [禁止] 在异步方法中阻塞等待 + public void DoSomething() + { + DoSomethingAsync().Result; // 或者 .Wait() + } + ``` +* **正例**: + ```csharp + // ✅ [推荐] 使用 await + public async Task DoSomething() + { + await DoSomethingAsync(); + } + ``` + +### 1.3 库代码使用 `ConfigureAwait(false)` + +* **规则**:在通用类库 (非 UI 层、非 ASP.NET Core Controller 层) 代码中,应使用 `.ConfigureAwait(false)`。 +* **理由**:避免在不需要特定 `SynchronizationContext` (如 UI 线程) 的情况下强制切回原上下文,提高性能并减少死锁风险。 +* **注意**:在 ASP.NET Core 应用层代码中通常不需要,因为 ASP.NET Core 没有 SynchronizationContext。但在编写底层 SDK (如 `Aegis.Caching`) 时必须遵守。 +* **正例**: + ```csharp + // ✅ [推荐] 类库代码 + public async Task GetDataAsync() + { + var result = await _httpClient.GetStringAsync(url).ConfigureAwait(false); + return result; + } + ``` + +### 1.4 传入 CancellationToken + +* **规则**:异步方法应尽可能支持 `CancellationToken`,以便在操作不再需要时取消。 +* **正例**: + ```csharp + // ✅ [推荐] 支持取消 + public async Task ProcessAsync(CancellationToken cancellationToken = default) + { + while (!cancellationToken.IsCancellationRequested) + { + await Task.Delay(1000, cancellationToken); + // ... 业务逻辑 + } + } + ``` + +## 2. 线程与任务 (Threads & Tasks) + +### 2.1 优先使用 Task 而非 Thread + +* **规则**:使用 `Task.Run` 或 `Task.Factory.StartNew` (带有 `TaskCreationOptions.LongRunning`),尽量避免直接 `new Thread()`。 +* **理由**:`Task` 基于线程池,资源利用率更高,且 API 更易于组合和异常处理。 +* **例外**:需要设置线程优先级、名称或必须是前台线程的特殊场景。 + +### 2.2 避免长时间阻塞线程池线程 + +* **规则**:如果任务是 CPU 密集型且运行时间很长,应指定 `TaskCreationOptions.LongRunning`,或者使用独立的线程,避免耗尽线程池导致吞吐量下降。 + +## 3. 线程安全与锁 (Locking) + +### 3.1 锁的选择 + +* **规则**: + * **同步代码**:首选 `lock` (即 `Monitor`)。 + * **异步代码**:必须使用 `SemaphoreSlim`,严禁在 `lock` 块中使用 `await`。 + * **高性能/轻量级**:`Interlocked` 用于简单的计数器或原子交换。 + * **自旋锁**:慎用 `SpinLock` 或 `SpinWait`,除非非常短的临界区且你非常清楚自己在做什么。 + *建议:对于大多数业务场景,普通的 `lock` 或 `SemaphoreSlim` 足够且更安全。除非经过 Benchmark 证明必须使用自旋锁。* + +* **正例 (异步锁)**: + ```csharp + private readonly SemaphoreSlim _lock = new SemaphoreSlim(1, 1); + + public async Task SafeOperationAsync() + { + await _lock.WaitAsync(); + try + { + // ✅ 可以在锁内 await + await DoSomethingAsync(); + } + finally + { + _lock.Release(); + } + } + ``` + +### 3.2 线程安全的集合 + +* **规则**:在多线程环境下读写集合,应使用 `System.Collections.Concurrent` 命名空间下的集合 (如 `ConcurrentDictionary`, `ConcurrentQueue`),而不是手动加锁的 `List` 或 `Dictionary`。 +* **正例**: + ```csharp + private readonly ConcurrentDictionary _cache = new(); + + public void AddUser(User user) + { + // ✅ 线程安全 + _cache.TryAdd(user.Id, user); + } + ``` + +### 3.3 静态成员的线程安全 + +* **规则**:静态成员 (Static Members) 在多线程环境下是共享的,必须保证其线程安全。 +* **建议**: + * 尽量设计为不可变 (Immutable)。 + * 如果必须可变,使用 `lock` 或 `Concurrent` 类型保护。 + +## 4. 并行处理 (Parallel) + +### 4.1 控制并发度 + +* **规则**:使用 `Parallel.ForEach` 或 `Task.WhenAll` 时,必须控制并发数量 (`MaxDegreeOfParallelism`),防止瞬间并发过高压垮下游服务 (数据库、Redis、外部 API)。 +* **正例**: + ```csharp + var options = new ParallelOptions { MaxDegreeOfParallelism = 5 }; + await Parallel.ForEachAsync(items, options, async (item, token) => + { + await ProcessItemAsync(item, token); + }); + ``` + +### 4.2 避免 Parallel 里的共享状态写入 + +* **规则**:避免在并行循环中写入共享变量,这通常是非线程安全的。应使用线程局部变量或并发集合。 + +## 5. 最佳实践总结 + +1. **不要阻塞异步代码**:避免 `.Result`, `.Wait()`。 +2. **异常处理**:`Task` 中的异常会被包装在 `AggregateException` 中 (如果使用 `.Result` 或 `.Wait()`),但在 `await` 时会解包抛出第一个异常。建议始终使用 `await`。 +3. **避免线程饥饿**:不要在线程池线程中执行长时间的同步 IO 操作。 +4. **不可变性**:尽可能设计不可变对象,天然线程安全。 diff --git a/docs/开发规范/1-编程规约/1.5-OOP规范.md b/docs/开发规范/1-编程规约/1.5-OOP规范.md new file mode 100644 index 0000000..f77aba1 --- /dev/null +++ b/docs/开发规范/1-编程规约/1.5-OOP规范.md @@ -0,0 +1,799 @@ +# 1.5 OOP规范 + +--- + +## 1. 类的职责划分 +### 1.1 单一职责原则 +```csharp +// ❌ 不建议:一个类承担多个职责 +public class User +{ + public int Id { get; set; } + public string Name { get; set; } + public string Email { get; set; } + // 数据访问职责 + public void SaveToDatabase() { } + // 邮件发送职责 + public void SendWelcomeEmail() { } + // 日志记录职责 + public void LogUserAction() { } + // 数据验证职责 + public bool ValidateEmail() { } +} +// ✅ 建议:职责分离 +public class User +{ + public int Id { get; set; } + public string Name { get; set; } + public string Email { get; set; } +} +public class UserRepository +{ + public async Task SaveAsync(User user) + { + // 数据访问逻辑 + } +} +public class UserEmailService +{ + public async Task SendWelcomeEmailAsync(User user) + { + // 邮件发送逻辑 + } +} +public class UserValidator +{ + public bool IsValidEmail(string email) + { + // 验证逻辑 + } +} +``` +### 1.2 类的大小控制 +```csharp +// - 方法数量:建议不超过 20 个 +// - 代码行数:建议不超过 300 行 +// - 依赖数量:建议不超过 5 个 +public class OrderService +{ + // 依赖注入(建议最多5个依赖) + private readonly IOrderRepository _orderRepository; + private readonly IProductService _productService; + private readonly IEmailService _emailService; + private readonly ILogger _logger; + public OrderService( + IOrderRepository orderRepository, + IProductService productService, + IEmailService emailService, + ILogger logger) + { + _orderRepository = orderRepository; + _productService = productService; + _emailService = emailService; + _logger = logger; + } + // 核心业务方法 + public async Task CreateOrderAsync(CreateOrderRequest request) + { + // 方法体不超过30行 + } +} +// ❌ 如果类过大,考虑拆分 +// 例如:将订单创建、订单查询、订单取消拆分为不同的服务类 +public class OrderCreationService { } +public class OrderQueryService { } +public class OrderCancellationService +``` +## 2. 封装原则 +### 2.1 字段私有化 +```csharp +// ❌ 不建议:公开字段 +public class BankAccount +{ + public decimal Balance; // 可被外部直接修改 +} +// ✅ 建议:私有字段 + 公共属性 +public class BankAccount +{ + private decimal _balance; + public decimal Balance + { + get => _balance; + private set => _balance = value; // 只能内部修改 + } + // 通过方法控制状态变化 + public void Deposit(decimal amount) + { + if (amount <= 0) + { + throw new ArgumentException("存款金额必须大于0"); + } + _balance += amount; + } + public void Withdraw(decimal amount) + { + if (amount <= 0) + { + throw new ArgumentException("取款金额必须大于0"); + } + if (amount > _balance) + { + throw new InvalidOperationException("余额不足"); + } + _balance -= amount; + } +} +``` +### 2.2 属性设计 +```csharp +public class Product +{ + // ✅ 自动属性(简单场景) + public int Id { get; set; } + public string Name { get; set; } + // ✅ 只读属性(外部只能读取) + public DateTime CreateTime { get; private set; } + // ✅ 计算属性(不存储值) + public decimal TotalPrice => Price * Quantity; + // ✅ 带验证的属性 + private decimal _price; + public decimal Price + { + get => _price; + set + { + if (value < 0) + { + throw new ArgumentException("价格不能为负数"); + } + _price = value; + } + } + // ✅ 延迟初始化属性 + private List _reviews; + public List Reviews => _reviews ??= new List(); + // ✅ init 访问器(只能在构造时设置) + public string Code { get; init; } + // ✅ required 修饰符(C# 11+) + public required string Category { get; init; } +} +// 使用示例 +var product = new Product +{ + Code = "P001", + Category = "电子产品", // 必须设置 + Price = 999.99m +}; +// product.Code = "P002"; // 编译错误,init 属性不可修改 +``` +### 2.3 对象不变性 +```csharp +// ✅ 不可变对象(线程安全) +public class Money +{ + public decimal Amount { get; } + public string Currency { get; } + public Money(decimal amount, string currency) + { + if (amount < 0) + { + throw new ArgumentException("金额不能为负数"); + } + Amount = amount; + Currency = currency ?? throw new ArgumentNullException(nameof(currency)); + } + // 操作返回新对象,而不修改当前对象 + public Money Add(Money other) + { + if (Currency != other.Currency) + { + throw new InvalidOperationException("货币类型不同"); + } + return new Money(Amount + other.Amount, Currency); + } + public Money Multiply(decimal factor) + { + return new Money(Amount * factor, Currency); + } +} +// ✅ 使用 record 创建不可变对象 +public record Address(string Street, string City, string ZipCode); +// 使用 +var address = new Address("中山路100号", "上海", "200000"); +// address.Street = "新地址"; // 编译错误 +// 创建副本并修改 +var newAddress = address with { Street = "南京路200号" }; +``` +## 3. 继承使用原则 +### 3.1 何时使用继承 +```csharp +// ✅ 正确使用继承 +public abstract class Animal +{ + public string Name { get; set; } + public abstract void MakeSound(); + public virtual void Eat() + { + Console.WriteLine($"{Name} is eating"); + } +} +public class Dog : Animal +{ + public override void MakeSound() + { + Console.WriteLine("Woof!"); + } +} +public class Cat : Animal +{ + public override void MakeSound() + { + Console.WriteLine("Meow!"); + } + public override void Eat() + { + base.Eat(); // 调用基类实现 + Console.WriteLine("Cat loves fish"); + } +} +// ❌ 错误使用继承:仅为复用代码 +public class Stack : List // 不应该继承 List +{ + // Stack 不是一个 List +} +// ✅ 正确:使用组合 +public class Stack +{ + private readonly List _items = new(); + public void Push(int item) => _items.Add(item); + public int Pop() + { + var item = _items[^1]; + _items.RemoveAt(_items.Count - 1); + return item; + } +} +``` +### 3.2 继承层次控制 +```csharp +// ✅ 继承深度建议不超过3层 +// Level 1 +public abstract class Entity +{ + public int Id { get; set; } +} +// Level 2 +public abstract class AuditableEntity : Entity +{ + public DateTime CreateTime { get; set; } + public DateTime? UpdateTime { get; set; } +} +// Level 3 +public class Product : AuditableEntity +{ + public string Name { get; set; } + public decimal Price { get; set; } +} +// ❌ 避免更深的继承层次 +// Level 4 - 过深 +public class SpecialProduct : Product { } // 考虑重新设计 +``` +### 3.3 虚方法使用规范 +```csharp +public abstract class PaymentProcessor +{ + // ✅ 模板方法模式 + public async Task ProcessPaymentAsync(PaymentRequest request) + { + // 1. 验证(子类可重写) + ValidateRequest(request); + // 2. 预处理(子类可重写) + await PreProcessAsync(request); + // 3. 核心处理(子类必须实现) + var result = await ExecutePaymentAsync(request); + // 4. 后处理(子类可重写) + await PostProcessAsync(result); + return result; + } + // 虚方法(可选重写) + protected virtual void ValidateRequest(PaymentRequest request) + { + if (request.Amount <= 0) + { + throw new ArgumentException("支付金额必须大于0"); + } + } + protected virtual Task PreProcessAsync(PaymentRequest request) + { + return Task.CompletedTask; + } + // 抽象方法(必须实现) + protected abstract Task ExecutePaymentAsync(PaymentRequest request); + protected virtual Task PostProcessAsync(PaymentResult result) + { + return Task.CompletedTask; + } +} +// 具体实现 +public class AlipayProcessor : PaymentProcessor +{ + protected override async Task ExecutePaymentAsync(PaymentRequest request) + { + // 支付宝支付逻辑 + return new PaymentResult { Success = true }; + } + protected override async Task PostProcessAsync(PaymentResult result) + { + // 发送支付通知 + await base.PostProcessAsync(result); + } +} +``` +## 4. 组合优于继承 +### 4.1 使用组合的场景 +```csharp +// ❌ 错误:使用继承实现不同功能组合 +public class FlyingSwimmingAnimal : Animal { } // 会飞会游泳的动物? +// ✅ 正确:使用组合和接口 +public interface IFlyable +{ + void Fly(); +} +public interface ISwimmable +{ + void Swim(); +} +public class Duck : Animal, IFlyable, ISwimmable +{ + private readonly FlyingAbility _flyingAbility = new(); + private readonly SwimmingAbility _swimmingAbility = new(); + public void Fly() => _flyingAbility.Fly(); + public void Swim() => _swimmingAbility.Swim(); + public override void MakeSound() + { + Console.WriteLine("Quack!"); + } +} +public class FlyingAbility +{ + public void Fly() + { + Console.WriteLine("Flying in the sky"); + } +} +public class SwimmingAbility +{ + public void Swim() + { + Console.WriteLine("Swimming in the water"); + } +} +``` +### 4.2 策略模式代替继承 +```csharp +// ✅ 使用策略模式提供不同行为 +public interface IShippingStrategy +{ + decimal CalculateCost(decimal weight, decimal distance); +} +public class StandardShipping : IShippingStrategy +{ + public decimal CalculateCost(decimal weight, decimal distance) + { + return weight * 0.5m + distance * 0.1m; + } +} +public class ExpressShipping : IShippingStrategy +{ + public decimal CalculateCost(decimal weight, decimal distance) + { + return weight * 1.0m + distance * 0.2m + 20m; + } +} +public class Order +{ + private IShippingStrategy _shippingStrategy; + public void SetShippingStrategy(IShippingStrategy strategy) + { + _shippingStrategy = strategy ?? throw new ArgumentNullException(nameof(strategy)); + } + public decimal CalculateShippingCost(decimal weight, decimal distance) + { + if (_shippingStrategy == null) + { + throw new InvalidOperationException("运输策略未设置"); + } + return _shippingStrategy.CalculateCost(weight, distance); + } +} +``` +## 5. 接口设计原则 +### 5.1 接口命名与定义 +```csharp +// ✅ 接口命名以 I 开头 +public interface ICustomerRepository +{ + Task GetByIdAsync(int id); + Task> GetAllAsync(); + Task AddAsync(Customer customer); + Task UpdateAsync(Customer customer); + Task DeleteAsync(int id); +} +// ✅ 能力接口(形容词) +public interface IDisposable +{ + void Dispose(); +} +public interface IComparable +{ + int CompareTo(T other); +} +// ✅ 服务接口(名词) +public interface IEmailService +{ + Task SendAsync(string to, string subject, string body); +} +public interface ILogger +{ + void LogInformation(string message); + void LogError(Exception ex, string message); +} +``` +### 5.2 接口隔离 +```csharp +// ❌ 不建议:臃肿的接口 +public interface IRepository +{ + // CRUD 操作 + void Add(); + void Update(); + void Delete(); + void Get(); + // 批量操作 + void BulkAdd(); + void BulkUpdate(); + void BulkDelete(); + // 搜索功能 + void Search(); + void AdvancedSearch(); + // 导出功能 + void ExportToExcel(); + void ExportToPdf(); +} +// ✅ 建议:接口隔离 +public interface IReadRepository +{ + Task GetByIdAsync(int id); + Task> GetAllAsync(); +} +public interface IWriteRepository +{ + Task AddAsync(T entity); + Task UpdateAsync(T entity); + Task DeleteAsync(int id); +} +public interface IBulkRepository +{ + Task BulkAddAsync(IEnumerable entities); + Task BulkUpdateAsync(IEnumerable entities); +} +public interface ISearchableRepository +{ + Task> SearchAsync(SearchCriteria criteria); +} +// 组合使用 +public interface ICustomerRepository : IReadRepository, + IWriteRepository, + ISearchableRepository +{ + // 特定于 Customer 的方法 + Task GetByEmailAsync(string email); +} +``` +### 5.3 显式接口实现 +```csharp +public interface IAnimal +{ + void Move(); +} +public interface IRobot +{ + void Move(); +} +// ✅ 显式接口实现解决方法冲突 +public class RobotDog : IAnimal, IRobot +{ + // 隐式实现(默认) + public void Move() + { + Console.WriteLine("RobotDog moving"); + } + // 显式实现 IAnimal + void IAnimal.Move() + { + Console.WriteLine("Animal walking"); + } + // 显式实现 IRobot + void IRobot.Move() + { + Console.WriteLine("Robot moving mechanically"); + } +} +// 使用 +var robotDog = new RobotDog(); +robotDog.Move(); // "RobotDog moving" +((IAnimal)robotDog).Move(); // "Animal walking" +((IRobot)robotDog).Move(); // "Robot moving mechanically" +``` +## 6. 抽象类使用场景 +### 6.1 抽象类 vs 接口 +```csharp +// ✅ 使用抽象类:有共同实现逻辑 +public abstract class Document +{ + // 公共属性 + public string Title { get; set; } + public DateTime CreateTime { get; set; } + // 公共方法实现 + public void Save() + { + ValidateBeforeSave(); + PerformSave(); + LogSave(); + } + // 模板方法 + protected virtual void ValidateBeforeSave() + { + if (string.IsNullOrWhiteSpace(Title)) + { + throw new ValidationException("标题不能为空"); + } + } + // 抽象方法(强制子类实现) + protected abstract void PerformSave(); + // 虚方法(可选重写) + protected virtual void LogSave() + { + Console.WriteLine($"Document '{Title}' saved at {DateTime.Now}"); + } +} +public class PdfDocument : Document +{ + protected override void PerformSave() + { + // PDF 特定的保存逻辑 + Console.WriteLine("Saving as PDF"); + } +} +public class WordDocument : Document +{ + protected override void PerformSave() + { + // Word 特定的保存逻辑 + Console.WriteLine("Saving as Word document"); + } + protected override void ValidateBeforeSave() + { + base.ValidateBeforeSave(); + // 额外的验证逻辑 + } +} +// ✅ 使用接口:定义契约,无共同实现 +public interface INotificationSender +{ + Task SendAsync(string recipient, string message); +} +public class EmailSender : INotificationSender +{ + public async Task SendAsync(string recipient, string message) + { + // 邮件发送实现 + } +} +public class SmsSender : INotificationSender +{ + public async Task SendAsync(string recipient, string message) + { + // 短信发送实现 + } +} +``` +### 6.2 抽象类设计要点 +```csharp +public abstract class BaseEntity +{ + // ✅ 抽象类可以有构造函数 + protected BaseEntity() + { + CreateTime = DateTime.UtcNow; + Id = Guid.NewGuid(); + } + // ✅ 可以有字段 + private readonly List _domainEvents = new(); + // 公共属性 + public Guid Id { get; private set; } + public DateTime CreateTime { get; private set; } + public DateTime? UpdateTime { get; protected set; } + // ✅ 受保护的方法供子类使用 + protected void AddDomainEvent(DomainEvent domainEvent) + { + _domainEvents.Add(domainEvent); + } + protected void MarkAsUpdated() + { + UpdateTime = DateTime.UtcNow; + } + // 抽象方法 + public abstract void Validate(); +} +public class Customer : BaseEntity +{ + public string Name { get; set; } + public string Email { get; set; } + public override void Validate() + { + if (string.IsNullOrWhiteSpace(Name)) + { + throw new ValidationException("客户名称不能为空"); + } + if (string.IsNullOrWhiteSpace(Email)) + { + throw new ValidationException("邮箱不能为空"); + } + } + public void UpdateEmail(string newEmail) + { + Email = newEmail; + MarkAsUpdated(); // 使用基类方法 + AddDomainEvent(new CustomerEmailChangedEvent(Id, newEmail)); + } +} +``` +## 7. 密封类使用 +### 7.1 何时使用 `sealed` +```csharp +// ✅ 防止被继承(性能优化、设计意图) +public sealed class ConfigurationManager +{ + private readonly Dictionary _settings = new(); + public string GetSetting(string key) + { + return _settings.TryGetValue(key, out var value) ? value : null; + } +} +// ❌ 无法继承 +// public class CustomConfigManager : ConfigurationManager { } // 编译错误 +// ✅ 工具类密封 +public sealed class StringHelper +{ + private StringHelper() { } // 私有构造防止实例化 + public static string Truncate(string value, int maxLength) + { + if (string.IsNullOrEmpty(value) || value.Length <= maxLength) + { + return value; + } + return value.Substring(0, maxLength) + "..."; + } +} +// ✅ 密封重写方法(防止进一步重写) +public class BaseProcessor +{ + public virtual void Process() + { + Console.WriteLine("Base processing"); + } +} +public class DerivedProcessor : BaseProcessor +{ + // 密封此方法,子类不能再重写 + public sealed override void Process() + { + Console.WriteLine("Derived processing"); + base.Process(); + } +} +public class FurtherDerived : DerivedProcessor +{ + // ❌ 编译错误:不能重写密封方法 + // public override void Process() { } +} +``` +## 8. 访问修饰符使用规范 +### 8.1 访问级别选择 +```csharp +public class Product +{ + // ✅ private - 只在类内部使用 + private decimal _costPrice; + private List _priceHistory = new(); + // ✅ private protected - 仅当前类和派生类访问(同一程序集) + private protected void RecordPriceChange(decimal oldPrice, decimal newPrice) + { + _priceHistory.Add(new PriceHistory(oldPrice, newPrice)); + } + // ✅ protected - 当前类和派生类可访问 + protected decimal CostPrice => _costPrice; + // ✅ internal - 同一程序集内可访问 + internal void SetCostPrice(decimal cost) + { + _costPrice = cost; + } + // ✅ protected internal - 同一程序集或派生类 + protected internal decimal CalculateMargin() + { + return Price - _costPrice; + } + // ✅ public - 所有地方可访问 + public decimal Price { get; set; } + public string Name { get; set; } + public decimal GetProfitMargin() + { + return (Price - _costPrice) / Price * 100; + } +} +// 访问修饰符决策树: +// 1. 只在当前类使用? -> private +// 2. 需要子类访问? -> protected +// 3. 同一程序集内使用? -> internal +// 4. 需要外部调用? -> public +// 5. 同程序集或子类? -> protected internal +// 6. 仅同程序集的子类? -> private protected +``` +### 8.2 最小权限原则 +```csharp +// ✅ 建议:默认使用最小访问权限 +public class OrderProcessor +{ + // 私有字段 + private readonly IOrderRepository _repository; + private readonly ILogger _logger; + + // 公共构造函数 + public OrderProcessor(IOrderRepository repository, ILogger logger) + { + _repository = repository; + _logger = logger; + } + + // 公共方法(对外API) + public async Task ProcessOrderAsync(int orderId) + { + var order = await GetOrderAsync(orderId); + ValidateOrder(order); + await SaveOrderAsync(order); + return order; + } + + // 私有方法(内部实现细节) + private async Task GetOrderAsync(int orderId) + { + return await _repository.GetByIdAsync(orderId); + } + + private void ValidateOrder(Order order) + { + if (order == null) + { + throw new ArgumentNullException(nameof(order)); + } + } + + private async Task SaveOrderAsync(Order order) + { + await _repository.UpdateAsync(order); + _logger.LogInformation("Order {OrderId} saved", order.Id); + } +} + +// ❌ 不建议:不必要的公开 +public class BadOrderProcessor +{ + // 不应该公开 + public IOrderRepository Repository { get; set; } + + // 不应该公开内部实现 + public void ValidateOrder(Order order) { } + public async Task SaveOrderAsync(Order order) { } +} +``` \ No newline at end of file diff --git a/docs/开发规范/2-异常日志/2.1-日志规约.md b/docs/开发规范/2-异常日志/2.1-日志规约.md new file mode 100644 index 0000000..2e9f3ad --- /dev/null +++ b/docs/开发规范/2-异常日志/2.1-日志规约.md @@ -0,0 +1,45 @@ +# 2.1 日志规约 + +--- + +## 【强制】 + +1. **日志组件**: + * **底层框架**:**Aegis.Core.Logs** 使用 **NLog** 作为日志记录提供程序。 + * **集中收集**:生产环境必须配置 **Seq**或**ELK** 作为日志的集中收集与分析端点。 + * **抽象使用**:代码中应依赖 `Microsoft.Extensions.Logging.ILogger` 接口进行日志记录,避免直接依赖 NLog 的特定 API。 + +2. **API 请求日志**: + * **自动化记录**:Web API 项目可以通过 **AOP 过滤器**(如 `[TypeFilter(typeof(LogAttribute))]` 或全局过滤器)自动记录 HTTP 请求的入参、出参、执行时间及客户端信息。 + * **避免冗余**:**禁止**在 Controller 方法内部手动记录 "进入方法"、"离开方法" 等流水账日志,除非有特殊的业务追踪需求。 + +3. **异常记录规范**: + * **完整堆栈**:记录异常时,**必须**将 `Exception` 对象作为 `LogError` 方法的第一个参数传入。 + * *反例*:`_logger.LogError($"处理失败:{ex.Message}");` (丢失堆栈信息) + * *正例*:`_logger.LogError(ex, "处理订单 {OrderId} 失败", orderId);` + +4. **结构化日志**: + * **使用模板**:必须使用**消息模板**(Message Templates)语法(使用 `{Property}` 占位符),**禁止**使用字符串拼接或 `string.Format`。这确保日志在 Seq 中可被索引和查询。 + * *正例*:`_logger.LogInformation("用户 {UserId} 修改了状态为 {Status}", userId, status);` + +5. **默认上下文信息**: + * **自动注入**:框架层(`Aegis.Core.Logs`)已统一配置日志 Layout,自动包含 **RequestId** (`${request-id}`) 和 **调用方法名** (`${callsite}`)。 + * **无需手动记录**:开发者在记录日志时,**无需**在消息体中重复记录当前方法名或 RequestId。 + * *说明*:默认 Layout 格式为 `${date}|${level:uppercase=true}|${request-id}|${callsite:includeNamespace=false}|${message}|${exception}`。 + +## 【推荐】 + +1. **日志级别定义**: + * **Trace/Debug**:仅用于开发环境调试,生产环境默认关闭。 + * **Information**:记录系统关键流程节点(如:定时任务开始/结束、关键状态流转)。 + * **Warning**:记录非预期的但系统可自动恢复的业务异常(如:参数校验失败、第三方接口偶尔超时)。 + * **Error**:记录导致当前请求失败的系统异常(如:数据库连接失败、空指针异常)。 + * **Critical**:记录导致系统崩溃或无法提供服务的致命错误。 + +2. **日志保留策略**: + * 本地文件日志建议按天滚动(`RollingFile`),保留 7-15 天。 + * Seq 服务器日志根据磁盘容量配置保留策略(通常 30 天)。 + +3. **敏感信息脱敏**: + * 严禁在日志中明文记录密码、密钥、身份证号、银行卡号等敏感信息。 + diff --git a/docs/开发规范/2-异常日志/2.2-异常处理.md b/docs/开发规范/2-异常日志/2.2-异常处理.md new file mode 100644 index 0000000..6c64faf --- /dev/null +++ b/docs/开发规范/2-异常日志/2.2-异常处理.md @@ -0,0 +1,122 @@ +# 2.2 异常处理 + +--- + +## 【强制】 + +1. **业务异常处理**: + * **优先校验**:应优先使用条件判断(`if`)来检查业务规则是否满足,在发现问题时提前拦截。 + * **抛出异常**:当校验不通过需中断流程时,应抛出 `BusinessException` 或其子类,而不是 `System.Exception`。 + * **消息友好**:异常消息(Message)将直接作为错误提示返回给前端/客户端,必须确保消息内容用户友好且无敏感技术信息。 + * **记录异常**:在业务逻辑中抛出异常时,必须记录异常信息和堆栈到日志中,以便后续分析和排查问题。 + * *正例*: + ```csharp + if (order.Status != OrderStatus.Created) + { + throw new BusinessException("当前订单状态不允许取消"); + } + ``` + +2. **全局异常处理**: + * **中间件接管**:严禁在 Controller 层进行通用的 `try-catch` 捕获。Aegis 框架已内置全局异常处理中间件,会自动捕获未处理异常并转换为标准 JSON 响应。 + * **响应格式**:框架会将未处理异常统一转换为如下格式: + ```json + { + "code": 500, + "message": "服务器内部错误 / 业务错误提示", + "data": null, + "success": false + } + ``` + +3. **必须通过预检查规避可预见的异常**:对于 `NullReferenceException`, `ArgumentNullException`, `IndexOutOfRangeException` 等可以通过代码检查来避免的运行时异常,不应该使用 `try-catch` 来处理。 + + + *说明*:异常处理的性能开销远高于条件判断。编写健壮的代码,通过防御性编程(如卫语句)来处理可预见的问题,是程序员的基本素养。 + + *反例*: + ```csharp + try + { + // obj 可能为 null + obj.DoSomething(); + } + catch (NullReferenceException ex) + { + _logger.LogError(ex, "obj was null"); + } + ``` + + *正例*: + ```csharp + if (obj is null) + { + throw new ArgumentNullException(nameof(obj)); + } + obj.DoSomething(); + ``` + +2. **禁止使用异常作为流程控制**:异常是为程序运行中的意外情况设计的,不应用于正常的业务逻辑判断和流程跳转。 + +3. **精确捕获异常类型**:`catch` 时应尽可能捕获具体的异常类型,而不是宽泛的 `System.Exception`。对不同的异常类型应有不同的处理逻辑。 + + *说明*:对大段代码进行笼统的 `try-catch`,会使程序无法对特定错误做出正确响应,不利于问题定位和系统稳定性。 + + *正例*: + ```csharp + try + { + // ... 数据库操作 + } + catch (SqlException ex) + { + // 处理数据库相关的特定错误 + _logger.LogError(ex, "A database error occurred."); + } + catch (TimeoutException ex) + { + // 处理连接超时错误 + _logger.LogWarning(ex, "The database connection timed out."); + } + ``` + +4. **捕获后必须处理或转抛**:捕获异常后,必须进行处理(如记录日志、事务回滚、向用户返回错误信息等)。如果当前层无法处理,应使用 `throw;` 语句将原始异常向上抛出。严禁“吞掉”异常(即空的 `catch` 块)。 + +5. **必须使用 `using` 语句管理资源**:对于实现了 `IDisposable` 接口的资源对象(如流、数据库连接、HttpClient 等),必须使用 `using` 语句来确保资源的正确释放。 + + *说明*:`using` 语句会自动生成 `try-finally` 块,并调用 `Dispose()` 方法,这是 .NET 中管理非托管资源的最安全、最简洁的方式。 + + *正例*: + ```csharp + using (var stream = new FileStream("path/to/file", FileMode.Open)) + { + // ... 对 stream 的操作 + } + // 此处 stream 已被自动关闭和释放 + ``` + +6. **禁止在 `finally` 块中使用 `return`**:`finally` 块中的 `return` 会覆盖 `try` 或 `catch` 块中的 `return` 或异常,导致非预期的行为。 + +## 【推荐】 + +1. **启用并遵循可空引用类型 (`Nullable Reference Types`)**:在项目(`.csproj`)中启用 `enable`,让编译器帮助你从静态层面发现潜在的 `NullReferenceException`。 + +2. **优雅地处理空引用**: + - **级联调用**:使用空条件运算符 `?.` 和 `?[]` 来安全地访问可能为 null 的对象的成员或索引。 + *正例*: `string? postalCode = customer?.Address?.PostalCode;` + - **返回值**:方法的返回值可以为 null,但必须在方法签名和 XML 注释中明确指出。调用方有责任处理 null 情况。 + +3. **使用自定义异常表达业务错误**:应定义有具体业务含义的自定义异常(继承自 `System.Exception`),而不是直接抛出泛泛的 `new Exception()`。 + + *正例*: + ```csharp + public class OrderNotFoundException : Exception + { + public OrderNotFoundException(int orderId) + : base($"Order with ID {orderId} was not found.") { } + } + ``` + +## 【参考】 + +1. **API 的错误响应**:对于对外暴露的 API(如 ASP.NET Core Web API),应建立一个全局异常处理中间件。该中间件负责捕获所有未处理的异常,记录日志,并将其转换为统一、标准的 JSON 错误响应格式返回给客户端,而不是直接暴露异常堆栈信息。 diff --git a/server/MiAssessment/src/MiAssessment.Admin/appsettings.json b/server/MiAssessment/src/MiAssessment.Admin/appsettings.json index 9e1f775..22acb96 100644 --- a/server/MiAssessment/src/MiAssessment.Admin/appsettings.json +++ b/server/MiAssessment/src/MiAssessment.Admin/appsettings.json @@ -1,13 +1,13 @@ { "ConnectionStrings": { - "DefaultConnection": "Server=127.0.0.1;uid=sa;pwd=1qaz!QAZ;Database=MiAssessment_Admin;MultipleActiveResultSets=true;pooling=true;min pool size=5;max pool size=32767;connect timeout=20;Encrypt=True;TrustServerCertificate=True;", - "BusinessConnection": "Server=127.0.0.1;uid=sa;pwd=1qaz!QAZ;Database=MiAssessment_Business;MultipleActiveResultSets=true;pooling=true;min pool size=5;max pool size=32767;connect timeout=20;Encrypt=True;TrustServerCertificate=True;", - "Redis": "127.0.0.1:6379,abortConnect=false,connectTimeout=5000" + "DefaultConnection": "Server=192.168.195.15,1433;uid=sa;pwd=Dbt@com@123;Database=MiAssessment_Admin;MultipleActiveResultSets=true;pooling=true;min pool size=5;max pool size=32767;connect timeout=20;Encrypt=True;TrustServerCertificate=True;", + "BusinessConnection": "Server=192.168.195.15,1433;uid=sa;pwd=Dbt@com@123;Database=MiAssessment_Business;MultipleActiveResultSets=true;pooling=true;min pool size=5;max pool size=32767;connect timeout=20;Encrypt=True;TrustServerCertificate=True;", + "Redis": "192.168.195.15:6379,defaultDatabase=2,abortConnect=false,connectTimeout=5000" }, "Jwt": { - "Secret": "{{JWT_SECRET_AT_LEAST_32_CHARACTERS}}", - "Issuer": "{{PROJECT_NAME}}.Admin", - "Audience": "{{PROJECT_NAME}}.Admin.Client", + "Secret": "MiAssessmentAdminSecretKey2024!@#$%^&*()_+", + "Issuer": "MiAssessment.Admin", + "Audience": "MiAssessment.Admin.Client", "ExpireMinutes": 1440 }, "Logging": { diff --git a/server/MiAssessment/src/MiAssessment.Api/appsettings.json b/server/MiAssessment/src/MiAssessment.Api/appsettings.json index 1419727..e36d8ba 100644 --- a/server/MiAssessment/src/MiAssessment.Api/appsettings.json +++ b/server/MiAssessment/src/MiAssessment.Api/appsettings.json @@ -1,7 +1,7 @@ { "ConnectionStrings": { - "DefaultConnection": "Server=127.0.0.1;uid=sa;pwd=1qaz!QAZ;Database=MiAssessment_Business;MultipleActiveResultSets=true;pooling=true;min pool size=5;max pool size=32767;connect timeout=20;Encrypt=True;TrustServerCertificate=True;", - "Redis": "127.0.0.1:6379,abortConnect=false,connectTimeout=5000" + "DefaultConnection": "Server=192.168.195.15,1433;uid=sa;pwd=Dbt@com@123;Database=MiAssessment_Business;MultipleActiveResultSets=true;pooling=true;min pool size=5;max pool size=32767;connect timeout=20;Encrypt=True;TrustServerCertificate=True;", + "Redis": "192.168.195.15:6379,defaultDatabase=2,abortConnect=false,connectTimeout=5000" }, "AppSettings": { "IsTestEnvironment": true @@ -9,26 +9,26 @@ "WechatPaySettings": { "DefaultMerchant": { "Name": "默认商户", - "MchId": "{{WECHAT_MCH_ID}}", - "AppId": "{{WECHAT_APP_ID}}", - "Key": "{{WECHAT_API_KEY}}", + "MchId": "YOUR_WECHAT_MCH_ID", + "AppId": "YOUR_WECHAT_APP_ID", + "Key": "YOUR_WECHAT_API_KEY", "OrderPrefix": "ORD", "Weight": 1, - "NotifyUrl": "{{WECHAT_NOTIFY_URL}}" + "NotifyUrl": "https://your-domain.com/api/payment/notify" }, "Merchants": [], "Miniprograms": [], "UnifiedOrderUrl": "https://api.mch.weixin.qq.com/pay/unifiedorder", "ShippingNotifyUrl": "https://api.weixin.qq.com/wxa/sec/order/upload_shipping_info", - "NotifyBaseUrl": "{{API_BASE_URL}}" + "NotifyBaseUrl": "https://your-domain.com" }, "AmapSettings": { - "ApiKey": "{{AMAP_API_KEY}}" + "ApiKey": "YOUR_AMAP_API_KEY" }, "JwtSettings": { - "Secret": "{{JWT_SECRET_AT_LEAST_32_CHARACTERS}}", - "Issuer": "{{PROJECT_NAME}}", - "Audience": "{{PROJECT_NAME}}Users", + "Secret": "MiAssessmentApiSecretKey2024!@#$%^&*()_+", + "Issuer": "MiAssessment", + "Audience": "MiAssessmentUsers", "ExpirationMinutes": 1440, "RefreshTokenExpirationDays": 7 },