docs: reorganize documentation, remove GSD workflow files
Some checks failed
Build and Push Docker / build (push) Failing after 2m33s
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:
parent
2f8c35ef3e
commit
fbc54c9ac5
@ -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 初始化后*
|
||||
@ -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 — 实施计划
|
||||
@ -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*
|
||||
@ -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*
|
||||
@ -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*
|
||||
@ -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*
|
||||
@ -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)
|
||||
- ORM:Entity 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*
|
||||
@ -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.103,rollForward: 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*
|
||||
@ -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*
|
||||
@ -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*
|
||||
@ -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` - 需要更新依赖注入
|
||||
@ -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*
|
||||
@ -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*
|
||||
@ -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>
|
||||
@ -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*
|
||||
@ -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*
|
||||
@ -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*
|
||||
@ -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>
|
||||
@ -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>
|
||||
@ -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
87
GATEWAY_ADMIN_README.md
Normal 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
183
docs/CLEANDDD-WORKFLOW.md
Normal 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` - 项目级架构概览
|
||||
Loading…
Reference in New Issue
Block a user