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

14 KiB
Raw Blame History

编码约定

分析日期: 2026-02-28

项目概述

此代码库是一个基于 .NET 10.0ASP.NET Core Web API 认证授权服务,采用 OpenIddict 实现 OAuth2/OIDC 协议,并使用 ASP.NET Core Identity 进行用户和角色管理。

命名模式

文件命名

约定: 使用 PascalCase 命名法

示例:

  • UsersController.cs
  • TokenController.cs
  • LoginViewModel.cs
  • OpenIddictSetup.cs

类与类型命名

约定: 使用 PascalCase 命名法

示例:

public class UsersController : ControllerBase
public class LoginViewModel
public class CreateUserDto
public static class OpenIddictSetup

方法命名

约定: 使用 PascalCase 命名法,动词或动词短语开头

示例:

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 命名法

示例:

var query = platformDbContext.Users.AsQueryable();
var totalCount = await query.CountAsync();
var user = await userManager.FindByNameAsync(request.Username);
var roles = await userManager.GetRolesAsync(user);

常量与枚举

约定: 使用 PascalCase 命名法

示例:

errors.InvalidGrant
Errors.UnsupportedGrantType
Statuses.Valid
AuthorizationTypes.Permanent

代码风格

命名空间

约定: 使用文件级别命名空间File-scoped namespaceC# 10+ 特性

示例:

namespace Fengling.AuthService.Controllers;
namespace Fengling.AuthService.ViewModels;
namespace Fengling.AuthService.Configuration;

主构造函数

约定: 优先使用 C# 12 主构造函数Primary Constructors

示例:

// 主构造函数方式(推荐)
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

示例:

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 进行类型推断,复杂类型或返回类型明确时显式声明

示例:

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

示例:

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;

命名空间别名

约定: 必要时使用命名空间别名简化长命名空间引用

示例:

using static OpenIddict.Abstractions.OpenIddictConstants;

控制器模式

API 控制器约定

约定: 所有 API 控制器使用以下属性

[ApiController]
[Route("api/[controller]")]
[Authorize]
public class UsersController(...) : ControllerBase

路由约定

约定:

  • 集合资源使用复数形式:api/users
  • 单个资源使用 /{id} 形式:api/users/{id}
  • 特殊端点使用 connect 前缀:connect/tokenconnect/authorize

示例:

[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 模式

示例:

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 状态码返回错误

示例:

// 资源不存在
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

示例:

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 类定义在对应控制器文件的末尾

示例:

// 位于 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 序列化配置保持一致

示例:

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> 使用

示例:

public class UsersController(
    UserManager<ApplicationUser> userManager,
    RoleManager<ApplicationRole> roleManager,
    ILogger<UsersController> logger,  // 注入日志记录器
    PlatformDbContext platformDbContext)
    : ControllerBase
{
}

Serilog 配置

约定:Program.cs 中配置 Serilog使用配置文件和代码配置结合

示例:

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();

审计日志

审计日志模式

约定: 关键业务操作需要记录审计日志,包括操作者、操作类型、操作目标等信息

示例:

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 中注册所有服务,使用链式调用

示例:

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> 模式

示例:

var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
var isTesting = builder.Configuration.GetValue<bool>("Testing", false);

特性与属性使用

常用特性

约定: 合理使用以下特性

[ApiController]                          // API 控制器
[Route("api/[controller]")]             // 路由
[Authorize]                              // 需要授权
[AllowAnonymous]                         // 允许匿名访问
[HttpGet] [HttpPost] [HttpPut] [HttpDelete] // HTTP 方法
[FromQuery]                              // 从查询参数绑定
[FromBody]                               // 从请求体绑定
[FromRoute]                              // 从路由参数绑定
[Required]                               // 必需验证
[StringLength]                           // 字符串长度验证
[EmailAddress]                           // 邮箱格式验证
[Compare]                                // 字段比较验证
[DataType]                               // 数据类型

注释规范

代码内注释

约定: 关键业务逻辑添加注释说明,使用英文或中文均可,保持一致

示例:

// 设置 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