diff --git a/Directory.Packages.props b/Directory.Packages.props index f081a5c..d662fd6 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -4,8 +4,8 @@ - - + + diff --git a/docs/DATABASE_SCHEMA_FIX.md b/docs/DATABASE_SCHEMA_FIX.md new file mode 100644 index 0000000..bd9e693 --- /dev/null +++ b/docs/DATABASE_SCHEMA_FIX.md @@ -0,0 +1,172 @@ +# Gateway 数据库表结构修复文档 + +## 问题描述 + +### 错误现象 +Gateway 服务启动时抛出异常: +``` +Npgsql.PostgresException (0x80004005): 42883: operator does not exist: bigint = character varying +``` + +### 根本原因 +1. `PlatformDbContext` 配置中 `GwCluster` 与 `GwDestination` 的关系外键类型不匹配 +2. 旧版 `GwCluster.Id` 是 `long` (bigint),新版改为 `string` (varchar) +3. 数据库表结构是旧的(bigint),但代码已更新为新的实体定义(string) + +### 影响范围 +- fengling-gateway 服务无法加载路由配置 +- YARP 代理功能无法正常工作 + +--- + +## 修复步骤 + +### 1. 更新 Platform 包版本 + +**文件**: `fengling-console/Directory.Packages.props` + +```xml + + + + + + + +``` + +### 2. 临时添加 EnsureDeletedAsync + +**文件**: `fengling-console/src/Program.cs` + +在初始化 Gateway 数据库前添加删除旧表逻辑: + +```csharp +// 2. 初始化 Gateway 数据库(创建 GwRoutes, GwClusters 等表) +var gatewayDb = scope.ServiceProvider.GetRequiredService(); +try +{ + app.Logger.LogInformation("Deleting old Gateway database schema..."); + await gatewayDb.Database.EnsureDeletedAsync(); // 临时:删除旧表结构 + app.Logger.LogInformation("Initializing Gateway database..."); + await gatewayDb.Database.EnsureCreatedAsync(); + app.Logger.LogInformation("Gateway database initialized successfully"); +} +``` + +### 3. 构建并部署 + +在 K3s 服务器 (192.168.100.120) 上执行: + +```bash +cd /root/fengling/Fengling.Refactory.Pack + +# 构建并推送新镜像 +cd fengling-console/src +docker build -f Dockerfile -t 192.168.100.120:8418/fengling/console:test . +docker push 192.168.100.120:8418/fengling/console:test + +# 重启 Console Pod(触发 EnsureDeleted + EnsureCreated) +kubectl rollout restart deployment/fengling-console -n fengling-test + +# 观察日志,确认数据库表重建成功 +kubectl logs -n fengling-test deployment/fengling-console -f +``` + +### 4. 验证修复 + +检查 Gateway 服务日志,确认路由加载成功: + +```bash +# 重启 Gateway 加载新表结构 +kubectl rollout restart deployment/fengling-gateway -n fengling-test + +# 验证路由加载 +kubectl logs -n fengling-test deployment/fengling-gateway | grep -i "loaded" +``` + +预期输出: +``` +Loaded X routes from database +Loaded X clusters from database +``` + +### 5. 清理临时代码 + +修复成功后,移除 `EnsureDeletedAsync()` 避免数据丢失风险: + +```bash +# 恢复 Program.cs(删除 EnsureDeletedAsync 行) +cd /root/fengling/Fengling.Refactory.Pack +git checkout fengling-console/src/Program.cs +# 或手动编辑删除 EnsureDeletedAsync 那一行 + +# 重新构建部署(不带删除逻辑) +cd fengling-console/src +docker build -f Dockerfile -t 192.168.100.120:8418/fengling/console:test . +docker push 192.168.100.120:8418/fengling/console:test +kubectl rollout restart deployment/fengling-console -n fengling-test +``` + +--- + +## 架构说明 + +### 数据库结构 + +``` +fengling_gateway 数据库 +├── GwRoutes (路由配置表) +│ ├── Id (string, PK) +│ ├── ServiceName (string) +│ ├── ClusterId (string, FK to GwClusters.ClusterId) +│ └── Match (jsonb) +│ +└── ServiceInstances (集群配置表) + ├── Id (string, PK) + ├── ClusterId (string, Unique) + ├── Name (string) + ├── Destinations (Owned Collection) + │ ├── DestinationId (string) + │ ├── Address (string) + │ ├── TenantCode (string) -- 用于多租户路由 + │ └── ... + └── ... +``` + +### 配置同步流程 + +``` +K8s Service (Label: app-router-*) + ↓ +Console (K8sServiceWatchService) + ↓ +PendingConfigCache → User Confirm + ↓ +PostgreSQL (GwRoutes, ServiceInstances) + ↓ +PostgreSQL NOTIFY + ↓ +Gateway (PgSqlConfigChangeListener) + ↓ +YARP Route Reload +``` + +--- + +## 注意事项 + +1. **数据备份**: `EnsureDeletedAsync()` 会删除所有数据,生产环境务必先备份 +2. **临时方案**: 此修复仅适用于测试环境,生产环境应使用 Migration +3. **多数据库**: Console 管理两个数据库: + - `fengling_console`: 身份认证、用户租户数据 + - `fengling_gateway`: 网关路由配置(由 Console 写入,Gateway 读取) + +--- + +## 相关文件 + +- `fengling-console/src/Program.cs` - 数据库初始化逻辑 +- `fengling-console/Directory.Packages.props` - Platform 包版本 +- `fengling-platform/Fengling.Platform.Infrastructure/PlatformDbContext.cs` - 实体配置 +- `fengling-platform/Fengling.Platform.Domain/AggregatesModel/GatewayAggregate/` - 实体定义 diff --git a/k8s/test/deployment.yaml b/k8s/test/deployment.yaml new file mode 100644 index 0000000..37ab995 --- /dev/null +++ b/k8s/test/deployment.yaml @@ -0,0 +1,135 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: fengling-console-config + namespace: fengling-test + labels: + app: fengling-console +data: + ASPNETCORE_ENVIRONMENT: "Production" + Kestrel__Endpoints__Http__Url: "http://0.0.0.0:8080" + Logging__LogLevel__Default: "Information" + Logging__LogLevel__Microsoft__AspNetCore: "Warning" + Logging__LogLevel__Npgsql: "Error" # 隐藏 GSSAPI 警告 + ServiceDiscovery__UseInClusterConfig: "true" + ServiceDiscovery__Namespace: "fengling-test" + ServiceDiscovery__LabelSelector: "app-router-name" +--- +apiVersion: v1 +kind: Secret +metadata: + name: fengling-console-secret + namespace: fengling-test +type: Opaque +stringData: + ConnectionStrings__DefaultConnection: "Host=81.68.223.70;Port=15432;Database=fengling_console;Username=movingsam;Password=sl52788542" + Jwt__Authority: "https://apigateway.shtao1.cn" +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: fengling-console + namespace: fengling-test + labels: + app: fengling-console + version: v2 +spec: + replicas: 1 + selector: + matchLabels: + app: fengling-console + template: + metadata: + labels: + app: fengling-console + version: v2 + spec: + serviceAccountName: fengling-console-sa + containers: + - name: console + image: 192.168.100.120:8418/fengling/fengling-console:test + imagePullPolicy: Always + ports: + - containerPort: 8080 + name: http + protocol: TCP + env: + - name: ASPNETCORE_ENVIRONMENT + value: "Production" + - name: ASPNETCORE_URLS + value: "http://+:8080" + envFrom: + - configMapRef: + name: fengling-console-config + - secretRef: + name: fengling-console-secret + livenessProbe: + httpGet: + path: /health + port: 8080 + initialDelaySeconds: 30 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 + readinessProbe: + httpGet: + path: /health + port: 8080 + initialDelaySeconds: 10 + periodSeconds: 5 + timeoutSeconds: 3 + failureThreshold: 3 + resources: + requests: + cpu: 200m + memory: 256Mi + limits: + cpu: 1000m + memory: 512Mi +--- +apiVersion: v1 +kind: Service +metadata: + name: fengling-console + namespace: fengling-test + labels: + app: fengling-console +spec: + type: ClusterIP + selector: + app: fengling-console + ports: + - port: 80 + targetPort: 8080 + name: http +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: fengling-console-sa + namespace: fengling-test +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: fengling-console-role +rules: +- apiGroups: [""] + resources: ["services"] + verbs: ["get", "list", "watch"] +- apiGroups: [""] + resources: ["namespaces"] + verbs: ["get", "list"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: fengling-console-binding +subjects: +- kind: ServiceAccount + name: fengling-console-sa + namespace: fengling-test +roleRef: + kind: ClusterRole + name: fengling-console-role + apiGroup: rbac.authorization.k8s.io diff --git a/src/Program.cs b/src/Program.cs index bafebfd..9ae74a6 100644 --- a/src/Program.cs +++ b/src/Program.cs @@ -30,6 +30,18 @@ builder.Services.AddDbContext(options => options.EnableDetailedErrors(); }); +// Gateway 数据库上下文(用于管理网关配置表) +builder.Services.AddDbContext(options => +{ + options.UseNpgsql(builder.Configuration.GetConnectionString("GatewayConnection") ?? + builder.Configuration.GetConnectionString("DefaultConnection")?.Replace("fengling_console", "fengling_gateway")); + if (builder.Environment.IsDevelopment()) + { + options.EnableSensitiveDataLogging(); + } + options.EnableDetailedErrors(); +}); + // Use Platform's identity builder.Services.AddIdentity() .AddEntityFrameworkStores() @@ -114,6 +126,41 @@ app.UseCors("AllowAll"); app.UseAuthentication(); app.UseAuthorization(); +// 执行数据库初始化(Console 负责数据库管理,测试环境使用 EnsureCreated) +using (var scope = app.Services.CreateScope()) +{ + // 1. 初始化 Console 数据库 + var consoleDb = scope.ServiceProvider.GetRequiredService(); + try + { + app.Logger.LogInformation("Initializing Console database..."); + await consoleDb.Database.EnsureCreatedAsync(); + app.Logger.LogInformation("Console database initialized successfully"); + } + catch (Exception ex) + { + app.Logger.LogError(ex, "Failed to initialize Console database"); + } + + // 2. 初始化 Gateway 数据库(创建 GwRoutes, GwClusters 等表) + var gatewayDb = scope.ServiceProvider.GetRequiredService(); + try + { + app.Logger.LogInformation("Deleting old Gateway database schema..."); + await gatewayDb.Database.EnsureDeletedAsync(); // 临时:删除旧表结构 + app.Logger.LogInformation("Initializing Gateway database..."); + await gatewayDb.Database.EnsureCreatedAsync(); + app.Logger.LogInformation("Gateway database initialized successfully"); + } + catch (Exception ex) + { + app.Logger.LogError(ex, "Failed to initialize Gateway database"); + } +} + app.MapControllers(); +// 健康检查端点 +app.MapGet("/health", () => Results.Ok(new { status = "healthy", timestamp = DateTime.UtcNow })); + app.Run();