docs: reorganize documentation, remove GSD workflow files
Some checks failed
Build and Push Docker / build (push) Failing after 2m33s

- Add CLEANDDD-WORKFLOW.md - CleanDDD development workflow guide
- Keep GATEWAY_ADMIN_README.md and DATABASE_SCHEMA_FIX.md
- Remove all .planning/ GSD workflow documents
- Remove old EF migrations (using EnsureCreated for test env)
This commit is contained in:
Kimi CLI 2026-03-08 15:48:58 +08:00
parent 2f8c35ef3e
commit fbc54c9ac5
23 changed files with 270 additions and 2566 deletions

View File

@ -1,100 +0,0 @@
# Fengling Console
## 这是什么
Fengling 微服务生态系统的中央管理控制台。负责用户管理、租户管理、OAuth 客户端管理,以及**网关配置管理**。是所有运维操作的中枢后台。
## 核心价值
统一的管理入口,负责所有运维相关的配置和操作,让其他服务专注于业务逻辑。
## 需求
### 已验证(现有功能)
- ✓ 用户管理CRUD、角色分配— 已有
- ✓ 租户管理 — 已有
- ✓ OAuth 客户端管理 — 已有
- ✓ 网关服务/路由/实例管理 — 已有GatewayController
- ✓ 租户申请审批流程 — 已有
### 进行中
- [ ] 实现配置变更广播机制(通过 PostgreSQL NOTIFY 通知所有网关实例)
- [ ] 实现 K8s 服务健康检查功能
- [ ] 实现配置变更广播机制(通知所有网关实例)
- [ ] 实现 K8s 服务健康检查功能
- [ ] 集成 Redis pub/sub 用于多实例通信
### 范围外
- [业务逻辑] — 由各微服务负责
- [API 认证] — 由 auth-service 负责
- [服务发现] — 由 service-discovery 负责
## 背景
**与 Gateway 的关系:**
```
fengling-console (管理平面)
├── 用户/租户/配置管理
└── 网关配置管理
├── GatewayDbContext (直接操作数据库)
├── GatewayController (API)
└── ReloadGatewayAsync() (待实现广播)
fengling-gateway (数据平面)
└── 监听配置变更,重新加载
```
**当前问题(来自 CONCERNS.md**
- OAuth 密钥硬编码
- CORS 允许所有(开发环境)
- 缺少测试覆盖
- ReloadGatewayAsync() 为空实现
**Console 已有能力:**
- GatewayDbContext - 管理网关路由、集群、实例数据
- GatewayController - 提供 /api/console/gateway/* API
- GatewayService - 业务逻辑
- 网关已有 PgSqlConfigChangeListener 使用 NOTIFY/LISTEN可复用
- OAuth 密钥硬编码
- CORS 允许所有(开发环境)
- 缺少测试覆盖
- Redis 已引用但未使用
- ReloadGatewayAsync() 为空实现
**Console 已有能力:**
- GatewayDbContext - 管理网关路由、集群、实例数据
- GatewayController - 提供 /api/console/gateway/* API
- GatewayService - 业务逻辑
- Redis 引用 - 可用于 pub/sub 广播
## 约束
- **多实例**Console 必须支持多实例部署
- **配置广播**:配置变更需要通知所有网关实例
- **K8s 健康**Console 需要实现 K8s 服务健康检查
- **技术栈**.NET 10.0, ASP.NET Core, PostgreSQL
## 关键决策
| 决策 | 理由 | 结果 |
|------|------|------|
| Console 作为运维中枢 | 集中管理,降低复杂度 | ✓ 良好 |
| Gateway 配置从 Console 变更 | 单一事实来源 | ✓ 良好 |
| PostgreSQL NOTIFY 广播 | 轻量方案,无需额外依赖 | ✓ 良好 |
|------|------|------|
| Console 作为运维中枢 | 集中管理,降低复杂度 | ✓ 良好 |
| Gateway 配置从 Console 变更 | 单一事实来源 | ✓ 良好 |
| Redis pub/sub 广播 | 成熟方案,易于实现 | ✓ 良好 |
---
*最后更新2026-03-02 初始化后*

View File

@ -1,93 +0,0 @@
# Roadmap
## 当前里程碑
### Phase 1: 实现 Gateway 配置管理及事件推送
- **目标**: 实现 Console 对 Gateway 配置的增删改查功能,并添加事件推送机制,使下游 yarpgateway 能够监听到配置变更
- **状态**: ✓ 完成
#### Goal
实现 Console 管理 Gateway 配置的完整能力,包括:
- Gateway 配置的 CRUD 操作
- 配置变更事件推送
- 下游 Gateway 监听配置变更并重载
#### Depends on
- 无外部依赖
#### Plans
- [x] 01-PLAN.md — 实现配置变更广播机制
---
### Phase 2: 实现 Gateway 插件系统
- **目标**: 实现 YARP 网关的插件系统,包括 Web UI 管理界面和动态编译加载功能
- **状态**: Not planned yet
#### Goal
实现 YARP 网关的插件系统规划与实现,包括:
- Web UI 管理界面(路由管理、集群管理、插件管理)
- 在线 C# 代码编辑Monaco Editor
- 动态编译加载Roslyn
- 插件生命周期管理
#### Depends on
- Phase 1: 实现 Gateway 配置管理及事件推送
#### Plans
- [ ] 02-PLAN.md — 实施计划
---
### Phase 3: 网关配置变更广播机制
- **目标**: 理解现有网关配置的完整链路:路由 -> 服务 -> 下游服务,梳理配置变更时如何发送新增/变更广播事件
- **状态**: Planned
#### Goal
理解现有网关配置的完整链路:
- 路由配置如何传递到下游服务
- 服务发现与下游服务的关系
- 配置变更时的新增/变更广播事件机制
#### Depends on
- Phase 2: 实现 Gateway 插件系统
#### Plans
- [x] 03-PLAN.md — 实施计划
---
### Phase 4: 适配 Platform 1.0.12 Gateway 实体变更
- **目标**: 适配 Platform 1.0.12 中的 Gateway 实体重构,修复编译错误,更新 Console 代码以使用新的 GwCluster/GwDestination/GwTenantRoute 模型
WR|- [x] 04-PLAN.md — 实施计划
NH|- **状态**: Planned
#### Goal
适配 Platform 1.0.12 实体变更:
- 移除 IInstanceStore 依赖,改用 IClusterStore
- 更新 GatewayService 使用新的接口方法
- 更新数据模型映射GatewayInstanceDto → GwDestination
- 修复编译错误
#### Depends on
- Phase 3: 网关配置变更广播机制
#### Plans
- [ ] 04-PLAN.md — 实施计划

View File

@ -1,83 +0,0 @@
# 状态Fengling Console
**最后更新:** 2026-03-04
---
## 项目引用
参考:.planning/PROJECT.md更新于 2026-03-02
**核心价值:** 统一的管理入口,负责所有运维相关的配置和操作,让其他服务专注于业务逻辑。
**当前重点:** Phase 4: 待添加(适配 Platform 1.0.12 实体变更)
---
## 项目状态
| 项目 | 状态 |
|------|------|
| PROJECT.md | ✓ 已初始化 |
| CODEBASE | ✓ 已有ARCHITECTURE.md, CONCERNS.md, STACK.md 等) |
| Roadmap | ✓ 已创建 |
| 变更文档 | ✓ 已创建 |
---
## 累积上下文
### 初始化
- **2026-03-02** 创建 PROJECT.md定义 Console 在生态系统中的角色
- 现有代码库(已有 ARCHITECTURE.md、INTEGRATIONS.md 等)
### 路线图演进
- **2026-03-02** Phase 1 已添加:实现 Gateway 配置管理及事件推送
- **2026-03-02** Phase 1 执行完成
- **2026-03-02** Phase 2 已添加:实现 Gateway 插件系统
- **2026-03-03** Phase 3 已添加:网关配置变更广播机制
- **2026-03-03** Phase 3 已规划
- **2026-03-03** Phase 3 上下文已捕获:广播策略 = 仅手动触发
- **2026-03-04** Platform 1.0.12 实体变更Gateway → GwCluster/GwDestination/GwTenantRoute
### 与 Gateway 的集成
| 组件 | 位置 | 现状 |
|------|------|------|
| GatewayDbContext | src/Data/ | 已实现,管理网关配置数据 |
| GatewayController | src/Controllers/ | 已实现,提供 API |
| GatewayService | src/Services/ | 已实现,业务逻辑 |
| ConfigNotificationService | src/Services/ | ✓ 已实现 PostgreSQL NOTIFY |
| ReloadGatewayAsync | src/Services/GatewayService.cs | 待集成通知服务 |
### 待完成任务
- **适配 Platform 1.0.12 实体变更**(编译错误待修复)
---
## 变更记录
### Platform 1.0.12 Gateway 实体变更
详细变更见:`.planning/docs/gateway-entity-changes-1.0.12.md`
**主要变更:**
1. GatewayInstance → GwDestination内嵌值对象
2. GatewayCluster → GwCluster聚合根包含 Destinations
3. GatewayRoute → GwTenantRoute通过 ClusterId 关联)
4. IInstanceStore 移除,改用 IClusterStore
---
## 备注
- Console 是运维中枢,网关配置的单一管理门户
- 广播策略:仅手动触发(通过 /reload 接口)
- 下游网关收到通知后自行查询数据库刷新
---
*最后更新2026-03-04*

View File

@ -1,109 +0,0 @@
# 架构
**分析日期:** 2026-02-28
## 模式概述
**整体:** 分层架构 + 领域驱动设计 (DDD)
**关键特性:**
- **控制器层 (Controllers)** - 处理 HTTP 请求,返回 API 响应
- **服务层 (Services)** - 业务逻辑实现,使用依赖注入
- **数据访问层** - 通过 Entity Framework Core 访问数据库
- **领域模型** - 来自外部 Domain 程序集 (Fengling.Platform.Domain)
## 层次
**控制器层 (Controllers)**
- 位置:`src/Controllers/`
- 包含:`UsersController.cs`, `RolesController.cs`, `TenantsController.cs`, `OAuthClientsController.cs`, `GatewayController.cs`
- 职责:处理 HTTP 请求、参数验证、返回响应
- 依赖Service 层接口
**服务层 (Services)**
- 位置:`src/Services/`
- 包含:`UserService.cs`, `RoleService.cs`, `TenantService.cs`, `OAuthClientService.cs`, `GatewayService.cs`, `H5LinkService.cs`
- 职责:业务逻辑实现、数据转换、事务管理
- 依赖Domain 模型、DbContext、UserManager、RoleManager
**数据层 (Data/Infrastructure)**
- PlatformDbContext - 平台业务数据
- GatewayDbContext - 网关配置数据
- 仓储模式:通过 NetCorePal.Extensions.Repository
**领域层 (Domain - 外部引用)**
- Fengling.Platform.Domain.AggregatesModel.UserAggregate
- Fengling.Platform.Domain.AggregatesModel.RoleAggregate
- Fengling.Platform.Domain.AggregatesModel.TenantAggregate
## 数据流
**典型请求流程:**
1. **HTTP 请求** → Controller 接收
2. **参数验证** → DTO 绑定
3. **业务处理** → Service 层执行
4. **数据持久化** → EF Core 保存
5. **响应返回** → Controller 返回结果
**状态管理:**
- 无状态 API 设计
- 状态存储在 PostgreSQL 数据库
- 认证状态通过 JWT Token 传递
## 关键抽象
**服务接口:**
- `IUserService` - 用户管理
- `IRoleService` - 角色管理
- `ITenantService` - 租户管理
- `IOAuthClientService` - OAuth 客户端管理
- `IGatewayService` - 网关配置
- `IH5LinkService` - H5 链接服务
**数据传输对象 (DTO)**
- 位置:`src/Models/Dtos/`
- 模式CreateXxxDto, UpdateXxxDto, XxxDto, XxxQueryDto
- 用途API 请求/响应数据结构
## 入口点
**主入口:**
- 位置:`src/Program.cs`
- 触发:应用启动时执行
- 职责:服务注册、中间件配置、管道构建
**API 端点:**
- `/api/console/[controller]` - RESTful API 前缀
- `/swagger` - API 文档
## 错误处理
**策略:**
- 异常捕获 + 日志记录
- 返回标准 HTTP 状态码
- 错误详情通过响应体返回
**模式:**
```csharp
try {
// 业务逻辑
} catch (KeyNotFoundException ex) {
return NotFound();
} catch (InvalidOperationException ex) {
return BadRequest();
} catch (Exception ex) {
_logger.LogError(ex, "...");
return StatusCode(500);
}
```
## 跨领域关注
**日志:** Microsoft.Extensions.Logging + ILogger<T>
**验证:** ASP.NET Core Model Validation + FluentValidation (已引用)
**认证:** OpenIddict + JWT Bearer
---
*架构分析2026-02-28*

View File

@ -1,75 +0,0 @@
# 代码库问题
**分析日期:** 2026-02-28
## 技术债务
**硬编码密钥:**
- 问题:`src/Program.cs:62` 包含硬编码的 OAuth 客户端密钥
- 影响:安全风险,不适合生产环境
- 修复:使用环境变量或密钥管理服务
**缺少测试项目:**
- 问题:当前项目没有测试目录
- 影响:无法进行自动化测试,难以保证代码质量
- 修复:添加 xUnit/MSTest 测试项目
## 已知问题
**当前未发现严重 Bug**
- 代码结构清晰,异常处理完善
## 安全考虑
**OAuth 密钥硬编码:**
- 风险:生产环境密钥泄露风险
- 当前缓解:仅在开发环境使用
- 建议:使用环境变量或密钥 vault
**CORS 允许所有:**
- 风险:`policy.AllowAnyOrigin()` 存在安全风险
- 当前缓解:仅开发环境使用
- 建议:生产环境限制具体域名
## 性能问题
**当前未发现明显性能瓶颈:**
- 使用 Entity Framework Core 进行数据库查询
- 支持分页查询,避免大数据量返回
## 脆弱区域
**审计日志记录:**
- 每次操作都写入审计日志
- 高并发场景可能成为瓶颈
- 建议:考虑异步写入或批量处理
## 扩展限制
**当前架构支持:**
- 水平扩展:通过 Docker 容器化易于扩展
- 功能扩展:分层架构便于添加新模块
## 依赖风险
**外部依赖:**
- NetCorePal.Extensions - 自定义扩展库
- OpenIddict - 社区维护的开源库
- 建议:关注版本更新,及时升级
## 缺失功能
**缺失测试覆盖:**
- 无单元测试
- 无集成测试
- 无 API 测试
- 优先级:高
**缺失功能:**
- 无缓存层Redis 已引用但未使用)
- 无消息队列集成
- 无后台任务系统
---
*问题审计2026-02-28*

View File

@ -1,118 +0,0 @@
# 编码规范
**分析日期:** 2026-02-28
## 命名模式
**文件:**
- PascalCase`UserService.cs`、`UsersController.cs`
**类/接口:**
- PascalCase`UserService`、`IUserService`
**方法:**
- PascalCase`GetUsersAsync`、`CreateUserAsync`
**变量:**
- camelCase`userService`、`userName`
## 代码风格
**格式化:**
- 使用 .editorconfig 或 Rider/VS 默认格式化
- 花括号风格K&R
**命名空间:**
- 层级式:`Fengling.Console.Controllers`
## 导入组织
**顺序:**
1. System 命名空间
2. 项目内部命名空间
3. 第三方库
**示例:**
```csharp
using System;
using System.Collections.Generic;
using Fengling.Console.Services;
using Fengling.Platform.Domain;
using Microsoft.AspNetCore.Mvc;
```
## 错误处理
**模式:**
- 使用 try-catch 捕获异常
- 按异常类型返回不同 HTTP 状态码
- 记录日志:`_logger.LogError(ex, "...")`
**示例:**
```csharp
try {
await _userService.CreateUserAsync(dto);
} catch (InvalidOperationException ex) {
return BadRequest(new { message = ex.Message });
} catch (Exception ex) {
_logger.LogError(ex, "Error creating user");
return StatusCode(500, new { message = ex.Message });
}
```
## 日志
**框架:** Microsoft.Extensions.Logging + ILogger<T>
**模式:**
```csharp
private readonly ILogger<UsersController> _logger;
_logger.LogError(ex, "Error message");
_logger.LogWarning(ex, "Warning message");
```
## 注释
**何时注释:**
- 公开 API 方法使用 XML 文档注释
- 复杂业务逻辑添加说明
**XML 注释示例:**
```csharp
/// <summary>
/// 获取用户列表
/// </summary>
/// <param name="query">分页查询参数</param>
/// <returns>分页的用户列表</returns>
[HttpGet]
public async Task<ActionResult<PagedResultDto<UserDto>>> GetUsers(...)
```
## 函数设计
**大小:**
- 保持方法简洁,单一职责
- 复杂逻辑拆分到私有方法
**参数:**
- 使用 DTO 进行参数分组
- 异步方法使用 Async 后缀
**返回值:**
- 使用 Task<T> 返回异步结果
- 集合使用 IEnumerable<T>
## 模块设计
**导出:**
- 公开接口I[Xxx]Service
- 实现类XxxService
**依赖注入:**
- 构造函数注入
- 接口+实现配对
---
*规范分析2026-02-28*

View File

@ -1,86 +0,0 @@
# 外部集成
**分析日期:** 2026-02-28
## APIs & 外部服务
**身份认证:**
- **OpenIddict** - OAuth 2.0 / OpenID Connect 身份提供商
- 实现:内置 OpenIddict 认证服务器
- 客户端fengling-api
- 密钥fengling-api-secret硬编码`src/Program.cs:62`
- 发行者http://localhost:5132/
**反向代理:**
- **YARP (YarpGateway)** - 反向代理网关
- 项目引用:`../../fengling-gateway/src/YarpGateway.csproj`
- 用于 API 网关和请求转发
## 数据存储
**数据库:**
- **PostgreSQL** - 主数据库
- 连接:`DefaultConnection` (PlatformDbContext)
- 连接:`GatewayConnection` (GatewayDbContext)
- ORMEntity Framework Core + Npgsql
**表结构:**
- 用户表 (ApplicationUser)
- 角色表 (ApplicationRole)
- 租户表 (Tenant)
- OAuth 客户端表
- 审计日志表 (AuditLog)
- 网关配置表
## 身份认证
**认证方案:**
- OpenIddict Validation (Bearer Token)
- JWT Bearer 认证
- ASP.NET Core Identity
**用户管理:**
- ASP.NET Core Identity
- 支持租户隔离 (TenantInfo)
## 监控与可观测性
**日志:**
- 使用 Microsoft.Extensions.Logging
- 通过 ILogger<T> 注入
- 开发环境启用敏感数据日志
**API 文档:**
- Swagger / OpenAPI
- 端点:`/swagger/v1/swagger.json`
## CI/CD & 部署
**主机:**
- Docker 容器化
- Dockerfile 位于 `src/Dockerfile`
**CI/CD 流水线:**
- Gitea Actions (`.gitea/workflows/`)
- `deploy.yml` - 部署流水线
- `docker.yml` - Docker 构建
- `build.yml` - 构建流水线
## 环境配置
**必需环境变量:**
- `ConnectionStrings:DefaultConnection` - 平台数据库连接
- `ConnectionStrings:GatewayConnection` - 网关数据库连接
**配置文件:**
- `appsettings.json` - 应用配置
- `appsettings.Development.json` - 开发配置
## Webhooks & 回调
**无外部 Webhooks**
- 当前无外部系统回调集成
---
*集成审计2026-02-28*

View File

@ -1,86 +0,0 @@
# 技术栈
**分析日期:** 2026-02-28
## 语言
**主要:**
- **C#** (.NET 10.0) - 后端 API 开发
**次要:**
- **JSON** - 配置文件和数据交换格式
## 运行时
**环境:**
- .NET 10.0.103 SDK
- ASP.NET Core 10.0 Web 应用
**包管理:**
- NuGet
- `global.json` 指定 SDK 版本 10.0.103rollForward: latestMinor
## 框架
**核心:**
- **ASP.NET Core 10.0** - Web 框架
- **Entity Framework Core** - ORM用于数据访问
- **Npgsql.EntityFrameworkCore.PostgreSQL** - PostgreSQL 数据库驱动
**身份认证:**
- **OpenIddict** - OAuth 2.0 / OIDC 身份提供商
- **Microsoft.AspNetCore.Authentication.JwtBearer** - JWT 令牌认证
**其他:**
- **Swashbuckle.AspNetCore** - Swagger/OpenAPI 文档
- **QRCoder** + **SkiaSharp** - 二维码生成
- **NetCorePal.Extensions** - 扩展库集合
## 关键依赖
**核心业务:**
- `Npgsql.EntityFrameworkCore.PostgreSQL` - PostgreSQL 数据库访问
- `OpenIddict.*` - 身份认证和授权
- `Microsoft.AspNetCore.Identity.EntityFrameworkCore` - 用户身份管理
**扩展库:**
- `NetCorePal.Extensions.AspNetCore` - ASP.NET Core 扩展
- `NetCorePal.Extensions.DistributedLocks.Redis` - 分布式锁
- `NetCorePal.Extensions.Repository.EntityFrameworkCore` - 仓储模式
**工具库:**
- `Swashbuckle.AspNetCore` - API 文档
- `QRCoder` + `SkiaSharp` - 二维码生成
- `Microsoft.OpenApi` - OpenAPI 支持
## 项目引用
- `YarpGateway` - 反向代理网关
- `Fengling.Platform.Infrastructure` - 平台基础设施
## 配置
**环境配置:**
- `appsettings.json` - 默认配置
- `appsettings.Development.json` - 开发环境配置
- `launchSettings.json` - 启动配置
**数据库连接:**
- `DefaultConnection` - 平台数据库 (PlatformDbContext)
- `GatewayConnection` - 网关数据库 (GatewayDbContext)
## 平台要求
**开发:**
- .NET 10.0 SDK
- PostgreSQL 数据库
- Visual Studio Code / Rider / Visual Studio
**生产:**
- Docker 容器化部署
- Linux 服务器
- PostgreSQL 数据库
---
*技术栈分析2026-02-28*

View File

@ -1,96 +0,0 @@
# 代码库结构
**分析日期:** 2026-02-28
## 目录布局
```
fengling-console/
├── global.json # .NET SDK 版本配置
├── Directory.Build.props # 项目共享属性
├── src/
│ ├── Fengling.Console.csproj # 主项目文件
│ ├── Program.cs # 应用入口
│ ├── Dockerfile # Docker 构建文件
│ ├── appsettings.json # 应用配置
│ ├── appsettings.Development.json # 开发环境配置
│ ├── Properties/
│ │ └── launchSettings.json # 启动配置
│ ├── Controllers/ # API 控制器
│ ├── Services/ # 业务服务层
│ ├── Models/
│ │ └── Dtos/ # 数据传输对象
│ └── bin/Debug/ # 编译输出
├── .gitea/workflows/ # CI/CD 流水线
```
## 目录用途
**src/Controllers/**
- 用途API 端点定义
- 包含文件:
- `UsersController.cs` - 用户管理 API
- `RolesController.cs` - 角色管理 API
- `TenantsController.cs` - 租户管理 API
- `OAuthClientsController.cs` - OAuth 客户端 API
- `GatewayController.cs` - 网关配置 API
- `GlobalUsing.cs` - 全局 using 指令
**src/Services/**
- 用途:业务逻辑实现
- 包含文件:
- `UserService.cs` - 用户业务逻辑
- `RoleService.cs` - 角色业务逻辑
- `TenantService.cs` - 租户业务逻辑
- `OAuthClientService.cs` - OAuth 客户端逻辑
- `GatewayService.cs` - 网关配置逻辑
- `H5LinkService.cs` - H5 链接服务
**src/Models/Dtos/**
- 用途API 数据传输对象
- 包含文件:
- CreateXxxDto.cs (CreateUserDto, CreateRoleDto, CreateTenantDto, CreateClientDto)
- UpdateXxxDto.cs (UpdateUserDto, UpdateRoleDto, UpdateTenantDto, UpdateClientDto)
- XxxDto.cs (UserDto, RoleDto, TenantDto, OAuthClientDto, GatewayDto)
- XxxQueryDto.cs (UserQueryDto, RoleQueryDto, TenantQueryDto, OAuthClientQueryDto)
- `PaginationDto.cs` - 分页结果
- `ResetPasswordDto.cs` - 密码重置
- `TenantSettingsDto.cs` - 租户设置
## 关键文件位置
**入口点:**
- `src/Program.cs` - 应用启动配置和服务注册
**配置:**
- `src/appsettings.json` - 应用配置
- `src/appsettings.Development.json` - 开发环境配置
**核心逻辑:**
- `src/Services/UserService.cs` - 用户服务实现
- `src/Controllers/UsersController.cs` - 用户 API
## 命名约定
**文件:**
- PascalCase`UserService.cs`、`UsersController.cs`
**目录:**
- PascalCase`Controllers/`、`Services/`、`Models/Dtos/`
**类/接口:**
- PascalCase`UserService`、`IUserService`
## 新增代码位置
**新增功能:**
- API 端点:`src/Controllers/`
- 业务逻辑:`src/Services/`
- DTO`src/Models/Dtos/`
**新增测试:**
- 建议位置:单独的测试项目(如 `tests/` 目录)
---
*结构分析2026-02-28*

View File

@ -1,144 +0,0 @@
# 测试模式
**分析日期:** 2026-02-28
## 测试框架
**未检测到测试框架:**
- 当前项目未包含测试项目
- 建议添加 xUnit、NUnit 或 MSTest
**推荐配置:**
- 框架xUnit
- Mock 库Moq
- 集成测试Microsoft.AspNetCore.Mvc.Testing
## 测试文件组织
**建议结构:**
```
tests/
├── Fengling.Console.Tests/
│ ├── Unit/
│ │ ├── Services/
│ │ └── Controllers/
│ └── Integration/
```
## 测试结构
**单元测试示例:**
```csharp
public class UserServiceTests
{
private readonly Mock<UserManager<ApplicationUser>> _userManagerMock;
private readonly UserService _userService;
public UserServiceTests()
{
_userManagerMock = new Mock<UserManager<ApplicationUser>>(...);
_userService = new UserService(...);
}
[Fact]
public async Task GetUsersAsync_ReturnsPagedResults()
{
// Arrange
var expectedUsers = new List<UserDto> { ... };
// Act
var result = await _userService.GetUsersAsync(1, 10);
// Assert
Assert.Equal(expectedUsers.Count, result.TotalCount);
}
}
```
## 模拟 (Mocking)
**常用 Mock 库:**
- Moq - 主流 Mock 框架
**Mock 对象:**
```csharp
var mockUserManager = new Mock<UserManager<ApplicationUser>>(
store: mockStore.Object,
optionsAccessor: new Mock<IOptions<IdentityOptions>>().Object,
passwordHasher: new Mock<IPasswordHasher<ApplicationUser>>().Object,
validators: new List<IUserValidator<ApplicationUser>>(),
keyNormalizer: new Mock<ILookupNormalizer>().Object,
errors: new IdentityErrorDescriber(),
logger: new Mock<ILogger<UserManager<ApplicationUser>>>().Object,
services: new Mock<IServiceProvider>().Object);
```
## Fixture 和工厂
**测试数据:**
- 使用静态工厂方法创建测试数据
- 每个测试方法独立,不共享状态
**示例:**
```csharp
public static class TestData
{
public static CreateUserDto CreateValidUserDto() => new CreateUserDto
{
UserName = "testuser",
Email = "test@example.com",
Password = "Test@123456",
RealName = "Test User"
};
}
```
## 测试类型
**单元测试:**
- 范围Service 层业务逻辑
- 重点:边界条件、异常处理
**集成测试:**
- 范围Controller API 端点
- 使用Microsoft.AspNetCore.Mvc.Testing
## 常见模式
**异步测试:**
```csharp
[Fact]
public async Task CreateUserAsync_ValidInput_ReturnsCreatedUser()
{
// Arrange
var dto = TestData.CreateValidUserDto();
// Act
var result = await _userService.CreateUserAsync(dto);
// Assert
Assert.NotNull(result);
Assert.Equal(dto.UserName, result.UserName);
}
```
**异常测试:**
```csharp
[Fact]
public async Task CreateUserAsync_DuplicateUser_ThrowsException()
{
// Arrange
var dto = TestData.CreateValidUserDto();
_userManagerMock
.Setup(x => x.CreateAsync(It.IsAny<ApplicationUser>(), It.IsAny<string>()))
.ReturnsAsync(IdentityResult.Failed(new IdentityError { Description = "Duplicate" }));
// Act & Assert
await Assert.ThrowsAsync<InvalidOperationException>(() =>
_userService.CreateUserAsync(dto));
}
```
---
*测试分析2026-02-28*

View File

@ -1,141 +0,0 @@
# Gateway 实体变更记录
**变更日期:** 2026-03-04
**Platform 版本:** 1.0.12
---
## 变更概述
Platform 1.0.12 对 Gateway 相关实体进行了重构,主要变化是将实例(Instance)内嵌到集群(Cluster)中,简化了领域模型。
---
## 实体变更
### 1. GwDestination新增 - 原 GatewayInstance
**旧名称:** GatewayInstance
**新名称:** GwDestination
**类型:** 值对象(内嵌于 GwCluster
```csharp
public class GwDestination
{
public string DestinationId { get; set; } // 目标标识
public string Address { get; set; } // 后端地址
public string? Health { get; set; } // 健康检查端点
public int Weight { get; set; } = 1; // 权重
public int HealthStatus { get; set; } = 1; // 健康状态
public int Status { get; set; } = 1; // 状态
}
```
### 2. GwCluster重构 - 原 GatewayCluster
**旧名称:** GatewayCluster
**新名称:** GwCluster
**类型:** 聚合根
**主要变化:**
- 包含 `List<GwDestination> Destinations` 作为内嵌集合
- 包含 `GwLoadBalancingPolicy` 负载均衡策略
- 包含 `GwHealthCheckConfig` 健康检查配置
- 包含 `GwSessionAffinityConfig` 会话亲和配置
### 3. GwTenantRoute重构 - 原 GatewayRoute
**旧名称:** GatewayRoute
**新名称:** GwTenantRoute
**类型:** 实体
**主要变化:**
- 通过 `ClusterId` 关联到 `GwCluster`
- 包含 `GwRouteMatch` 路由匹配配置
- 支持 `GwLoadBalancingPolicy` 路由级别负载均衡覆盖
---
## 接口变更
### IInstanceStore已移除
**状态:** 已移除
**原因:** 实例(Destination)现在是 GwCluster 的内嵌对象,不再需要独立的 IInstanceStore 接口。
### IClusterStore新增
```csharp
public interface IClusterStore
{
// Basic CRUD
Task<GwCluster?> FindByIdAsync(string? id, CancellationToken cancellationToken = default);
Task<GwCluster?> FindByClusterIdAsync(string clusterId, CancellationToken cancellationToken = default);
Task<IList<GwCluster>> GetAllAsync(CancellationToken cancellationToken = default);
Task<IList<GwCluster>> GetPagedAsync(int page, int pageSize, string? clusterId = null,
string? name = null, int? status = null, CancellationToken cancellationToken = default);
Task<int> GetCountAsync(string? clusterId = null, string? name = null,
int? status = null, CancellationToken cancellationToken = default);
Task<IdentityResult> CreateAsync(GwCluster cluster, CancellationToken cancellationToken = default);
Task<IdentityResult> UpdateAsync(GwCluster cluster, CancellationToken cancellationToken = default);
Task<IdentityResult> DeleteAsync(GwCluster cluster, CancellationToken cancellationToken = default);
// Destination management (NEW)
Task<GwCluster?> AddDestinationAsync(string clusterId, GwDestination destination, CancellationToken cancellationToken = default);
Task<GwCluster?> UpdateDestinationAsync(string clusterId, string destinationId, GwDestination destination, CancellationToken cancellationToken = default);
Task<GwCluster?> RemoveDestinationAsync(string clusterId, string destinationId, CancellationToken cancellationToken = default);
}
```
---
## 架构变化
### 旧架构
```
Route → ClusterId → Instance (独立实体)
```
### 新架构
```
TenantRoute → ClusterId → GwCluster (聚合根) → List<GwDestination>
```
---
## Console 适配需求
由于接口变更Console 需要进行以下适配:
1. **移除 IInstanceStore 依赖**
- 移除 `IInstanceStore` 注入
- 使用 `IClusterStore` 替代
2. **更新 GatewayService**
- 实例操作改为通过 `IClusterStore.AddDestinationAsync` 等方法
- 查询实例改为从 `GwCluster.Destinations` 获取
3. **更新数据模型**
- GatewayInstanceDto → 从 GwDestination 映射
- GatewayClusterDto → 从 GwCluster 映射
4. **更新 API 端点**
- `/instances` 相关端点可能需要调整
---
## 相关文件
### Platform 侧
- `Fengling.Platform.Domain/AggregatesModel/GatewayAggregate/GwCluster.cs`
- `Fengling.Platform.Domain/AggregatesModel/GatewayAggregate/GwDestination.cs`
- `Fengling.Platform.Domain/AggregatesModel/GatewayAggregate/GwTenantRoute.cs`
- `Fengling.Platform.Infrastructure/IClusterStore.cs`
- `Fengling.Platform.Infrastructure/ClusterStore.cs`
### Console 侧(需要适配)
- `src/Services/GatewayService.cs` - 需要适配新接口
- `src/Program.cs` - 需要更新依赖注入

View File

@ -1,712 +0,0 @@
# 网关插件系统技术方案
## 一、概述
本文档描述 YARP 网关的插件系统规划,包括 Web UI 管理界面和动态编译加载两大核心功能。
---
## 二、整体架构
```
┌─────────────────────┐
│ fengling-console │ (运维后端 - Backend)
│ web 前端 │
└─────────┬───────────┘
│ HTTP API
┌─────────────────────┐
│ fengling-console │ (运维服务端)
│ │
│ - 路由管理 API │ ───▶ 数据库持久化
│ - 集群管理 API │ ───▶ Redis Pub/Sub (发布事件)
│ - 插件管理 API │
└─────────┬───────────┘
│ 事件订阅
┌─────────┴───────────┐
│ fengling-gateway │ (YARP 网关多实例)
│ - YARP 代理 │
│ - 插件执行 │
│ - 事件监听 │
└─────────────────────┘
```
### 项目职责
| 项目 | 职责 |
|------|------|
| **fengling-gateway** | 纯 YARP 代理 + 事件订阅 + 插件执行 |
| **fengling-console** | 运维 API + 配置持久化 + 事件发布 |
| **fengling-console-web** | 前端 UI (Monaco Editor) |
---
## 三、Web UI 管理界面
### 3.1 技术选型
| 项目 | 选择 | 理由 |
|------|------|------|
| 前端框架 | React/Vue | 独立前端项目 |
| 编辑器 | Monaco Editor | VS Code 同款,体验一致 |
| 路由 | `/gateway` | 运维平台内统一路由 |
### 3.2 功能模块
```
/gateway
├── 路由管理 (Routes)
│ ├── 列表/搜索
│ ├── 创建/编辑/删除
│ └── 路由规则配置
├── 集群管理 (Clusters)
│ ├── 上下游服务列表
│ ├── 实例管理
│ └── 健康状态
├── 插件管理 (Plugins)
│ ├── 已加载插件列表
│ ├── 上传 DLL
│ └── 在线编写 C# 代码
└── 监控统计
├── QPS/延迟
└── 流量图表
```
## 一、概述
本文档描述 YARP 网关的插件系统规划,包括 Web UI 管理界面和动态编译加载两大核心功能。
---
## 二、Web UI 管理界面
### 2.1 技术选型
| 项目 | 选择 | 理由 |
|------|------|------|
| 框架 | Razor Pages | 嵌入主应用,单项目部署 |
| 路由 | `/gateway/ui` | 参考 SwaggerUI 风格 |
| 编辑器 | Monaco Editor | VS Code 同款,体验一致 |
### 2.2 功能模块
```
/gateway/ui
├── 路由管理 (Routes)
│ ├── 列表/搜索
│ ├── 创建/编辑/删除
│ └── 路由规则配置
├── 集群管理 (Clusters)
│ ├── 上下游服务列表
│ ├── 实例管理
│ └── 健康状态
├── 插件管理 (Plugins)
│ ├── 已加载插件列表
│ ├── 上传 DLL
│ └── 在线编写 C# 代码
└── 监控统计
├── QPS/延迟
└── 流量图表
```
---
## 三、插件系统架构
### 3.1 插件类型定义
```csharp
namespace Fengling.Gateway.Plugin.Abstractions
{
/// <summary>
/// 插件基础接口
/// </summary>
public interface IGatewayPlugin
{
string Name { get; }
string Version { get; }
string? Description { get; }
Task OnLoadAsync();
Task OnUnloadAsync();
}
/// <summary>
/// 请求处理插件
/// </summary>
public interface IRequestPlugin : IGatewayPlugin
{
/// <summary>请求到达网关前</summary>
Task<HttpContext?> OnRequestAsync(HttpContext context);
/// <summary>路由决策后</summary>
Task<HttpContext?> OnRouteMatchedAsync(HttpContext context, RouteConfig route);
/// <summary>转发到后端前</summary>
Task<HttpContext?> OnForwardingAsync(HttpContext context, HttpRequestMessage request);
}
/// <summary>
/// 响应处理插件
/// </summary>
public interface IResponsePlugin : IGatewayPlugin
{
/// <summary>后端响应后</summary>
Task OnBackendResponseAsync(HttpContext context, HttpResponseMessage response);
/// <summary>返回客户端前</summary>
Task OnResponseFinalizingAsync(HttpContext context);
}
/// <summary>
/// 路由转换插件
/// </summary>
public interface IRouteTransformPlugin : IGatewayPlugin
{
Task<RouteConfig> TransformRouteAsync(RouteConfig original, HttpContext context);
}
/// <summary>
/// 负载均衡插件
/// </summary>
public interface ILoadBalancePlugin : IGatewayPlugin
{
Task<Destination> SelectDestinationAsync(
IReadOnlyList<Destination> destinations,
HttpContext context);
}
}
```
### 3.2 插件阶段枚举
```csharp
public enum PipelineStage
{
None = 0,
OnRequest = 1, // 请求到达网关前
OnRoute = 2, // 路由决策时
OnRequestBackend = 3, // 转发到后端前
OnResponseBackend = 4, // 后端响应后
OnResponse = 5 // 返回给客户端前
}
```
---
## 四、核心模块设计
### 4.1 依赖管理A方案
#### 简单场景:直接使用网关已有程序集
```csharp
// API 暴露网关程序集
[ApiController]
public class AssembliesController : ControllerBase
{
[HttpGet("available")]
public List<AssemblyInfo> GetAvailableAssemblies()
{
return AppDomain.CurrentDomain.GetAssemblies()
.Where(a => !a.IsDynamic && !string.IsNullOrEmpty(a.Location))
.Select(a => new AssemblyInfo
{
Name = a.GetName().Name,
Version = a.GetName().Version?.ToString(),
Location = a.Location
})
.OrderBy(a => a.Name)
.ToList();
}
}
```
#### 复杂场景:上传 ZIP 包
```csharp
public class PluginUploadService
{
private readonly IObjectStorage _storage;
private readonly PluginDbContext _db;
private readonly string _localPluginPath = "/app/plugins";
public async Task<PluginPackage> UploadPluginAsync(IFormFile zipFile, string pluginName)
{
// 1. 上传到对象存储
var storageKey = $"plugins/{Guid.NewGuid()}/{zipFile.FileName}";
await _storage.UploadAsync(zipFile.OpenReadStream(), storageKey);
// 2. 保存到数据库
var plugin = new PluginPackage
{
Id = Guid.NewGuid(),
Name = pluginName,
StorageKey = storageKey,
UploadedAt = DateTime.UtcNow,
Status = PluginStatus.Pending
};
await _db.PluginPackages.AddAsync(plugin);
await _db.SaveChangesAsync();
return plugin;
}
public async Task ExtractAndLoadAsync(Guid pluginId)
{
var plugin = await _db.PluginPackages.FindAsync(pluginId);
// 3. 下载到本地
var localDir = Path.Combine(_localPluginPath, plugin.Id.ToString());
Directory.CreateDirectory(localDir);
await _storage.DownloadAsync(plugin.StorageKey, localDir + ".zip");
// 4. 解压
ZipFile.ExtractToDirectory(localDir + ".zip", localDir, overwriteFiles: true);
File.Delete(localDir + ".zip");
// 5. 加载插件
await _pluginManager.LoadFromDirectoryAsync(localDir);
}
}
```
#### ZIP 上传验证
```csharp
public class PluginValidationService
{
public async Task<PluginValidationResult> ValidateAsync(Stream zipStream)
{
var tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
await ExtractZipAsync(zipStream, tempDir);
try
{
var dlls = Directory.GetFiles(tempDir, "*.dll");
var result = new PluginValidationResult();
foreach (var dll in dlls)
{
var dllResult = await ValidateDllAsync(dll);
result.Assemblies.Add(dllResult);
}
var validPlugins = result.Assemblies
.Where(a => a.IsValidPlugin)
.ToList();
if (validPlugins.Count == 0)
{
result.IsValid = false;
result.ErrorMessage = "未找到实现 IGatewayPlugin 接口的类";
}
else
{
result.IsValid = true;
result.ValidPluginTypes = validPlugins
.SelectMany(a => a.PluginTypes)
.ToList();
}
return result;
}
finally
{
Directory.Delete(tempDir, true);
}
}
private async Task<DllValidationResult> ValidateDllAsync(string dllPath)
{
var result = new DllValidationResult { DllName = Path.GetFileName(dllPath) };
try
{
var assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(dllPath);
var pluginTypes = assembly.GetTypes()
.Where(t => typeof(IGatewayPlugin).IsAssignableFrom(t))
.Where(t => !t.IsAbstract && !t.IsInterface)
.ToList();
if (pluginTypes.Count > 0)
{
result.IsValidPlugin = true;
result.PluginTypes = pluginTypes.Select(t => new PluginTypeInfo
{
TypeName = t.FullName,
ImplementedInterfaces = t.GetInterfaces().Select(i => i.Name).ToList()
}).ToList();
}
}
catch (Exception ex)
{
result.IsValid = false;
result.ErrorMessage = ex.Message;
}
return result;
}
}
```
---
### 4.2 插件间通信B方案
采用**方案 3强类型 + 弱类型混合**
#### 插件上下文
```csharp
public class GatewayContext
{
// 预定义常用字段(强类型)
public string? UserId { get; set; }
public string? TenantId { get; set; }
public UserTier Tier { get; set; }
public bool IsAuthenticated { get; set; }
public DateTime RequestTime { get; set; }
// 扩展数据(弱类型)
public PluginDataBag Data { get; } = new();
}
public class PluginDataBag
{
private readonly Dictionary<string, object> _data = new();
public T? Get<T>(string key) => _data.TryGetValue(key, out var v) ? (T)v : default;
public void Set<T>(string key, T value) => _data[key] = value!;
}
```
#### 使用示例
```csharp
// 插件 A: 认证
public class AuthPlugin : IRequestPlugin
{
public async Task<HttpContext?> OnRequestAsync(HttpContext context)
{
var userId = ValidateToken(context);
if (userId != null)
{
context.Items["CurrentUserId"] = userId;
context.Items["IsAuthenticated"] = true;
}
return context;
}
}
// 插件 B: 审计
public class AuditPlugin : IRequestPlugin
{
public async Task<HttpContext?> OnRequestAsync(HttpContext context)
{
if (context.Items.TryGetValue("CurrentUserId", out var userId))
{
await _logger.LogAsync($"User {userId} accessed {context.Request.Path}");
}
return context;
}
}
```
---
### 4.3 在线代码编辑器C方案
采用 **Monaco Editor + 前端模拟补全**
> **补充说明**:如需更完整的 C# IntelliSense如真实代码分析、跳转到定义可使用 Microsoft 官方的 **roslyn-language-server**VS Code C# 扩展背后使用的语言服务器)。
>
> **部署方式**
> ```bash
> # 安装
> dotnet tool install -g Microsoft.CodeAnalysis.LanguageServer
>
> # 启动服务
> roslyn-languageserver --port 5000
> ```
>
> 前端通过 WebSocket 连接该服务获取完整的语言特性支持。但目前阶段前端模拟补全已足够使用。
```html
<script src="https://cdn.jsdelivr.net/npm/monaco-editor@0.45.0/min/vs/loader.js"></script>
<div id="editor" style="height: 500px;"></div>
<script>
require.config({ paths: { 'vs': 'https://cdn.jsdelivr.net/npm/monaco-editor@0.45.0/min/vs' }});
require(['vs/editor/editor.main'], function() {
monaco.editor.create(document.getElementById('editor'), {
value: getEditorTemplate(),
language: 'csharp',
theme: 'vs-dark',
automaticLayout: true,
minimap: { enabled: false },
fontSize: 14,
lineNumbers: 'on',
scrollBeyondLastLine: false
});
// 注册 C# 补全
monaco.languages.registerCompletionItemProvider('csharp', {
provideCompletionItems: (model, position) => {
const suggestions = [
// 常用类型
{ label: 'HttpContext', kind: monaco.languages.CompletionItemKind.Class },
{ label: 'HttpRequest', kind: monaco.languages.CompletionItemKind.Class },
{ label: 'HttpResponse', kind: monaco.languages.CompletionItemKind.Class },
// 插件接口方法
{ label: 'OnRequestAsync', kind: monaco.languages.CompletionItemKind.Method },
{ label: 'OnResponseAsync', kind: monaco.languages.CompletionItemKind.Method },
{ label: 'TransformRouteAsync', kind: monaco.languages.CompletionItemKind.Method },
// 常用属性
{ label: 'ctx.Request', kind: monaco.languages.CompletionItemKind.Property },
{ label: 'ctx.Response', kind: monaco.languages.CompletionItemKind.Property },
{ label: 'ctx.Items', kind: monaco.languages.CompletionItemKind.Property },
];
return { suggestions };
}
});
});
</script>
```
#### 编辑器模板
```csharp
// 生成的代码模板
$@"
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Fengling.Gateway.Plugin.Abstractions;
public class {pluginName} : IRequestPlugin
{{
public string Name => ""{pluginName}"";
public string Version => ""1.0.0"";
public async Task<HttpContext?> OnRequestAsync(HttpContext ctx)
{{
// 编写你的逻辑
{userCode}
}}
}}
";
```
---
### 4.4 插件生命周期管理
```csharp
public class PluginManager
{
private readonly Dictionary<string, PluginInstance> _plugins = new();
private readonly RoslynPluginCompiler _compiler = new();
public async Task<PluginInstance> LoadPluginAsync(byte[] assemblyBytes, string pluginName)
{
// 1. 隔离加载程序集
var context = new PluginLoadContext(pluginName);
var assembly = context.LoadFromStream(new MemoryStream(assemblyBytes));
// 2. 查找插件入口类型
var pluginType = assembly.GetTypes()
.FirstOrDefault(t => typeof(IGatewayPlugin).IsAssignableFrom(t));
if (pluginType == null)
{
context.Unload();
throw new InvalidOperationException("No IGatewayPlugin implementation found");
}
// 3. 创建实例
var plugin = (IGatewayPlugin)Activator.CreateInstance(pluginType)!;
await plugin.OnLoadAsync();
// 4. 保存实例
var instance = new PluginInstance
{
Name = pluginName,
Assembly = assembly,
Context = context,
Plugin = plugin,
LoadedAt = DateTime.UtcNow
};
_plugins[pluginName] = instance;
return instance;
}
public async Task UnloadPluginAsync(string pluginName)
{
if (!_plugins.TryGetValue(pluginName, out var instance))
return;
await instance.Plugin.OnUnloadAsync();
instance.Context.Unload();
_plugins.Remove(pluginName);
}
}
public class PluginLoadContext : AssemblyLoadContext
{
public PluginLoadContext(string name) : base(name, isCollectible: true) { }
protected override Assembly? Load(AssemblyName assemblyName)
{
return null;
}
}
```
---
### 4.5 插件编译服务
```csharp
public class RoslynPluginCompiler
{
private readonly IEnumerable<MetadataReference> _defaultReferences;
public RoslynPluginCompiler()
{
_defaultReferences = GetDefaultReferences();
}
public CompileResult Compile(string sourceCode, string pluginName, IEnumerable<string> extraAssemblies)
{
var syntaxTree = CSharpSyntaxTree.ParseText(sourceCode);
var references = _defaultReferences.Concat(
extraAssemblies.Select(a => MetadataReference.CreateFromFile(a))
);
var compilation = CSharpCompilation.Create(
assemblyName: $"Plugin_{pluginName}_{Guid.NewGuid():N}",
syntaxTrees: new[] { syntaxTree },
references: references,
options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)
.WithAllowUnsafe(false)
.WithOptimizationLevel(OptimizationLevel.Release));
using var ms = new MemoryStream();
var emitResult = compilation.Emit(ms);
if (!emitResult.Success)
{
return CompileResult.Fail(emitResult.Diagnostics);
}
ms.Seek(0, SeekOrigin.Begin);
return CompileResult.Success(ms.ToArray());
}
private IEnumerable<MetadataReference> GetDefaultReferences()
{
return AppDomain.CurrentDomain.GetAssemblies()
.Where(a => !a.IsDynamic && !string.IsNullOrEmpty(a.Location))
.Select(a => MetadataReference.CreateFromFile(a.Location));
}
}
```
---
## 五、数据库模型
```csharp
public class PluginPackage
{
public Guid Id { get; set; }
public string Name { get; set; } = string.Empty;
public string Version { get; set; } = "1.0.0";
public string? Description { get; set; }
public string StorageKey { get; set; } = string.Empty;
public PluginStatus Status { get; set; }
public DateTime UploadedAt { get; set; }
public DateTime? LoadedAt { get; set; }
}
public class PluginPipeline
{
public Guid Id { get; set; }
public Guid PluginId { get; set; }
public PipelineStage Stage { get; set; }
public int Order { get; set; }
public bool IsEnabled { get; set; }
}
public enum PluginStatus
{
Pending = 0,
Validated = 1,
Loaded = 2,
Failed = 3,
Disabled = 4
}
```
---
## 六、实施计划
| 阶段 | 任务 | 优先级 |
|------|------|--------|
| Phase 1 | 集成 YARP 到现有项目 | 高 |
| Phase 2 | 插件基础接口定义 | 高 |
| Phase 3 | 插件编译 + 加载框架 | 高 |
| Phase 4 | 嵌入式 Razor UI | 中 |
| Phase 5 | Monaco Editor 集成 | 中 |
| Phase 6 | ZIP 上传验证功能 | 中 |
| Phase 7 | 测试与优化 | 低 |
---
## 七、NuGet 依赖
```xml
<!-- 核心编译 -->
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.13.0" />
<!-- Scripting API (可选) -->
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="4.13.0" />
<!-- 依赖解析 -->
<PackageReference Include="System.Runtime.Loader" Version="4.3.0" />
```
---
## 八、总结
本方案实现了:
1. **Web UI 管理**:类 SwaggerUI 风格的可视化界面
2. **动态编译**Roslyn 在线编译 C# 代码
3. **插件加载**:独立 AssemblyLoadContext支持热卸载
4. **灵活扩展**:支持简单场景(使用已有程序集)和复杂场景(上传 ZIP
5. **流程控制**:插件可分配到 5 个不同阶段执行
---
*文档版本: 1.0*
*最后更新: 2026-03-01*

View File

@ -1,75 +0,0 @@
# Phase 1: 实现 Gateway 配置管理及事件推送 - Context
**收集日期:** 2026-03-02
**状态:** Ready for planning
**来源:** Manual planning (gsd-tools not available)
**更新:** 2026-03-03 - 添加数据源决策
<domain>
## Phase Boundary
实现 Console 管理 Gateway 配置的完整能力,包括:
- Gateway 配置的 CRUD 操作(已大部实现)
- 配置变更事件推送(待实现)
- 下游 Gateway 监听配置变更并重载
**现有能力:**
- GatewayController: API 端点已实现
- GatewayService: 业务逻辑已实现
- DTOs: 数据传输对象已定义
**待实现:**
- ReloadGatewayAsync() 广播机制
- 配置变更时自动触发广播
</domain>
<decisions>
## Implementation Decisions
### 技术选型
- **广播机制**: PostgreSQL NOTIFY/LISTEN轻量方案无需额外依赖
- **备选方案**: Redis pub/sub如需多实例通信
### 数据源
- **通知服务数据库连接**: 从 EF Core DbContext 获取,而非从配置文件读取
- **实现方式**: 注入 ConsoleDbContext使用 `DbContext.Database.GetConnectionString()`
### 功能决策
- **自动广播**: 配置变更(创建/更新/删除)时自动触发广播
- **手动广播**: 提供 /api/console/gateway/reload 手动触发端点
### Claude's Discretion
- 具体的 NOTIFY 通道名称格式
- 事件 payload 结构设计
- 是否需要事件类型区分service/route/instance
</decisions>
<specifics>
## Specific Ideas
**关键文件:**
- src/Services/GatewayService.cs - ReloadGatewayAsync() 空实现需填充
- src/Controllers/GatewayController.cs - POST /reload 端点
- src/Services/ConfigNotificationService.cs - 需修改为使用 DbContext 获取连接字符串
**依赖:**
- Npgsql - PostgreSQL 通知(已通过 EF Core 引用)
- Redis可选- 如选择 Redis pub/sub
**参考实现:**
- 网关已有 PgSqlConfigChangeListener 使用 NOTIFY/LISTEN可复用
</specifics>
<deferred>
## Deferred Ideas
- K8s 服务健康检查(后续 Phase
- Redis pub/sub如果 PostgreSQL NOTIFY 方案不够用再考虑)
</deferred>
---
*Phase: 01-gateway-config-management*
*Context gathered: 2026-03-02, updated 2026-03-03*

View File

@ -1,146 +0,0 @@
---
phase: 01-gateway-config-management
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
- src/Services/GatewayService.cs
- src/Services/ConfigNotificationService.cs
- src/Data/ConsoleDbContext.cs
autonomous: true
requirements: []
user_setup: []
must_haves:
truths:
- "配置变更后下游 Gateway 能收到通知"
- "手动触发 /reload 端点能广播配置变更"
- "自动触发:服务/路由/实例变更时自动广播"
artifacts:
- path: "src/Services/ConfigNotificationService.cs"
provides: "配置变更通知服务"
contains: "INotificationService"
- path: "src/Services/GatewayService.cs"
provides: "触发通知逻辑"
contains: "ReloadGatewayAsync"
key_links:
- from: "GatewayService"
to: "ConfigNotificationService"
via: "依赖注入"
pattern: "INotificationService"
---
<objective>
实现配置变更广播机制,使下游 Gateway 能够监听到配置变更。
</objective>
<context>
@.planning/ROADMAP.md
@.planning/PROJECT.md
@.planning/STATE.md
@.planning/phases/01-gateway-config-management/01-CONTEXT.md
@src/Services/GatewayService.cs
@src/Controllers/GatewayController.cs
</context>
<tasks>
<task type="auto">
<name>Task 1: 修改 ConfigNotificationService 使用 DbContext 获取连接字符串</name>
<files>src/Services/ConfigNotificationService.cs</files>
<action>
修改现有的 PgSqlNotificationService 实现:
1. 修改构造函数:
- 注入 ConsoleDbContext而非使用 IConfiguration
- 使用 DbContext.Database.GetConnectionString() 获取连接字符串
2. 移除:
- IConfiguration 依赖
- _configuration.GetConnectionString("DefaultConnection")
3. 示例代码:
```csharp
public PgSqlNotificationService(
ConsoleDbContext dbContext,
ILogger<PgSqlNotificationService> logger)
{
_connectionString = dbContext.Database.GetConnectionString()
?? throw new InvalidOperationException("DefaultConnection not configured");
_logger = logger;
}
```
4. 在 Program.cs 中注册服务时传入 DbContext
```csharp
services.AddScoped<INotificationService>(sp =>
new PgSqlNotificationService(
sp.GetRequiredService<ConsoleDbContext>(),
sp.GetRequiredService<ILogger<PgSqlNotificationService>>()));
```
</action>
<verify>
<automated>dotnet build --no-restore 2>&1 | head -20</automated>
</verify>
<done>PgSqlNotificationService 已修改为使用 DbContext 获取连接字符串</done>
</task>
<task type="auto">
<name>Task 2: 修改 GatewayService 集成通知服务</name>
<files>src/Services/GatewayService.cs</files>
<action>
修改 GatewayService 以集成通知服务:
1. 添加 INotificationService 依赖注入到 GatewayService 构造函数
2. 修改 ReloadGatewayAsync() 实现:
- 调用 _notificationService.PublishAsync("gateway_config_changed", JsonSerialize(reloadEvent))
- 日志记录广播成功
3. 在以下 CRUD 操作中添加自动广播(创建/更新/删除后):
- RegisterServiceAsync - 服务注册
- UnregisterServiceAsync - 服务注销
- CreateRouteAsync - 路由创建
- AddInstanceAsync - 实例添加
- RemoveInstanceAsync - 实例删除
- UpdateInstanceWeightAsync - 权重更新
4. 事件 Payload 格式:
```json
{
"eventType": "service|route|instance",
"action": "create|update|delete|reload",
"timestamp": "2026-03-02T12:00:00Z",
"details": { ... }
}
```
</action>
<verify>
<automated>dotnet build --no-restore 2>&1 | head -30</automated>
</verify>
<done>GatewayService 集成通知服务,所有配置变更操作自动触发广播</done>
</task>
</tasks>
<verification>
整体验证:
1. dotnet build 编译通过
2. 手动调用 POST /api/console/gateway/reload 返回成功
3. PostgreSQL 数据库能收到 NOTIFY 消息
</verification>
<success_criteria>
- [x] ConfigNotificationService 改为使用 DbContext 获取连接字符串
- [ ] INotificationService 接口定义完成
- [ ] PgSqlNotificationService 实现完成
- [ ] GatewayService 集成通知服务
- [ ] ReloadGatewayAsync 触发广播
- [ ] CRUD 操作自动触发广播
- [ ] 编译通过
</success_criteria>
<output>
完成后创建 .planning/phases/01-gateway-config-management/01-SUMMARY.md
</output>

View File

@ -1,83 +0,0 @@
# Phase 1: 实现 Gateway 配置管理及事件推送 - 执行摘要
**完成日期:** 2026-03-02
**状态:** ✓ Complete
## 执行结果
| Plan | 任务 | 状态 |
|------|------|------|
| 01 | Task 1: 创建配置通知服务 | ✓ |
| 01 | Task 2: 修改 GatewayService 集成通知服务 | ✓ |
## 实现的功能
### 1. 配置通知服务 (ConfigNotificationService.cs)
**创建/修改的文件:**
- `src/Services/ConfigNotificationService.cs`
**包含:**
- `INotificationService` 接口 - 通知服务抽象
- `PgSqlNotificationService` 实现 - 使用 PostgreSQL NOTIFY 机制
- `ConfigChangeEvent` - 配置变更事件数据模型
- 通知通道: `gateway_config_changed`
**实现细节:**
- 使用 `DbContextOptions<ConsoleDbContext>` 获取连接字符串(而非直接从配置文件读取)
- 通过反射从 EF Core Npgsql 扩展中提取连接字符串
**创建的文件:**
- `src/Services/ConfigNotificationService.cs`
**包含:**
- `INotificationService` 接口 - 通知服务抽象
- `PgSqlNotificationService` 实现 - 使用 PostgreSQL NOTIFY 机制
- `ConfigChangeEvent` - 配置变更事件数据模型
- 通知通道: `gateway_config_changed`
**事件格式:**
```json
{
"eventType": "service|route|instance|gateway",
"action": "create|update|delete|reload",
"timestamp": "2026-03-02T12:00:00Z",
"details": { ... }
}
```
### 2. GatewayService 集成
**修改的文件:**
- `src/Services/GatewayService.cs` - 添加 INotificationService 依赖
- `src/Program.cs` - 注册 NotificationService
**自动广播触发点:**
- `RegisterServiceAsync` - 服务注册时
- `UnregisterServiceAsync` - 服务注销时
- `CreateRouteAsync` - 路由创建时
- `AddInstanceAsync` - 实例添加时
- `RemoveInstanceAsync` - 实例删除时
- `UpdateInstanceWeightAsync` - 权重更新时
- `ReloadGatewayAsync` - 手动触发重载时
## 验证
- [x] dotnet build 编译通过
- [x] INotificationService 接口定义完成
- [x] PgSqlNotificationService 实现完成
- [x] GatewayService 集成通知服务
- [x] ReloadGatewayAsync 触发广播
- [x] CRUD 操作自动触发广播
## 下游使用
下游 Gateway (yarpgateway) 需要实现:
1. 监听 `gateway_config_changed` 通道
2. 收到通知后重新加载配置
---
*Phase: 01-gateway-config-management*
*Plan: 01*
*Executed: 2026-03-02*

View File

@ -1,27 +0,0 @@
# Phase 2: 实现 Gateway 插件系统
- **目标**: 实现 YARP 网关的插件系统,包括 Web UI 管理界面和动态编译加载功能
- **状态**: Not planned yet
---
## Goal
实现 YARP 网关的插件系统规划与实现,包括:
- Web UI 管理界面(路由管理、集群管理、插件管理)
- 在线 C# 代码编辑Monaco Editor
- 动态编译加载Roslyn
- 插件生命周期管理
## Depends on
- Phase 1: 实现 Gateway 配置管理及事件推送
## Plans
- [ ] 02-PLAN.md — 实施计划
---
*相关文档:.planning/docs/gateway-plugin-system.md*

View File

@ -1,74 +0,0 @@
# Phase 3: 网关配置变更广播机制 - Context
**Gathered:** 2026-03-03
**Status:** Ready for planning
<domain>
## Phase Boundary
分析现有的网关配置广播机制,梳理路由→服务→下游的完整链路,确定配置变更时的广播策略。
</domain>
<decisions>
## Implementation Decisions
### 广播触发策略
- **仅手动触发**:所有 CRUD 操作(路由、集群、实例、权重)不自动广播
- 下游需要刷新时,手动调用 POST /api/console/gateway/reload
- 事件只通知"需要刷新",不包含具体变更内容
- 下游收到通知后,自行查询数据库刷新配置
### 广播事件格式
- 通道:`gateway_config_changed`
- 事件内容:只包含 action: "reload",不含具体变更详情
- 下游逻辑:收到通知 → 查询数据库 → 刷新内存缓存
### 需分析的现有代码
- ConfigNotificationService.cs - 已实现的 NOTIFY 机制
- GatewayService.cs - 需集成通知服务
- GatewayController.cs - /reload 接口
### Claude's Discretion
- 自动触发 vs 手动触发的具体实现方式
- 广播失败时的错误处理策略
- 日志记录细节
</decisions>
<code_context>
## Existing Code Insights
### Reusable Assets
- ConfigNotificationService.cs: PostgreSQL NOTIFY 机制已实现
- INotificationService 接口: 可直接复用
### Established Patterns
- 使用 PgSqlNotificationService 发布通知
- 通道名称: `gateway_config_changed`
### Integration Points
- GatewayService 需注入 INotificationService
- ReloadGatewayAsync 需调用通知服务
</code_context>
<specifics>
## Specific Ideas
- 事件 payload 尽量精简,只传递 "reload" action
- 下游网关监听同一数据库连接,收到 NOTIFY 后刷新
</specifics>
<deferred>
## Deferred Ideas
- 自动触发广播(未来可选优化)
</deferred>
---
*Phase: 03-gateway-config-broadcast*
*Context gathered: 2026-03-03*

View File

@ -1,112 +0,0 @@
---
phase: 03-gateway-config-broadcast
plan: 01
type: execute
wave: 1
depends_on: []
files_modified: []
autonomous: true
requirements: []
user_setup: []
must_haves:
truths:
- "现有广播机制已文档化"
- "路由 -> 服务 -> 下游流程已理解"
- "配置变更事件已验证可用"
artifacts:
- path: ".planning/phases/03-gateway-config-broadcast/03-SUMMARY.md"
provides: "阶段执行摘要"
key_links: []
---
<objective>
分析和文档化现有的网关配置广播机制。理解从路由配置到下游服务的完整链路,并验证配置变更事件广播是否正常工作。
</objective>
<context>
@.planning/phases/01-gateway-config-management/01-SUMMARY.md
@.planning/phases/01-gateway-config-management/01-PLAN.md
## 现有实现(来自 Phase 1
广播机制使用 PostgreSQL NOTIFY
- **通道:** `gateway_config_changed`
- **事件类型:** service, route, instance, gateway
- **操作:** create, update, delete, reload
- **服务:** ConfigNotificationService.cs
- **集成:** GatewayService.cs 在所有 CRUD 操作时触发广播
</context>
<tasks>
<task type="auto">
<name>任务 1: 分析现有广播实现</name>
<files>src/Services/ConfigNotificationService.cs, src/Services/GatewayService.cs</files>
<action>
分析现有实现以了解:
1. ConfigNotificationService 如何工作PostgreSQL NOTIFY
2. GatewayService 如何在 CRUD 操作时触发广播
3. 发送的事件类型和载荷是什么
阅读源代码并记录发现。
</action>
<verify>
<automated>文件存在且包含通知逻辑</automated>
</verify>
<done>实现分析完成,发现已记录</done>
</task>
<task type="auto">
<name>任务 2: 绘制路由 -> 服务 -> 下游流程</name>
<files></files>
<action>
文档化完整配置链路:
1. 路由如何在 Console 中定义
2. 路由如何映射到服务
3. 下游 Gateway 如何发现服务
4. 配置变更时,广播如何到达下游
参考 src/Models/、src/Services/、src/Controllers/ 中的现有代码
</action>
<verify>
<automated>流程文档已创建</automated>
</verify>
<done>配置链路已文档化</done>
</task>
<task type="auto">
<name>任务 3: 验证广播端到端工作</name>
<files></files>
<action>
验证广播机制:
1. 检查 PostgreSQL LISTEN/NOTIFY 是否正确配置
2. 验证 ReloadGatewayAsync 发送正确事件
3. 确认所有 CRUD 操作(服务/路由/实例)都触发广播
4. 如可能,测试端到端流程
</action>
<verify>
<automated>编译成功API 端点可用</automated>
</verify>
<done>广播验证完成</done>
</task>
</tasks>
<verification>
1. 阅读并分析 ConfigNotificationService.cs
2. 阅读并分析 GatewayService.cs
3. 文档化路由 -> 服务 -> 下游流程
4. 验证编译通过
</verification>
<success_criteria>
- [x] 现有广播实现已分析
- [x] 配置链路已文档化
- [x] 广播事件已验证
- [x] 摘要已创建
</success_criteria>
<output>
完成后创建 `.planning/phases/03-gateway-config-broadcast/03-SUMMARY.md`
</output>

View File

@ -1,128 +0,0 @@
---
phase: 04-gateway-entity-update
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
- src/Services/GatewayService.cs
- src/Program.cs
autonomous: true
requirements: []
user_setup: []
must_haves:
truths:
- "编译错误已修复"
- "GatewayService 使用新的 IClusterStore 接口"
- "实例操作改为通过 Cluster.Destinations 管理"
artifacts:
- path: "src/Services/GatewayService.cs"
provides: "GatewayService 使用 IClusterStore"
- path: "src/Program.cs"
provides: "依赖注入更新"
key_links:
- from: "GatewayService"
to: "IClusterStore"
via: "依赖注入"
---
<objective>
适配 Platform 1.0.12 中的 Gateway 实体重构,修复编译错误,更新 Console 代码以使用新的 GwCluster/GwDestination/GwTenantRoute 模型。
</objective>
<context>
@.planning/docs/gateway-entity-changes-1.0.12.md
## 编译错误
当前编译错误:
```
error CS0246: IInstanceStore 找不到
```
## 变更摘要
1. **IInstanceStore 已移除** - 实例现在是 GwCluster 的内嵌对象
2. **IClusterStore 是新接口** - 包含 Destination 管理方法
3. **数据模型变化**:
- GatewayInstance → GwDestination内嵌值对象
- GatewayCluster → GwCluster聚合根包含 Destinations
- 路由通过 ClusterId 关联到集群
</context>
<tasks>
<task type="auto">
<name>任务 1: 更新 Program.cs 依赖注入</name>
<files>src/Program.cs</files>
<action>
1. 移除 IInstanceStore 的注入(如果有)
2. 添加 IClusterStore 的注入:
```csharp
builder.Services.AddScoped<IClusterStore, ClusterStore<PlatformDbContext>>();
```
3. 确保使用正确的 PlatformDbContext
</action>
<verify>
<automated>dotnet build --no-restore 2>&1 | head -30</automated>
</verify>
<done>Program.cs 依赖注入已更新</done>
</task>
<task type="auto">
<name>任务 2: 更新 GatewayService 使用 IClusterStore</name>
<files>src/Services/GatewayService.cs</files>
<action>
1. 移除 IInstanceStore 依赖
2. 添加 IClusterStore 依赖注入
3. 更新实例相关方法:
- GetInstancesAsync → 从 Cluster.Destinations 获取
- AddInstanceAsync → 使用 IClusterStore.AddDestinationAsync
- RemoveInstanceAsync → 使用 IClusterStore.RemoveDestinationAsync
- UpdateInstanceWeightAsync → 使用 IClusterStore.UpdateDestinationAsync
4. 更新数据模型映射:
- GatewayInstanceDto → 从 GwDestination 映射
- GatewayClusterDto → 从 GwCluster 映射
</action>
<verify>
<automated>dotnet build --no-restore 2>&1 | head -30</automated>
</verify>
<done>GatewayService 已更新为使用 IClusterStore</done>
</task>
<task type="auto">
<name>任务 3: 验证编译通过</name>
<files></files>
<action>
运行完整编译验证:
```bash
dotnet build src/Fengling.Console.csproj
```
确保没有编译错误。
</action>
<verify>
<automated>dotnet build src/Fengling.Console.csproj 2>&1 | tail -10</automated>
</verify>
<done>编译通过,无错误</done>
</task>
</tasks>
<verification>
1. dotnet build 编译通过
2. GatewayService 使用 IClusterStore
3. 实例操作通过 Cluster.Destinations 管理
</verification>
<success_criteria>
- [x] IInstanceStore 依赖已移除
- [x] IClusterStore 已集成
- [x] 编译错误已修复
- [x] GatewayService 功能正常
</success_criteria>
<output>
完成后创建 `.planning/phases/04-gateway-entity-update/04-SUMMARY.md`
</output>

View File

@ -1,78 +0,0 @@
# Phase 4 总结:适配 Platform 1.0.12 Gateway 实体变更
## 概述
本次 Phase 4 成功完成了 Fengling Console 对 Platform 1.0.12 Gateway 实体变更的适配工作。
## 主要变更
### 1. Program.cs 依赖注入更新
**变更内容:**
- 移除了 `IInstanceStore``InstanceStore` 的注册
- 保留了 `IClusterStore``ClusterStore` 的注册
**变更原因:**
Platform 1.0.12 移除了 `IInstanceStore` 接口,实例(Destination)现在是 `GwCluster` 的内嵌对象。
### 2. ConsoleDbContext 实体配置清理
**变更内容:**
- 移除了 `GwTenant` 实体配置(原平台中已移除)
- 移除了 `GwServiceInstance` 实体配置(已重构为 GwDestination
### 3. GatewayService 实体属性适配
**变更内容:**
| 旧属性 | 新属性 | 说明 |
|--------|--------|------|
| `GwTenantRoute.PathPattern` (string) | `GwTenantRoute.Match.Path` ( GwRouteMatch.Path ) | 路由匹配配置从简单字符串升级为复杂对象 |
| `Status = RouteStatus.Active` (enum) | `Status = (int)RouteStatus.Active` (int) | Status 字段为 int 类型,需要显式转换枚举 |
**具体代码变更:**
```csharp
// 旧代码
new GwTenantRoute
{
PathPattern = pathPattern,
Status = RouteStatus.Active,
}
// 新代码
new GwTenantRoute
{
Match = new GwRouteMatch { Path = pathPattern },
Status = (int)RouteStatus.Active,
}
```
```csharp
// 旧代码读取
r.PathPattern
r.Status
// 新代码读取
r.Match.Path
r.Status (已是 int)
```
## 编译结果
✅ 编译成功0 个错误3 个警告(警告为预存在的代码质量问题,与本次变更无关)
## 验证
- [x] `dotnet build` 通过
- [x] 无新增编译错误
## 相关文档
- 实体变更详情:`.planning/docs/gateway-entity-changes-1.0.12.md`
## 下一步
可以考虑的改进:
1. 修复 TenantService.cs 中的警告roleManager 参数未使用)
2. 完善 GatewayService 中的空值处理Match 可能为 null

87
GATEWAY_ADMIN_README.md Normal file
View File

@ -0,0 +1,87 @@
# Gateway Admin - 独立前端项目
## 项目结构
```
fengling-console/
├── src/
│ ├── Controllers/
│ │ └── GatewayAdminController.cs # Gateway Admin API
│ ├── wwwroot/
│ │ └── gateway-admin/ # Vue3 构建输出 (gitignore)
│ └── ...
├── gateway-admin-client/ # Vue3 + shadcn-vue 独立项目
│ ├── src/
│ │ ├── components/ # UI 组件
│ │ ├── views/ # 页面
│ │ │ ├── Dashboard.vue # 仪表盘
│ │ │ ├── Routes.vue # 路由管理
│ │ │ ├── Clusters.vue # 集群管理
│ │ │ └── Discovery.vue # 服务发现
│ │ ├── api/
│ │ │ └── gateway.ts # API 客户端
│ │ ├── App.vue # 根组件
│ │ └── main.ts # 入口
│ ├── package.json
│ ├── vite.config.ts # 构建输出到 ../src/wwwroot/gateway-admin/
│ └── ...
└── ...
```
## 与 Vben Admin 的关系
- **Vben Admin** (`fengling-console-web/`): 管理非网关部分(用户、角色、租户等)
- **Gateway Admin** (`gateway-admin-client/`): 独立的网关管理界面
**集成方式**: Vben Admin 通过 `<iframe src="/gateway-admin/">` 嵌入网关管理页面
## 开发流程
### 1. 安装依赖
```bash
cd fengling-console/gateway-admin-client
npm install
```
### 2. 开发模式
```bash
npm run dev
# 访问 http://localhost:5173
# API 代理到 http://localhost:5000
```
### 3. 构建
```bash
npm run build
# 输出到 fengling-console/src/wwwroot/gateway-admin/
```
### 4. 运行 Console
```bash
cd fengling-console/src
dotnet run
# 访问 http://localhost:5000/gateway-admin/
```
## API 端点
| 方法 | 路径 | 说明 |
|------|------|------|
| GET | /api/gateway/dashboard/summary | Dashboard 汇总 |
| GET | /api/gateway/routes | 路由列表 |
| GET | /api/gateway/clusters | 集群列表 |
| GET | /api/gateway/discovery/pending | 待配置服务 |
| POST | /api/gateway/discovery/approve | 确认服务配置 |
| POST | /api/gateway/config/reload | 热重载配置 |
## 技术栈
- Vue 3 + TypeScript
- Vue Router (SPA)
- Tailwind CSS
- 自定义组件 (参考 shadcn-vue 风格)
- 独立构建,不依赖 Vben Admin

183
docs/CLEANDDD-WORKFLOW.md Normal file
View File

@ -0,0 +1,183 @@
# CleanDDD 开发工作流
本文档描述 Fengling 微服务项目的标准开发流程,基于 Clean Architecture 和 Domain-Driven Design 原则。
## 开发技能栈
| 技能 | 用途 | 位置 |
|------|------|------|
| `cleanddd-requirements-analysis` | 需求澄清与拆解 | `~/.agents/skills/` |
| `cleanddd-modeling` | 领域模型设计(聚合/命令/事件/查询) | `~/.agents/skills/` |
| `cleanddd-dotnet-coding` | 代码实现(实体/命令/端点/事件) | `~/.agents/skills/` |
| `cleanddd-dotnet-init` | 新项目初始化 | `~/.agents/skills/` |
## 标准开发流程
### 阶段 1需求分析
```
业务需求描述
cleanddd-requirements-analysis
结构化需求文档(标注业务实体、干系人)
```
### 阶段 2领域建模
```
结构化需求
cleanddd-modeling
领域蓝图:
- 聚合根定义含强类型ID
- 领域事件列表
- 命令与查询分离
- API 端点设计
- 仓储接口
```
### 阶段 3代码实现
```
领域蓝图
cleanddd-dotnet-coding
项目结构:
├── Domain/
│ ├── AggregatesModel/{Aggregate}/
│ │ ├── {Aggregate}.cs # 聚合根
│ │ ├── {Aggregate}Id.cs # 强类型ID
│ │ └── Events/ # 领域事件
│ └── DomainEvents/
├── Infrastructure/
│ ├── EntityConfigurations/ # EF 配置
│ └── Repositories/ # 仓储实现
└── Web/
├── Application/
│ ├── Commands/ # CQRS 命令
│ ├── Queries/ # CQRS 查询
│ └── DomainEventHandlers/ # 领域事件处理器
└── Endpoints/ # API 端点
```
## 项目结构规范
### CleanDDD 标准结构
```
{ServiceName}/
├── src/
│ ├── {ServiceName}.Domain/ # 领域层(无依赖)
│ │ ├── AggregatesModel/
│ │ │ └── {Aggregate}/
│ │ │ ├── {Aggregate}.cs
│ │ │ ├── {Aggregate}Id.cs
│ │ │ └── Events/
│ │ └── DomainEvents/
│ │
│ ├── {ServiceName}.Infrastructure/ # 基础设施层
│ │ ├── EntityConfigurations/
│ │ ├── Repositories/
│ │ └── {ServiceName}DbContext.cs
│ │
│ └── {ServiceName}.Web/ # 表现层
│ ├── Application/
│ │ ├── Commands/
│ │ │ └── {Feature}/
│ │ │ ├── {Command}.cs
│ │ │ └── {Command}Handler.cs
│ │ ├── Queries/
│ │ └── DomainEventHandlers/
│ └── Endpoints/
│ └── {Feature}Endpoints.cs
└── tests/ # 测试项目
├── {ServiceName}.Domain.UnitTests/
├── {ServiceName}.Infrastructure.UnitTests/
└── {ServiceName}.Web.UnitTests/
```
### 命名约定
| 类型 | 命名格式 | 示例 |
|------|----------|------|
| 聚合根 | `{名词}` | `Campaign`, `Order` |
| 强类型ID | `{名词}Id` | `CampaignId`, `OrderId` |
| 领域事件 | `{名词}{动词}edEvent` | `CampaignCreatedEvent` |
| 命令 | `{动词}{名词}Command` | `CreateCampaignCommand` |
| 查询 | `Get{名词}Query` | `GetCampaignQuery` |
| 端点 | `{名词}Endpoints` | `CampaignEndpoints` |
| 仓储 | `I{名词}Repository` | `ICampaignRepository` |
## 代码规范
### 必须遵守
1. **强类型 ID**:所有实体使用 `IInt64StronglyTypedId``IGuidStronglyTypedId`
2. **聚合根**:继承 `Entity<TId>`,实现 `IAggregateRoot`
3. **领域事件**:在状态变更时发布 `this.AddDomainEvent()`
4. **仓储**:使用异步方法(`GetAsync`, `AddAsync`),禁止在 Handler 中调用 `SaveChanges`
5. **命令验证**:每个命令必须有对应的 `AbstractValidator<T>`
6. **端点配置**:使用属性配置 `[HttpPost]`, `[Tags]`,不使用 `Configure()` 方法
### 禁止事项
- ❌ 手动赋值实体 ID使用 EF 值生成器)
- ❌ 在领域层引用基础设施层
- ❌ 在非聚合/实体中发布领域事件
- ❌ 仓储同步方法
- ❌ 命令 Handler 中调用 `SaveChanges`
## 网关架构说明
### 配置流向
```
K8s Service (app-router-* label)
Console (K8sServiceWatchService)
PendingConfigCache (内存缓存)
PendingConfigController (用户确认)
PostgreSQL (GwRoutes, ServiceInstances)
PostgreSQL NOTIFY (gateway_config_changed)
Gateway (PgSqlConfigChangeListener)
YARP DynamicProxyConfigProvider
TenantRoutingTransform (按 TenantCode 路由)
后端服务
```
### 多租户路由
| 路由类型 | URL 模式 | 说明 |
|----------|----------|------|
| 全局路由 | `/{ServicePrefix}/{Version}/{Resource}` | 所有租户共享 |
| 租户路由 | `/tenant/{tenantCode}/{ServicePrefix}/{Version}/{Resource}` | 租户专属目标 |
### 关键实体
- **GwRoute**全局路由配置Path → Cluster
- **GwCluster**:服务集群(含 Destinations 列表)
- **GwDestination**:目标端点(含 TenantCode 用于租户路由)
## 故障排除
| 问题 | 原因 | 解决 |
|------|------|------|
| `bigint = character varying` | 表结构过时 | 见 `DATABASE_SCHEMA_FIX.md` |
| `libgssapi_krb5.so.2` 警告 | Alpine 缺少 Kerberos 库 | 可忽略,或设 `Logging__LogLevel__Npgsql=Error` |
| Watch 连接断开不恢复 | HTTP/2 连接超时 | 已修复onClosed 回调触发重连 |
| Config 未生效 | NOTIFY 未触发 | 检查 `LISTEN config_changes;` |
## 相关文档
- `DATABASE_SCHEMA_FIX.md` - 数据库表结构修复步骤
- `GATEWAY_ADMIN_README.md` - Gateway Admin UI 开发指南
- `../../AGENTS.md` - 项目级架构概览