- 描述整体基于ASP.NET Core的分层架构与领域驱动设计 - 详细说明表现层、视图模型层、配置层和基础设施层职责 - 介绍用户认证、OAuth2授权码与令牌颁发的数据流过程 - 抽象说明用户与租户、声明和授权实体设计 - 说明应用启动入口和关键HTTP端点 - 列出错误处理策略和跨领域关注点(日志、追踪、安全) docs(concerns): 新增代码库问题与关注点分析文档 - 汇总并详述安全漏洞如配置文件泄露、Cookie策略不当 - 记录技术债务包括缺乏单元测试、依赖注入不统一等 - 罗列性能问题和具体代码缺陷 - 给出优先级明确的修复建议和改进措施 - 涵盖架构设计问题和依赖兼容性风险 - 说明测试覆盖缺口及高风险未测试区域 docs(conventions): 新增编码约定与规范文档 - 明确文件、类、方法、变量等命名规则 - 规范代码风格包括命名空间、主构造函数使用 - 制定日志记录、审计日志和依赖注入的标准 - 规定控制器路由、异步模式和错误处理方式 - 说明DTO命名及特性使用规范 - 描述配置管理、注释规范及常用代码注释样例 docs(integrations): 添加外部系统集成文档 - 介绍数据库连接和PostgreSQL客户端库版本 - 描述身份认证与授权服务及默认用户信息 - 说明可观测性方案及OpenTelemetry组件 - 涵盖容器化部署相关Docker与Kubernetes配置 - 说明CI/CD流水线触发条件与构建流程 - 列出环境变量需求和主要API端点 - 强调生产环境密钥管理与安全存储机制
14 KiB
编码约定
分析日期: 2026-02-28
项目概述
此代码库是一个基于 .NET 10.0 的 ASP.NET Core Web API 认证授权服务,采用 OpenIddict 实现 OAuth2/OIDC 协议,并使用 ASP.NET Core Identity 进行用户和角色管理。
命名模式
文件命名
约定: 使用 PascalCase 命名法
示例:
UsersController.csTokenController.csLoginViewModel.csOpenIddictSetup.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 namespace),C# 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 指令
- 系统命名空间(
System.*) - 项目依赖包命名空间(
Microsoft.*、OpenIddict.*、Fengling.*) - 当前项目命名空间(
Fengling.AuthService.*) - 静态导入(
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/token、connect/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