# 编码约定 **分析日期:** 2026-02-28 ## 项目概述 此代码库是一个基于 **.NET 10.0** 的 **ASP.NET Core Web API** 认证授权服务,采用 **OpenIddict** 实现 OAuth2/OIDC 协议,并使用 **ASP.NET Core Identity** 进行用户和角色管理。 ## 命名模式 ### 文件命名 **约定:** 使用 PascalCase 命名法 **示例:** - `UsersController.cs` - `TokenController.cs` - `LoginViewModel.cs` - `OpenIddictSetup.cs` ### 类与类型命名 **约定:** 使用 PascalCase 命名法 **示例:** ```csharp public class UsersController : ControllerBase public class LoginViewModel public class CreateUserDto public static class OpenIddictSetup ``` ### 方法命名 **约定:** 使用 PascalCase 命名法,动词或动词短语开头 **示例:** ```csharp public async Task> GetUsers(...) public async Task> CreateUser(CreateUserDto dto) public async Task UpdateUser(long id, UpdateUserDto dto) private async Task CreateAuditLog(...) ``` ### 变量与属性命名 **约定:** 使用 camelCase 命名法 **示例:** ```csharp var query = platformDbContext.Users.AsQueryable(); var totalCount = await query.CountAsync(); var user = await userManager.FindByNameAsync(request.Username); var roles = await userManager.GetRolesAsync(user); ``` ### 常量与枚举 **约定:** 使用 PascalCase 命名法 **示例:** ```csharp errors.InvalidGrant Errors.UnsupportedGrantType Statuses.Valid AuthorizationTypes.Permanent ``` ## 代码风格 ### 命名空间 **约定:** 使用文件级别命名空间(File-scoped namespace),C# 10+ 特性 **示例:** ```csharp namespace Fengling.AuthService.Controllers; namespace Fengling.AuthService.ViewModels; namespace Fengling.AuthService.Configuration; ``` ### 主构造函数 **约定:** 优先使用 C# 12 主构造函数(Primary Constructors) **示例:** ```csharp // 主构造函数方式(推荐) public class UsersController( UserManager userManager, RoleManager roleManager, ILogger logger, PlatformDbContext platformDbContext) : ControllerBase { } // 传统构造函数方式(部分文件使用) public class RolesController : ControllerBase { private readonly PlatformDbContext _context; private readonly RoleManager _roleManager; public RolesController( PlatformDbContext context, RoleManager roleManager, ...) { _context = context; _roleManager = roleManager; } } ``` ### 属性与字段 **约定:** 私有字段使用下划线前缀(`_camelCase`),公共属性使用 PascalCase **示例:** ```csharp public class RolesController : ControllerBase { private readonly PlatformDbContext _context; private readonly RoleManager _roleManager; private readonly UserManager _userManager; private readonly ILogger _logger; } ``` ### 使用 var 推断类型 **约定:** 优先使用 `var` 进行类型推断,复杂类型或返回类型明确时显式声明 **示例:** ```csharp var query = platformDbContext.Users.AsQueryable(); var user = await platformDbContext.Users.FindAsync(id); var totalCount = await query.CountAsync(); ActionResult result = Ok(new { ... }); IEnumerable destinations = GetDestinations(claim, principal); ``` ## 导入组织 ### using 指令顺序 **约定:** 按以下顺序排列 using 指令 1. 系统命名空间(`System.*`) 2. 项目依赖包命名空间(`Microsoft.*`、`OpenIddict.*`、`Fengling.*`) 3. 当前项目命名空间(`Fengling.AuthService.*`) 4. 静态导入(`using static`) **示例:** ```csharp using System.ComponentModel.DataAnnotations; using System.Security.Claims; using System.Security.Cryptography; using Fengling.Platform.Domain.AggregatesModel.UserAggregate; using Fengling.Platform.Domain.AggregatesModel.RoleAggregate; using Fengling.Platform.Infrastructure; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using OpenIddict.Abstractions; using OpenIddict.Server.AspNetCore; using Fengling.AuthService.Configuration; using Fengling.AuthService.ViewModels; using static OpenIddict.Abstractions.OpenIddictConstants; ``` ### 命名空间别名 **约定:** 必要时使用命名空间别名简化长命名空间引用 **示例:** ```csharp using static OpenIddict.Abstractions.OpenIddictConstants; ``` ## 控制器模式 ### API 控制器约定 **约定:** 所有 API 控制器使用以下属性 ```csharp [ApiController] [Route("api/[controller]")] [Authorize] public class UsersController(...) : ControllerBase ``` ### 路由约定 **约定:** - 集合资源使用复数形式:`api/users` - 单个资源使用 `/{id}` 形式:`api/users/{id}` - 特殊端点使用 `connect` 前缀:`connect/token`、`connect/authorize` **示例:** ```csharp [HttpGet] // GET api/users [HttpGet("{id}")] // GET api/users/123 [HttpPost] // POST api/users [HttpPut("{id}")] // PUT api/users/123 [HttpDelete("{id}")] // DELETE api/users/123 [HttpPut("{id}/password")] // PUT api/users/123/password ``` ### 异步模式 **约定:** 所有数据库和 I/O 操作使用 async/await 模式 **示例:** ```csharp public async Task> GetUsers( [FromQuery] int page = 1, [FromQuery] int pageSize = 10, [FromQuery] string? userName = null, [FromQuery] string? email = null, [FromQuery] string? tenantCode = null) { var query = platformDbContext.Users.AsQueryable(); if (!string.IsNullOrEmpty(userName)) { query = query.Where(u => u.UserName!.Contains(userName)); } var totalCount = await query.CountAsync(); var users = await query .OrderByDescending(u => u.CreatedTime) .Skip((page - 1) * pageSize) .Take(pageSize) .ToListAsync(); return Ok(new { items = users, totalCount, page, pageSize }); } ``` ## 错误处理 ### 控制器错误返回 **约定:** 使用标准 HTTP 状态码返回错误 **示例:** ```csharp // 资源不存在 if (user == null) { return NotFound(); } // 请求数据验证失败 return BadRequest(result.Errors); // 业务规则错误 if (role.IsSystem) { return BadRequest("系统角色不能修改"); } // 操作成功无内容 return NoContent(); // 创建成功 return CreatedAtAction(nameof(GetUser), new { id = user.Id }, user); // OpenIddict 错误响应 return BadRequest(new OpenIddictResponse { Error = Errors.InvalidGrant, ErrorDescription = "用户名或密码错误" }); ``` ### 异常抛出 **约定:** 在无法恢复的错误情况下抛出 `InvalidOperationException` **示例:** ```csharp var request = HttpContext.GetOpenIddictServerRequest() ?? throw new InvalidOperationException("OpenIddict request is null"); var user = await userManager.GetUserAsync(User) ?? throw new InvalidOperationException("The user details cannot be retrieved."); ``` ## 数据传输对象(DTO) ### DTO 定义位置 **约定:** DTO 类定义在对应控制器文件的末尾 **示例:** ```csharp // 位于 UsersController.cs 末尾 public class CreateUserDto { public string UserName { get; set; } = string.Empty; public string Email { get; set; } = string.Empty; public string RealName { get; set; } = string.Empty; public string? Phone { get; set; } public long? TenantId { get; set; } public List RoleIds { get; set; } = new(); public string Password { get; set; } = string.Empty; public bool EmailConfirmed { get; set; } public bool IsActive { get; set; } = true; } public class UpdateUserDto { public string Email { get; set; } = string.Empty; public string RealName { get; set; } = string.Empty; public string? Phone { get; set; } public bool EmailConfirmed { get; set; } public bool IsActive { get; set; } = true; } ``` ### DTO 属性命名 **约定:** 使用 PascalCase 命名法,与 JSON 序列化配置保持一致 **示例:** ```csharp public class RegisterViewModel { [Required(ErrorMessage = "租户编号不能为空")] [StringLength(10, MinimumLength = 4, ErrorMessage = "租户编号长度必须在4·10个字符之间")] public string TenantCode { get; set; } [Required(ErrorMessage = "用户名不能为空")] [StringLength(50, MinimumLength = 3, ErrorMessage = "用户名长度必须在3-50个字符之间")] public string Username { get; set; } = default!; [Required(ErrorMessage = "邮箱不能为空")] [EmailAddress(ErrorMessage = "请输入有效的邮箱地址")] public string Email { get; set; } = default!; } ``` ## 日志记录 ### 日志框架 **约定:** 使用 `Microsoft.Extensions.Logging` 框架,通过依赖注入 `ILogger` 使用 **示例:** ```csharp public class UsersController( UserManager userManager, RoleManager roleManager, ILogger logger, // 注入日志记录器 PlatformDbContext platformDbContext) : ControllerBase { } ``` ### Serilog 配置 **约定:** 在 `Program.cs` 中配置 Serilog,使用配置文件和代码配置结合 **示例:** ```csharp Log.Logger = new LoggerConfiguration() .ReadFrom.Configuration(builder.Configuration) .Enrich.FromLogContext() .WriteTo.Console(outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}") .CreateLogger(); builder.Host.UseSerilog(); ``` ## 审计日志 ### 审计日志模式 **约定:** 关键业务操作需要记录审计日志,包括操作者、操作类型、操作目标等信息 **示例:** ```csharp private async Task CreateAuditLog( string operation, string action, string targetType, long? targetId, string? targetName, string? oldValue = null, string? newValue = null) { var userName = User.FindFirstValue(ClaimTypes.NameIdentifier) ?? User.Identity?.Name ?? "system"; var tenantId = User.FindFirstValue("TenantId"); var log = new AuditLog { Operator = userName, TenantId = tenantId, Operation = operation, Action = action, TargetType = targetType, TargetId = targetId, TargetName = targetName, IpAddress = HttpContext.Connection.RemoteIpAddress?.ToString() ?? "unknown", Status = "success", OldValue = oldValue, NewValue = newValue, }; platformDbContext.AuditLogs.Add(log); await platformDbContext.SaveChangesAsync(); } ``` ## 依赖注入 ### 服务注册 **约定:** 在 `Program.cs` 中注册所有服务,使用链式调用 **示例:** ```csharp builder.Services.AddPlatformCore(options => { options.UseNpgsql(connectionString); options.UseOpenIddict(); }).AddIdentity() .AddEntityFrameworkStores() .AddDefaultTokenProviders(); builder.Services.AddOpenIddictConfiguration(builder.Configuration); builder.Services.AddMediatR(x => x.RegisterServicesFromAssemblies( typeof(PlatformDbContext).Assembly, Assembly.GetExecutingAssembly()) .AddCommandLockBehavior() .AddKnownExceptionValidationBehavior() .AddUnitOfWorkBehaviors() ); ``` ### 构造函数注入 **约定:** 优先使用构造函数注入,通过主构造函数简化 ## 配置管理 ### 配置文件 **约定:** 使用 `appsettings.json` 系列文件进行配置 **文件结构:** - `appsettings.json` - 默认配置 - `appsettings.Development.json` - 开发环境配置 - `appsettings.Testing.json` - 测试环境配置 ### 配置读取 **约定:** 使用 `IConfiguration` 接口和强类型 `IOptions` 模式 **示例:** ```csharp var connectionString = builder.Configuration.GetConnectionString("DefaultConnection"); var isTesting = builder.Configuration.GetValue("Testing", false); ``` ## 特性与属性使用 ### 常用特性 **约定:** 合理使用以下特性 ```csharp [ApiController] // API 控制器 [Route("api/[controller]")] // 路由 [Authorize] // 需要授权 [AllowAnonymous] // 允许匿名访问 [HttpGet] [HttpPost] [HttpPut] [HttpDelete] // HTTP 方法 [FromQuery] // 从查询参数绑定 [FromBody] // 从请求体绑定 [FromRoute] // 从路由参数绑定 [Required] // 必需验证 [StringLength] // 字符串长度验证 [EmailAddress] // 邮箱格式验证 [Compare] // 字段比较验证 [DataType] // 数据类型 ``` ## 注释规范 ### 代码内注释 **约定:** 关键业务逻辑添加注释说明,使用英文或中文均可,保持一致 **示例:** ```csharp // 设置 Claim Destinations foreach (var claim in principal.Claims) { claim.SetDestinations(GetDestinations(claim, principal)); } // 明确处理租户 ID - 这是业务关键信息 case "tenant_id": yield return OpenIddictConstants.Destinations.AccessToken; yield break; // Never include the security stamp in the access and identity tokens case "AspNet.Identity.SecurityStamp": yield break; ``` --- *编码约定分析:2026-02-28*