docs(architecture): 添加系统架构分析文档

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

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

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

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

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

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

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

View File

@ -0,0 +1,257 @@
# 架构分析
**分析日期:** 2026-02-28
## 模式概述
**整体架构:** 基于 ASP.NET Core 的现代 Web 应用架构,采用分层设计结合领域驱动思想。
**核心特性:**
- 使用 OpenIddict 实现 OAuth2/OIDC 授权服务器功能
- 采用 ASP.NET Core Identity 进行用户身份管理
- 基于 Entity Framework Core 进行数据访问
- 支持多租户Tenant架构
- 集成 MediatR 实现 CQRS 模式(通过共享基础设施包)
## 分层架构
### 1. 表现层Presentation Layer
**位置:** `src/Controllers/`、`src/Views/`
**职责:**
- 处理 HTTP 请求与响应
- 渲染 Razor 视图
- 用户交互界面
**关键控制器:**
- `AccountController`:用户登录、注册、登出
- `AuthorizationController`OAuth2 授权端点
- `TokenController`:令牌颁发端点
- `DashboardController`:用户仪表板
- `UsersController`、`RolesController`、`TenantsController`:管理功能
### 2. 视图模型层ViewModel Layer
**位置:** `src/ViewModels/`
**职责:**
- 在控制器与视图之间传递数据
- 表单数据绑定与验证
**关键视图模型:**
- `LoginViewModel`:登录表单
- `RegisterViewModel`:注册表单
- `AuthorizeViewModel`:授权确认表单
- `DashboardViewModel`:仪表板数据
### 3. 配置层Configuration Layer
**位置:** `src/Configuration/`
**职责:**
- 集中管理应用程序配置
- OpenIddict 服务配置
- 身份验证策略配置
**关键配置:**
- `OpenIddictSetup.cs`OpenIddict 完整配置,包括授权流程、令牌生命周期、作用域等
- `FormValueRequiredAttribute.cs`:自定义属性用于表单提交处理
### 4. 基础设施层Infrastructure Layer
**外部依赖:** `Fengling.Platform.Infrastructure` NuGet 包
**提供的功能:**
- `PlatformDbContext`Entity Framework Core 数据库上下文
- `ApplicationUser`:应用程序用户实体
- `ApplicationRole`:应用程序角色实体
- 仓储Repository模式实现
- MediatR 中间件与行为Behaviors
- 命令锁定行为Command Lock Behavior
- 工作单元Unit of Work支持
## 数据流
### 用户认证流程
```
1. 用户访问 /account/login
2. AccountController 返回登录视图Login.cshtml
3. 用户提交表单 → AccountController.Login POST
4. UserManager 验证用户名密码
5. SignInManager 创建认证 Cookie
6. 重定向到原始 URL 或 /dashboard
```
### OAuth2 授权码流程
```
1. 客户端应用重定向到 /connect/authorize
2. AuthorizationController.Authorize 检查用户登录状态
3. 未登录 → 重定向到登录页面
4. 已登录 → 检查授权记录
5. 需要用户授权 → 返回授权确认视图Authorize.cshtml
6. 用户确认 → AuthorizationController.Accept
7. 创建授权记录 → 返回令牌
```
### 令牌颁发流程
```
1. 客户端 POST /connect/token
2. TokenController.Exchange 处理请求
3. 根据授权类型password/refresh_token/authorization_code处理
4. 从 UserManager 获取用户信息
5. 构建 Claims 身份(包括租户信息、角色)
6. 设置 Claim Destinations
7. SignIn 返回令牌
```
## 关键抽象
### 用户与租户抽象
**ApplicationUser**
- 继承自 ASP.NET Core Identity 的 IdentityUser
- 包含 `TenantInfo` 属性实现多租户支持
**TenantInfo 值对象:**
- `TenantId`:租户标识符
- `TenantCode`:租户代码
- `TenantName`:租户名称
### 声明Claims抽象
**标准声明:**
- `sub`:用户唯一标识
- `name`:用户名
- `email`:用户邮箱
- `role`:用户角色
**自定义声明:**
- `tenant_id`:租户 ID
- `tenant_code`:租户代码
- `tenant_name`:租户名称
### 授权Authorization抽象
**OpenIddict 实体:**
- `OpenIddictApplication`OAuth2 客户端应用
- `OpenIddictAuthorization`:授权记录
- `OpenIddictScope`:授权作用域
- `OpenIddictToken`:访问令牌/刷新令牌
## 入口点
### Web 应用程序入口
**位置:** `src/Program.cs`
**启动流程:**
1. 配置 Serilog 日志
2. 注册平台核心服务AddPlatformCore
3. 配置 ASP.NET Core Identity
4. 配置 Razor Pages 和 MVC
5. 配置 Cookie 认证
6. 配置 OpenIddict
7. 配置 OpenTelemetry 分布式追踪
8. 配置健康检查
9. 注册仓储Repositories
10. 注册 MediatR 和中间件行为
11. 配置 Swagger开发环境
12. 初始化数据库
### HTTP 端点
**认证端点:**
- `GET/POST /account/login`:用户登录
- `GET/POST /account/register`:用户注册
- `POST /account/~/connect/logout`:用户登出
- `GET/POST /connect/authorize`:授权端点
- `POST /connect/token`:令牌端点
- `GET /connect/userinfo`:用户信息端点
**管理端点:**
- `GET /dashboard`:用户仪表板
- `GET /dashboard/profile`:用户资料
- `GET /dashboard/settings`:设置页面
- `GET/POST /users`:用户管理
- `GET/POST /roles`:角色管理
- `GET/POST /tenants`:租户管理
**系统端点:**
- `GET /health`:健康检查
- `GET /swagger/index.html`API 文档
## 错误处理
**策略:** 基于 ASP.NET Core 标准异常处理
**模式:**
- 模型验证失败返回视图ModelState
- 认证失败返回 401/403
- OpenIddict 错误返回标准错误响应
- 全局异常由中间件捕获并记录
## 跨领域关注点
### 日志记录
**框架:** Serilog
**配置:** 在 Program.cs 中配置,控制台输出
**模板:**
```
[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}
```
### 分布式追踪
**框架:** OpenTelemetry
**组件:**
- AspNetCoreInstrumentation
- HttpClientInstrumentation
- OpenIddict.Server.AspNetCore 追踪源
- OTLP Exporter
### 健康检查
**端点:** `/health`
**检查项:**
- PostgreSQL 数据库连接NpgSql
### 安全配置
**认证方案:**
- Cookie 认证(默认方案):用于 Web 界面登录
- OpenIddict Server用于 OAuth2/OIDC
**令牌配置:**
- 访问令牌生命周期24 小时
- 使用引用令牌Reference Tokens
- 支持刷新令牌
---
*架构分析2026-02-28*

