fengling-auth-service/.planning/codebase/CONVENTIONS.md
movingsam 2a60caae80 docs(architecture): 添加系统架构分析文档
- 描述整体基于ASP.NET Core的分层架构与领域驱动设计
- 详细说明表现层、视图模型层、配置层和基础设施层职责
- 介绍用户认证、OAuth2授权码与令牌颁发的数据流过程
- 抽象说明用户与租户、声明和授权实体设计
- 说明应用启动入口和关键HTTP端点
- 列出错误处理策略和跨领域关注点(日志、追踪、安全)

docs(concerns): 新增代码库问题与关注点分析文档

- 汇总并详述安全漏洞如配置文件泄露、Cookie策略不当
- 记录技术债务包括缺乏单元测试、依赖注入不统一等
- 罗列性能问题和具体代码缺陷
- 给出优先级明确的修复建议和改进措施
- 涵盖架构设计问题和依赖兼容性风险
- 说明测试覆盖缺口及高风险未测试区域

docs(conventions): 新增编码约定与规范文档

- 明确文件、类、方法、变量等命名规则
- 规范代码风格包括命名空间、主构造函数使用
- 制定日志记录、审计日志和依赖注入的标准
- 规定控制器路由、异步模式和错误处理方式
- 说明DTO命名及特性使用规范
- 描述配置管理、注释规范及常用代码注释样例

docs(integrations): 添加外部系统集成文档

- 介绍数据库连接和PostgreSQL客户端库版本
- 描述身份认证与授权服务及默认用户信息
- 说明可观测性方案及OpenTelemetry组件
- 涵盖容器化部署相关Docker与Kubernetes配置
- 说明CI/CD流水线触发条件与构建流程
- 列出环境变量需求和主要API端点
- 强调生产环境密钥管理与安全存储机制
2026-03-01 11:28:44 +08:00

527 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 编码约定
**分析日期:** 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<ActionResult<object>> GetUsers(...)
public async Task<ActionResult<ApplicationUser>> CreateUser(CreateUserDto dto)
public async Task<IActionResult> 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 namespaceC# 10+ 特性
**示例:**
```csharp
namespace Fengling.AuthService.Controllers;
namespace Fengling.AuthService.ViewModels;
namespace Fengling.AuthService.Configuration;
```
### 主构造函数
**约定:** 优先使用 C# 12 主构造函数Primary Constructors
**示例:**
```csharp
// 主构造函数方式(推荐)
public class UsersController(
UserManager<ApplicationUser> userManager,
RoleManager<ApplicationRole> roleManager,
ILogger<UsersController> logger,
PlatformDbContext platformDbContext)
: ControllerBase
{
}
// 传统构造函数方式(部分文件使用)
public class RolesController : ControllerBase
{
private readonly PlatformDbContext _context;
private readonly RoleManager<ApplicationRole> _roleManager;
public RolesController(
PlatformDbContext context,
RoleManager<ApplicationRole> roleManager,
...)
{
_context = context;
_roleManager = roleManager;
}
}
```
### 属性与字段
**约定:** 私有字段使用下划线前缀(`_camelCase`),公共属性使用 PascalCase
**示例:**
```csharp
public class RolesController : ControllerBase
{
private readonly PlatformDbContext _context;
private readonly RoleManager<ApplicationRole> _roleManager;
private readonly UserManager<ApplicationUser> _userManager;
private readonly ILogger<RolesController> _logger;
}
```
### 使用 var 推断类型
**约定:** 优先使用 `var` 进行类型推断,复杂类型或返回类型明确时显式声明
**示例:**
```csharp
var query = platformDbContext.Users.AsQueryable();
var user = await platformDbContext.Users.FindAsync(id);
var totalCount = await query.CountAsync();
ActionResult<object> result = Ok(new { ... });
IEnumerable<string> 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<ActionResult<object>> 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<long> 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<T>` 使用
**示例:**
```csharp
public class UsersController(
UserManager<ApplicationUser> userManager,
RoleManager<ApplicationRole> roleManager,
ILogger<UsersController> 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<PlatformDbContext>(options =>
{
options.UseNpgsql(connectionString);
options.UseOpenIddict();
}).AddIdentity<ApplicationUser, ApplicationRole>()
.AddEntityFrameworkStores<PlatformDbContext>()
.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<T>` 模式
**示例:**
```csharp
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
var isTesting = builder.Configuration.GetValue<bool>("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*