View File

@ -0,0 +1,537 @@
# 代码库问题与关注点
**分析日期:** 2026-02-28
---
## 一、严重安全问题
### 1.1 配置文件泄露敏感凭证
**问题描述:** `appsettings.json` 包含明文数据库密码和 JWT 密钥。
**文件位置:** `src/appsettings.json`
**泄露内容:**
```json
"ConnectionStrings": {
"DefaultConnection": "Host=81.68.223.70;Port=15432;Database=fengling_auth;Username=movingsam;Password=sl52788542"
},
"Jwt": {
"Secret": "FenglingAuthSecretKey2024!ChangeThisInProduction!"
}
```
**影响:**
- 数据库凭据完全暴露,包括用户名、密码、主机地址和端口
- JWT 密钥硬编码在配置文件中,攻击者可用其伪造令牌
- 若此文件被提交到版本控制系统,将造成严重安全漏洞
**修复建议:**
- 立即将敏感信息迁移至环境变量或密钥保管库
- 使用 ASP.NET Core 的密钥管理功能(`dotnet user-secrets`
- 在生产环境中使用 Azure Key Vault、AWS Secrets Manager 等
- 创建 `appsettings.Production.json` 并通过环境变量加载配置
---
### 1.2 Cookie 安全策略配置不当
**问题描述:** 认证 Cookie 配置为非安全策略。
**文件位置:** `src/Program.cs` 第 43-44 行
```csharp
options.Cookie.SecurePolicy = CookieSecurePolicy.None;
options.Cookie.SameSite = SameSiteMode.Lax;
```
**影响:**
- `CookieSecurePolicy.None` 允许 Cookie 通过非 HTTPS 连接传输
- `SameSiteMode.Lax` 无法完全防止 CSRF 攻击
- 用户凭证可能在网络传输中被截获
**修复建议:**
```csharp
options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
options.Cookie.SameSite = SameSiteMode.Strict;
options.Cookie.HttpOnly = true;
```
---
### 1.3 访问令牌未加密
**问题描述:** OpenIddict 配置禁用了访问令牌加密。
**文件位置:** `src/Configuration/OpenIddictSetup.cs` 第 64 行
```csharp
options.DisableAccessTokenEncryption();
```
**影响:**
- 访问令牌以明文形式传输,任何中间人都能读取令牌内容
- 攻击者可窃取令牌并冒充合法用户
**修复建议:**
- 生产环境必须启用令牌加密
- 使用有效的加密证书而非开发证书
---
### 1.4 CORS 允许所有来源
**问题描述:** CORS 配置允许任意来源的跨域请求。
**文件位置:** `src/Program.cs` 第 102-109 行
```csharp
app.UseCors(x =>
{
x.SetIsOriginAllowed(origin => true)
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials()
.Build();
});
```
**影响:**
- 任何网站都能向此 API 发起跨域请求
- 极大增加 CSRF 和数据泄露风险
**修复建议:**
- 明确配置允许的来源列表
- 使用环境变量控制允许的域名
---
### 1.5 开发证书用于生产环境
**问题描述:** OpenIddict 使用开发环境证书进行签名和加密。
**文件位置:** `src/Configuration/OpenIddictSetup.cs` 第 61-62 行
```csharp
options.AddDevelopmentEncryptionCertificate()
.AddDevelopmentSigningCertificate();
```
**影响:**
- 开发证书的私钥是公开的,攻击者可伪造令牌
- 严重威胁生产环境安全
**修复建议:**
- 生产环境使用正式的 SSL/TLS 证书
- 使用 Azure Key Vault 或类似服务存储证书
---
## 二、技术债务
### 2.1 完全没有单元测试
**问题描述:** 代码库中不存在任何测试文件。
**文件位置:** 整个项目
**影响:**
- 无法确保代码质量
- 重构风险极高,容易引入 bug
- 难以验证边界条件和错误处理
**修复建议:**
- 引入 xUnit 或 NUnit 测试框架
- 为所有 Controller 编写单元测试
- 为关键业务逻辑Token 颁发、用户管理等)编写集成测试
- 目标覆盖率应达到 70% 以上
---
### 2.2 使用 .NET 10.0
**问题描述:** 项目目标框架为 .NET 10.0。
**文件位置:** `src/Fengling.AuthService.csproj` 第 3 行
```xml
<TargetFramework>net10.0</TargetFramework>
```
**影响:**
- .NET 10.0 目前可能为预览版或早期版本
- 稳定性存在风险,缺乏长期支持
- 依赖包兼容性问题
**修复建议:**
- 生产环境应使用 LTS长期支持版本如 .NET 8.0
- 密切关注 .NET 10.0 的正式发布和稳定性评估
---
### 2.3 审计日志逻辑重复
**问题描述:** 审计日志创建逻辑在多个控制器中重复实现。
**受影响文件:**
- `src/Controllers/UsersController.cs` - `CreateAuditLog` 方法
- `src/Controllers/RolesController.cs` - `CreateAuditLog` 方法
- `src/Controllers/TenantsController.cs` - `CreateAuditLog` 方法
**影响:**
- 代码重复,维护困难
- 行为不一致风险
- 违反 DRY 原则
**修复建议:**
- 提取为独立的审计日志服务
- 使用 MediatR 管道行为统一处理
- 创建审计日志特性的 AOP 方案
---
### 2.4 不一致的依赖注入风格
**问题描述:** 控制器同时使用两种不同的依赖注入方式。
**文件位置:**
- 构造函数注入(传统方式):`src/Controllers/RolesController.cs`
- Primary Constructor 注入(记录式):`src/Controllers/UsersController.cs`
**示例对比:**
传统方式RolesController
```csharp
public class RolesController : ControllerBase
{
private readonly PlatformDbContext _context;
public RolesController(PlatformDbContext context)
{
_context = context;
}
}
```
Primary Constructor 方式UsersController
```csharp
public class UsersController(
UserManager<ApplicationUser> userManager,
RoleManager<ApplicationRole> roleManager,
ILogger<UsersController> logger,
PlatformDbContext platformDbContext)
: ControllerBase
```
**影响:**
- 代码风格不统一
- 增加新人学习成本
**修复建议:**
- 统一采用 Primary Constructor 方式ASP.NET Core 8.0+ 推荐)
- 或统一使用传统构造函数注入方式
---
## 三、已知缺陷
### 3.1 未实现的接口方法
**问题描述:** 某些接口返回硬编码或空值。
**文件位置:** `src/Controllers/TenantsController.cs` 第 171-192 行
```csharp
[HttpGet("{tenantId}/settings")]
public async Task<ActionResult<TenantSettings>> GetTenantSettings(long tenantId)
{
// ...
var settings = new TenantSettings
{
AllowRegistration = false,
AllowedEmailDomains = "",
DefaultRoleId = null,
PasswordPolicy = new List<string> { "requireNumber", "requireLowercase" },
MinPasswordLength = 8,
SessionTimeout = 120,
};
return Ok(settings);
}
```
**影响:**
- 租户设置无法持久化
- 配置变更不生效
**修复建议:**
- 创建 TenantSettings 实体
- 实现 CRUD 操作
- 与租户配置表关联
---
### 3.2 Profile 接口重定向
**问题描述:** Profile 和 Settings 路由指向未实现的方法。
**文件位置:** `src/Controllers/AccountController.cs` 第 111-117 行
```csharp
[HttpGet("profile")]
[HttpGet("settings")]
[HttpGet("/connect/logout")]
public IActionResult NotImplemented()
{
return RedirectToAction("Index", "Dashboard");
}
```
**影响:**
- 用户访问个人资料页面时被重定向
- 功能缺失,用户体验差
**修复建议:**
- 实现完整的个人资料页面
- 允许用户查看和修改基本信息
---
## 四、性能问题
### 4.1 N+1 查询问题
**问题描述:** 列表接口在循环中执行数据库查询。
**文件位置:** `src/Controllers/RolesController.cs` 第 63-78 行
```csharp
foreach (var role in roles)
{
var users = await _userManager.GetUsersInRoleAsync(role.Name!);
result.Add(new
{
// ...
userCount = users.Count,
// ...
});
}
```
**影响:**
- 假设有 N 个角色,将执行 1+N 次数据库查询
- 角色数量增加时,性能呈线性下降
- 数据库负载显著增加
**修复建议:**
- 使用单一查询获取所有角色及其用户数
- 通过 JOIN 或子查询聚合计数
---
### 4.2 内存分页问题
**问题描述:** OAuth 客户端列表先将所有数据加载到内存,再进行分页。
**文件位置:** `src/Controllers/OAuthClientsController.cs` 第 31-86 行
```csharp
var applications = _applicationManager.ListAsync();
var clientList = new List<object>();
await foreach (var application in applications)
{
// 处理每个应用...
clientList.Add(new { ... });
}
var sortedClients = clientList
.OrderByDescending(c => (c as dynamic).clientId)
.Skip((page - 1) * pageSize)
.Take(pageSize)
.ToList();
```
**影响:**
- 客户端数量增加时,内存占用大幅增长
- 首次加载缓慢
- 无法处理大量客户端场景
**修复建议:**
- 使用数据库层面的分页EF Core 的 Skip/Take
- 实现服务端分页而非内存分页
---
### 4.3 缺少数据库索引
**问题描述:** 频繁查询的字段可能缺少索引。
**涉及字段:**
- `Users.UserName` - 登录查询
- `Users.TenantInfo.TenantId` - 租户隔离查询
- `AuditLogs.CreatedAt` - 日志查询
- `AccessLogs.CreatedAt` - 访问日志查询
**影响:**
- 查询性能随数据量增加而下降
- 大表全表扫描风险
**修复建议:**
- 分析慢查询日志
- 为常用查询字段添加索引
- 使用 EF Core 的 Fluent API 配置索引
---
## 五、架构与设计问题
### 5.1 异常处理作为流程控制
**问题描述:** 使用异常抛出处理正常业务流程。
**文件位置:** `src/Controllers/AuthorizationController.cs` 多处
```csharp
var request = HttpContext.GetOpenIddictServerRequest() ??
throw new InvalidOperationException("The OpenID Connect request cannot be retrieved.");
var user = await userManager.GetUserAsync(User) ??
throw new InvalidOperationException("The user details cannot be retrieved.");
```
**影响:**
- 异常处理开销大,性能差
- 代码可读性差
- 混淆业务逻辑和错误处理
**修复建议:**
- 使用空值合并和空值检查
- 返回适当的 HTTP 状态码(如 400、401
- 使用 `ActionResult<T>` 模式
---
### 5.2 租户隔离不完整
**问题描述:** 某些查询未正确应用租户隔离。
**文件位置:** `src/Controllers/UsersController.cs` 第 32-47 行
```csharp
var query = platformDbContext.Users.AsQueryable();
if (!string.IsNullOrEmpty(userName))
{
query = query.Where(u => u.UserName!.Contains(userName));
}
// ...
```
**影响:**
- 管理员可能查看所有租户的数据
- 租户数据泄露风险
**修复建议:**
- 创建租户过滤的基类或中间件
- 在所有查询中自动应用租户 ID 过滤
- 使用 EF Core 的全局查询过滤器
---
### 5.3 缺少 API 版本控制
**问题描述:** API 端点没有版本控制机制。
**影响:**
- 无法平滑升级 API
- 客户端兼容性问题
- 难以废弃旧版 API
**修复建议:**
- 实现 API 版本控制URL 路径或 Header
- 使用 `Microsoft.AspNetCore.Mvc.Versioning`
- 文档化各版本差异
---
## 六、依赖与兼容性
### 6.1 依赖外部包版本风险
**问题描述:** 项目依赖可能存在版本兼容性问题。
**关键依赖:**
- `OpenIddict.AspNetCore` - OAuth/OIDC 实现
- `Fengling.Platform.Infrastructure` - 内部共享库
- `NetCorePal.Extensions.AspNetCore` - 扩展库
**风险:**
- 依赖更新可能导致破坏性变更
- 内部包版本不同步问题
**修复建议:**
- 锁定依赖版本
- 定期审查依赖更新
- 建立依赖升级测试流程
---
## 七、测试覆盖缺口
### 7.1 高风险未测试区域
| 区域 | 文件位置 | 风险 |
|------|----------|------|
| 身份验证流程 | `TokenController.cs` | 令牌颁发、刷新 |
| 用户管理 | `UsersController.cs` | 用户创建、密码重置 |
| 授权逻辑 | `AuthorizationController.cs` | OAuth 授权流程 |
| 租户隔离 | 多个 Controller | 数据泄露风险 |
### 7.2 缺失的测试类型
- **单元测试:** 业务逻辑、验证逻辑
- **集成测试:** 数据库交互、API 端点
- **安全测试:** 认证流程、权限检查
- **性能测试:** 大数据量场景
---
## 八、优先级修复建议
### 高优先级(立即处理)
1. **移除 `appsettings.json` 中的敏感信息**
- 迁移到环境变量或密钥保管库
- 轮换已泄露的密码和密钥
2. **启用 Cookie 安全策略**
- 改为 `SecurePolicy.Always`
- 启用 HttpOnly 和 SameSite=Strict
3. **启用访问令牌加密**
- 移除 `DisableAccessTokenEncryption()`
- 配置生产证书
4. **修复 CORS 配置**
- 限制允许的来源列表
### 中优先级(近期处理)
1. **添加单元测试框架和基础测试**
2. **修复 N+1 查询问题**
3. **实现内存分页优化**
4. **统一依赖注入风格**
5. **实现租户隔离的全局过滤**
### 低优先级(长期规划)
1. **升级到 .NET LTS 版本**
2. **提取审计日志服务**
3. **实现 API 版本控制**
4. **完善租户设置功能**
---
*问题审计完成*

View File

@ -0,0 +1,526 @@
# 编码约定
**分析日期:** 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*

View File

@ -0,0 +1,115 @@
# 外部集成
**分析日期:** 2026-02-28
## 数据库
**PostgreSQL**
- **类型:** 关系型数据库
- **连接信息:**
- 主机81.68.223.70
- 端口15432
- 数据库fengling_auth
- 用户名movingsam
- **客户端库:** Npgsql.EntityFrameworkCore.PostgreSQL 10.0.3
- **连接字符串来源:** `appsettings.json` 或环境变量 `ConnectionStrings__DefaultConnection`
- **健康检查:** 通过 AspNetCore.HealthChecks.Npgsql 集成
## 身份认证与授权
**OpenIddictOAuth2/OIDC**
- 实现标准的 OAuth2 授权服务器功能
- 支持的授权类型password密码模式
- JWT Token 发行
- 令牌配置:
- Issuer签发者`http://localhost:5132`(开发环境)
- Audience受众`fengling-api`
- JWT Secret开发环境`FenglingAuthSecretKey2024!ChangeThisInProduction!`
**默认用户:**
- **管理员:** 用户名 `admin`,密码 `Admin@123`,角色 `Admin`
- **测试用户:** 用户名 `testuser`,密码 `Test@123`,角色 `User`
## 可观测性
**OpenTelemetryOTLP 导出):**
- 导出协议OpenTelemetry Protocol (OTLP)
- 导出目标:未在配置中明确指定(需配置 OTLP 端点)
- Instrumentation
- ASP.NET Core 请求追踪
- HTTP 客户端追踪
- OpenIddict 服务器追踪
## 容器与部署
**Docker**
- 镜像仓库:`gitea.shtao1.cn`Gitea 私有仓库)
- 镜像名称:`fengling/fengling-auth-service:latest`
- Dockerfile多阶段构建build + publish
**Kubernetes**
- 命名空间:`fengling`
- 副本数2
- 服务端口80
- 资源配置:
- 请求CPU 100m内存 256Mi
- 限制CPU 500m内存 512Mi
- 健康检查:
- 就绪探针:/health初始延迟 10 秒,周期 10 秒
- 存活探针:/health初始延迟 30 秒,周期 30 秒
## CI/CD
**Gitea Actions**
- 触发条件:
- 推送至 main 或 master 分支
- 合并至 main 或 master 分支的 Pull Request
- .NET 版本10.0
- 阶段:
1. 构建Build
2. Docker 镜像构建与推送
3. 部署至 Kubernetes
**NuGet 源:**
- 私有源:`https://gitea.shtao1.cn/api/packages/fengling/nuget/index.json`
- 公共源:`https://api.nuget.org/v3/index.json`
- 认证方式:用户名 + 访问令牌
## 环境变量
**必需的环境变量:**
- `ConnectionStrings__DefaultConnection` - 数据库连接字符串(从 Kubernetes Secret 注入)
- `OpenIddict__Issuer` - Token 签发者 URL生产环境`https://auth.fengling.local`
- `OpenIddict__Audience` - Token 受众
- `ASPNETCORE_ENVIRONMENT` - 运行环境(如 Production
- `ASPNETCORE_URLS` - 监听 URL默认`http://+:80`
**可选变量:**
- `Testing` - 是否为测试模式(启用/禁用 Swagger
## API 端点
**主要端点:**
- `/connect/token` - 获取 OAuth2 令牌(密码模式)
- `/health` - 健康检查端点
- `/swagger/v1/swagger.json` - OpenAPI 文档
**管理端点(需认证):**
- `/api/users` - 用户管理
- `/api/roles` - 角色管理
- `/api/tenants` - 租户管理
- `/api/oauth/clients` - OAuth 客户端管理
- `/api/audit/logs` - 审计日志
- `/api/access/logs` - 访问日志
## 密钥管理
**生产环境密钥存储:**
- Kubernetes Secret`fengling-auth-secrets`
- 存储内容:
- `connection-string` - 数据库连接字符串
- 镜像拉取凭据Kubernetes ImagePullSecret
---
*外部集成审计2026-02-28*

117
.planning/codebase/STACK.md Normal file
View File

@ -0,0 +1,117 @@
# 技术栈
**分析日期:** 2026-02-28
## 语言
**主要:**
- **C# 12** - 项目使用最新的 C# 语言特性
- **.NET 10.0** - 目标框架版本
**运行时:**
- **ASP.NET Core 10.0** - 用于构建 Web 应用程序和 API
## 运行时环境
**框架:**
- **.NET 10.0** - 当前稳定版本
- **ASP.NET Core** - Web 框架
**包管理:**
- **NuGet** - .NET 包管理器
- **Fengling.Platform.Infrastructure** - 1.0.0 版本,内部平台基础设施包
- **NetCorePal.Extensions** - 3.2.1 版本,一系列扩展库
## 核心框架
**身份认证与授权:**
- **OpenIddict** 7.2.0 - OAuth2/OpenID Connect 实现
- `OpenIddict.AspNetCore` - OpenIddict 的 ASP.NET Core 集成
- `OpenIddict.EntityFrameworkCore` - OpenIddict 的 EF Core 存储
- `OpenIddict.Quartz` - OpenIddict 的 Quartz.NET 集成(用于后台任务)
**数据访问:**
- **Entity Framework Core** 10.0.3 - ORM 框架
- `Microsoft.EntityFrameworkCore` - 核心 EF Core 包
- `Microsoft.EntityFrameworkCore.Design` - EF Core 设计时工具
- `Npgsql.EntityFrameworkCore.PostgreSQL` - PostgreSQL 提供程序(已注释)
- `Microsoft.EntityFrameworkCore.InMemory` - 内存数据库(用于测试)
**用户与角色管理:**
- **ASP.NET Core Identity** 10.0.3 - 用户身份管理系统
**Web API**
- **Swashbuckle.AspNetCore** 10.1.4 - Swagger/OpenAPI 文档生成
**健康检查:**
- **AspNetCore.HealthChecks.Npgsql** 9.0.0 - PostgreSQL 健康检查
- **Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore** 10.0.3 - EF Core 健康检查
## 日志与观测
**日志:**
- **Serilog** - 结构化日志框架
- `Serilog.AspNetCore` 10.0.0 - Serilog 的 ASP.NET Core 集成
- `Serilog.Sinks.Console` 6.1.1 - 控制台输出接收器
**分布式追踪:**
- **OpenTelemetry** 1.15.0 - 可观测性标准
- `OpenTelemetry` - 核心包
- `OpenTelemetry.Extensions.Hosting` - Host 集成
- `OpenTelemetry.Instrumentation.AspNetCore` - ASP.NET Core instrumentation
- `OpenTelemetry.Instrumentation.Http` - HTTP 客户端 instrumentation
- `OpenTelemetry.Exporter.OpenTelemetryProtocol` - OTLP 导出器
## 业务逻辑
**CQRS 与中介者:**
- **MediatR** 12.5.0 - 中介者模式实现
**领域驱动设计支持:**
- **NetCorePal.Extensions.Domain.Abstractions** - 领域抽象
- **NetCorePal.Extensions.Primitives** - 基础类型扩展
- **NetCorePal.Extensions.Repository.EntityFrameworkCore** - 仓储模式实现
- **NetCorePal.Extensions.Repository.EntityFrameworkCore.Snowflake** - 雪花算法 ID 生成
## 配置
**环境配置:**
- `appsettings.json` - 基础配置
- `appsettings.Development.json` - 开发环境配置
- `appsettings.Testing.json` - 测试环境配置
**关键配置项:**
```json
{
"ConnectionStrings": {
"DefaultConnection": "Host=81.68.223.70;Port=15432;Database=fengling_auth;..."
},
"Jwt": {
"Issuer": "http://localhost:5132",
"Audience": "fengling-api",
"Secret": "..."
},
"OpenIddict": {
"Issuer": "http://localhost:5132",
"Audience": "fengling-api"
}
}
```
**Docker 配置:**
- 基础镜像:`mcr.microsoft.com/dotnet/aspnet:10.0`
- 构建镜像:`mcr.microsoft.com/dotnet/sdk:10.0`
- 暴露端口80
## 项目结构
**解决方案:**
- `Fengling.AuthService.slnx` - 现代解决方案格式
**项目文件:**
- `src/Fengling.AuthService.csproj` - 主项目文件
- `Directory.Packages.props` - 集中化包版本管理
---
*技术栈分析2026-02-28*

View File

@ -0,0 +1,226 @@
# 代码库结构
**分析日期:** 2026-02-28
## 目录布局
```
fengling-auth-service/
├── src/ # 源代码根目录
│ ├── Controllers/ # MVC 控制器
│ ├── ViewModels/ # 视图模型
│ ├── Views/ # Razor 视图
│ ├── Configuration/ # 配置类
│ ├── Properties/ # 启动配置
│ ├── Program.cs # 入口点
│ ├── Fengling.AuthService.csproj # 项目文件
│ ├── appsettings.json # 应用配置
│ ├── appsettings.Development.json # 开发环境配置
│ └── Fengling.AuthService.http # HTTP 请求测试文件
├── k8s/ # Kubernetes 部署配置
│ ├── deployment.yaml # Deployment 配置
│ └── service.yaml # Service 配置
├── .gitea/workflows/ # Gittea CI/CD 配置
│ └── ci.yaml # CI 流程
├── Dockerfile # Docker 镜像构建
├── NuGet.Config # NuGet 源配置
├── Directory.Packages.props # 包版本管理
├── Fengling.AuthService.slnx # 解决方案文件
└── README.md # 项目说明
```
## 目录用途
### 源代码目录src/
**Controllers/**
- 用途MVC 控制器,处理 HTTP 请求
- 包含:
- `AccountController.cs`:账户管理(登录、注册、登出)
- `AuthorizationController.cs`OAuth2 授权
- `TokenController.cs`:令牌颁发
- `DashboardController.cs`:用户仪表板
- `UsersController.cs`、`RolesController.cs`、`TenantsController.cs`:管理功能
- `StatsController.cs`、`AuditLogsController.cs`、`AccessLogsController.cs`:日志统计
- `OAuthClientsController.cs`OAuth 客户端管理
- `LogoutController.cs`:登出处理
**ViewModels/**
- 用途:视图模型,用于视图与控制器之间的数据传输
- 包含:
- `LoginViewModel.cs`:登录视图模型
- `RegisterViewModel.cs`:注册视图模型
- `AuthorizeViewModel.cs`:授权确认视图模型
- `DashboardViewModel.cs`:仪表板视图模型
**Views/**
- 用途Razor 视图文件
- 结构:
- `Account/`账户相关视图Login、Register
- `Authorization/`授权视图Authorize
- `Dashboard/`仪表板视图Index、Profile、Settings
- `Shared/`共享视图_Layout
- `_ViewStart.cshtml`:视图起始配置
- `_ViewImports.cshtml`:视图导入配置
**Configuration/**
- 用途:集中管理应用程序配置
- 包含:
- `OpenIddictSetup.cs`OpenIddict 服务配置
- `FormValueRequiredAttribute.cs`:自定义验证属性
**Properties/**
- 用途:启动配置文件
- 包含:
- `launchSettings.json`:启动配置
## 关键文件位置
### 入口点
**`src/Program.cs`**
- 应用程序启动入口
- 服务注册
- 中间件配置
- 管道构建
### 配置文件
**`src/appsettings.json`**
- 数据库连接字符串
- JWT 配置
- OpenIddict 配置
- 日志配置
**`src/appsettings.Development.json`**
- 开发环境特定配置
### 项目文件
**`src/Fengling.AuthService.csproj`**
- 项目依赖声明
- 目标框架net10.0
- 关键包:
- OpenIddictOAuth2/OIDC
- Entity Framework Core数据访问
- ASP.NET Core Identity身份管理
- Serilog日志
- OpenTelemetry可观测性
## 命名约定
### 文件命名
**控制器:**
- 模式:`{EntityName}Controller.cs`
- 示例:`AccountController.cs`、`UsersController.cs`
**视图模型:**
- 模式:`{Feature}ViewModel.cs`
- 示例:`LoginViewModel.cs`、`AuthorizeViewModel.cs`
**视图:**
- 模式:`{Action}.cshtml`
- 示例:`Login.cshtml`、`Authorize.cshtml`
**配置类:**
- 模式:`{Feature}Setup.cs` 或 `{Feature}Attribute.cs`
- 示例:`OpenIddictSetup.cs`、`FormValueRequiredAttribute.cs`
### 目录命名
** Controllers**
- 复数形式,如 `Controllers`、`Users`
** ViewModels**
- 复数形式,如 `ViewModels`
** Views**
- 与控制器对应,如 `Account`、`Dashboard`
### 命名空间
**模式:** `Fengling.AuthService.{FolderName}`
**示例:**
- `Fengling.AuthService.Controllers`
- `Fengling.AuthService.ViewModels`
- `Fengling.AuthService.Configuration`
### 类命名
**控制器:**
- 继承自 `Controller``ControllerBase`
- 使用 `[ControllerName]` 属性或路由属性
**视图模型:**
- 使用 `ViewModel` 后缀
- 包含输入属性和验证属性
## 添加新代码的位置
### 新增功能模块
**场景:** 添加新的业务功能
**代码位置:** `src/Controllers/`
- 创建新的 Controller 文件
**视图位置:** `src/Views/{Feature}/`
**视图模型位置:** `src/ViewModels/`
**配置位置:** `src/Configuration/`
### 新增 API 端点
**场景:** 添加新的 REST API
**代码位置:** `src/Controllers/`
- 在现有 Controller 中添加 Action
- 或创建新的 Controller继承 ControllerBase
### 新增视图页面
**场景:** 添加新的 Razor 页面
**视图位置:** `src/Views/{ControllerName}/{Action}.cshtml`
**视图模型位置:** `src/ViewModels/{Feature}ViewModel.cs`
### 新增配置
**场景:** 添加新的服务配置
**配置位置:** `src/Configuration/`
- 创建静态配置类
- 在 Program.cs 中调用
## 特殊目录
### k8s/
- 用途Kubernetes 部署配置
- 生成:手动维护
- 提交:是的
### .gitea/workflows/
- 用途Gitea CI/CD 流水线配置
- 生成:手动维护
- 提交:是的
### Properties/
- 用途:开发环境启动配置
- 生成IDE 生成
- 提交是的launchSettings.json 不含敏感信息)
### Views/Shared/
- 用途:共享视图组件
- 包含_Layout.cshtml 主布局
---
*结构分析2026-02-28*

View File

@ -0,0 +1,440 @@
# 测试模式
**分析日期:** 2026-02-28
## 测试框架概述
此代码库目前**未建立独立的测试项目**,但具备测试基础设施支持。代码库使用以下测试相关配置:
- `appsettings.Testing.json` - 测试环境配置文件
- `Microsoft.EntityFrameworkCore.InMemory` - 内存数据库提供程序(用于测试场景)
- 支持通过配置切换测试模式
## 测试配置
### 测试环境配置
**配置文件位置:** `src/appsettings.Testing.json`
**配置内容:**
```json
{
"Testing": true,
"ConnectionStrings": {
"DefaultConnection": "..."
},
"OpenIddict": {
"Issuer": "...",
"Audience": "..."
}
}
```
### 测试模式切换
**约定:** 通过配置项 `Testing` 控制是否启用完整 OpenIddict 配置
**示例:**
```csharp
var isTesting = builder.Configuration.GetValue<bool>("Testing", false);
if (!isTesting)
{
builder.AddServer(options =>
{
// 生产环境 OpenIddict 配置
options.SetIssuer(configuration["OpenIddict:Issuer"] ?? "http://localhost:5132");
// ... 其他配置
});
}
```
### 内存数据库
**依赖包:** `Microsoft.EntityFrameworkCore.InMemory`
**使用场景:** 测试环境中使用内存数据库,避免外部数据库依赖
**示例:**
```csharp
// 在测试环境中,可以使用 InMemory 提供程序
options.UseInMemoryDatabase("TestDatabase");
```
## 测试现状分析
### 缺失的测试项目
**问题:** 当前代码库没有独立的测试项目xUnit、NUnit 或 MSTest
**影响:**
- 缺少单元测试
- 缺少集成测试
- 缺少控制器测试
- 无法进行回归测试
### 建议的测试策略
由于代码库采用 Clean Architecture 和 MediatR 模式,建议建立以下测试层次:
#### 1. 单元测试xUnit/NUnit
**目标:** 验证业务逻辑、命令处理器、查询处理器
**测试框架建议:**
- **xUnit** - 推荐,与 .NET 生态集成良好
- **NUnit** - 传统选择,功能全面
- **FluentAssertions** - 断言库,提供更自然的断言语法
- **Moq** - 模拟框架
**示例测试结构:**
```csharp
public class UsersControllerTests
{
private readonly Mock<UserManager<ApplicationUser>> _userManagerMock;
private readonly Mock<RoleManager<ApplicationRole>> _roleManagerMock;
private readonly Mock<ILogger<UsersController>> _loggerMock;
private readonly PlatformDbContext _context;
private readonly UsersController _controller;
public UsersControllerTests()
{
// 设置模拟对象
var store = new Mock<IUserStore<ApplicationUser>>();
_userManagerMock = new Mock<UserManager<ApplicationUser>>(
store.Object, null, null, null, null, null, null, null, null);
_roleManagerMock = new Mock<RoleManager<ApplicationRole>>();
_loggerMock = new Mock<ILogger<UsersController>>();
// 使用内存数据库
var options = new DbContextOptionsBuilder<PlatformDbContext>()
.UseInMemoryDatabase(databaseName: "TestDb")
.Options;
_context = new PlatformDbContext(options);
_controller = new UsersController(
_userManagerMock.Object,
_roleManagerMock.Object,
_loggerMock.Object,
_context);
}
}
```
#### 2. 集成测试
**目标:** 测试控制器端点、数据库操作、OpenIddict 流程
**测试框架建议:**
- **Microsoft.AspNetCore.Mvc.Testing** - ASP.NET Core 测试支持
- **TestHost** - 内存 Web 主机
- **FluentAssertions** - 断言库
**示例测试结构:**
```csharp
public class UsersControllerIntegrationTests : IClassFixture<WebApplicationFactory<Program>>
{
private readonly WebApplicationFactory<Program> _factory;
private readonly HttpClient _client;
public UsersControllerIntegrationTests(WebApplicationFactory<Program> factory)
{
_factory = factory;
_client = _factory.CreateClient();
}
[Fact]
public async Task GetUsers_ReturnsPagedResults()
{
// Arrange
var request = "/api/users?page=1&pageSize=10";
// Act
var response = await _client.GetAsync(request);
// Assert
response.StatusCode.Should().Be(HttpStatusCode.OK);
var content = await response.Content.ReadAsStringAsync();
// 验证响应内容
}
}
```
#### 3. 端到端测试
**目标:** 验证完整用户流程(可选)
**工具建议:**
- **Playwright** - 现代 Web 测试框架
- **Selenium** - 传统选择
## 测试文件组织
### 建议的目录结构
```
fengling-auth-service/
├── src/
│ └── Fengling.AuthService.csproj
└── tests/
├── Fengling.AuthService.UnitTests/
│ ├── Fengling.AuthService.UnitTests.csproj
│ ├── Controllers/
│ ├── Commands/
│ ├── Queries/
│ └── Services/
├── Fengling.AuthService.IntegrationTests/
│ ├── Fengling.AuthService.IntegrationTests.csproj
│ ├── Controllers/
│ └── Fixtures/
└── Fengling.AuthService.E2ETests/
├── Fengling.AuthService.E2ETests.csproj
└── Scenarios/
```
### 测试项目依赖
**建议的测试项目 csproj 配置:**
```xml
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="coverlet.collector" Version="*" />
<PackageReference Include="FluentAssertions" Version="*" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="*" />
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="*" />
<PackageReference Include="Moq" Version="*" />
<PackageReference Include="xunit" Version="*" />
<PackageReference Include="xunit.runner.visualstudio" Version="*">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Fengling.AuthService.csproj" />
</ItemGroup>
</Project>
```
## 模拟模式
### Moq 使用示例
**控制器依赖模拟:**
```csharp
// 模拟 UserManager
var userStoreMock = new Mock<IUserStore<ApplicationUser>>();
var userManagerMock = new Mock<UserManager<ApplicationUser>>(
userStoreMock.Object,
null, null, null, null, null, null, null, null);
// 模拟 RoleManager
var roleStoreMock = new Mock<IRoleStore<ApplicationRole>>();
var roleManagerMock = new Mock<RoleManager<ApplicationRole>>(
roleStoreMock.Object,
null, null, null, null, null, null, null);
// 模拟 ILogger
var loggerMock = new Mock<ILogger<UsersController>>();
// 设置模拟行为
userManagerMock
.Setup(x => x.FindByNameAsync(It.IsAny<string>()))
.ReturnsAsync(new ApplicationUser { UserName = "testuser" });
userManagerMock
.Setup(x => x.GetRolesAsync(It.IsAny<ApplicationUser>()))
.ReturnsAsync(new List<string> { "User" });
```
### 数据库模拟
**使用 InMemory 数据库:**
```csharp
var options = new DbContextOptionsBuilder<PlatformDbContext>()
.UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
.Options;
using var context = new PlatformDbContext(options);
// 添加测试数据
context.Users.Add(new ApplicationUser
{
UserName = "testuser",
Email = "test@example.com"
});
await context.SaveChangesAsync();
```
## 测试数据工厂
### 建议的 Fixture 模式
```csharp
public static class UserFixture
{
public static ApplicationUser CreateUser(string userName = "testuser")
{
return new ApplicationUser
{
UserName = userName,
Email = $"{userName}@example.com",
TenantInfo = new TenantInfo(new Tenant { TenantId = 1, TenantCode = "TEST" }),
EmailConfirmed = true,
CreatedTime = DateTimeOffset.UtcNow
};
}
public static ApplicationUser CreateAdminUser()
{
var user = CreateUser("admin");
return user;
}
}
public static class RoleFixture
{
public static ApplicationRole CreateRole(string name = "User")
{
return new ApplicationRole
{
Name = name,
DisplayName = name,
Description = $"{name} Role",
IsSystem = false,
CreatedTime = DateTime.UtcNow
};
}
public static ApplicationRole CreateSystemRole()
{
var role = CreateRole("Admin");
role.IsSystem = true;
return role;
}
}
```
## 断言模式
### FluentAssertions 使用示例
```csharp
using FluentAssertions;
// 对象断言
result.Should().NotBeNull();
result.Should().BeOfType<ApplicationUser>();
result.Id.Should().Be(1);
result.UserName.Should().Be("testuser");
// 集合断言
users.Should().HaveCount(10);
users.Should().Contain(u => u.UserName == "testuser");
users.Should().BeInAscendingOrder(u => u.CreatedTime);
// 异常断言
action.Should().Throw<InvalidOperationException>()
.WithMessage("User not found");
// HTTP 响应断言
response.StatusCode.Should().Be(HttpStatusCode.OK);
response.Content.Headers.ContentType.MediaType.Should().Be("application/json");
```
## 测试覆盖率
### 覆盖率目标建议
| 测试类型 | 覆盖率目标 | 说明 |
|---------|-----------|------|
| 单元测试 | ≥ 80% | 业务逻辑、命令、查询 |
| 集成测试 | ≥ 60% | 控制器端点、数据访问 |
| 整体 | ≥ 70% | 综合覆盖率 |
### 覆盖率工具
**工具:** `coverlet.collector``coverlet.msbuild`
**运行命令:**
```bash
# 使用 dotnet test
dotnet test --collect:"XPlat Code Coverage"
# 查看覆盖率报告
dotnet test --collect:"XPlat Code Coverage" --results-directory ./coverage
```
## CI/CD 测试集成
### 建议的测试脚本
```yaml
# azure-pipelines.yml 或 .github/workflows/test.yml
trigger:
- main
pool:
vmImage: 'ubuntu-latest'
steps:
- task: UseDotNet@2
inputs:
packageType: 'sdk'
version: '10.0.x'
- script: |
dotnet restore
dotnet build --configuration Release
displayName: 'Build'
- script: |
dotnet test --configuration Release --collect:"XPlat Code Coverage" --results-directory ./coverage
displayName: 'Test'
- script: |
dotnet tool install --global dotnet-reportgenerator-globaltool
reportgenerator -reports:./coverage/coverage.cobertura.xml -targetdir:./coverage-report -reporttypes:Html
displayName: 'Generate Coverage Report'
```
## 当前测试缺口
### 未测试的关键领域
1. **控制器端点** - 缺少 API 端点测试
2. **业务逻辑** - 命令处理器和查询处理器未测试
3. **身份验证流程** - OAuth2/OIDC 流程未测试
4. **多租户隔离** - 租户数据隔离逻辑未验证
5. **角色权限** - RBAC 逻辑未测试
### 优先测试建议
基于代码库特点,建议按以下优先级添加测试:
1. **高优先级**
- `TokenController` - 令牌颁发逻辑
- `UsersController` - 用户 CRUD 操作
- `RolesController` - 角色管理操作
2. **中优先级**
- `AuthorizationController` - 授权流程
- 审计日志功能
- 多租户数据隔离
3. **低优先级**
- 健康检查端点
- 静态文件服务
---
*测试模式分析2026-02-28*