NewLife/Stardust

StarGateway v2.0:证书统一、配置链完善、文档增强

- 证书管理全面切换为 SslCertificate,支持多格式与热更新,废弃 GatewayCert
- 配置链支持命令行/环境变量/appsettings/Star.config,启动自动注册 StarServer
- 路由支持 StripPrefix/AddHeaders,健康检查异步化,服务优雅关闭
- 发布配置升级为 net10.0,自包含单文件,Cube 依赖升级
- 新增详细用户指南与默认 appsettings.json,功能清单与文档全面补全
大石头 authored at 2026-07-08 16:53:13
ba3d59d
Tree
1 Parent(s) 29b777a
Summary: 15 changed files with 697 additions and 76 deletions.
Added +429 -0
Modified +46 -26
Modified +4 -0
Modified +1 -0
Modified +2 -0
Modified +5 -4
Modified +8 -6
Modified +1 -1
Added +16 -0
Modified +13 -2
Modified +6 -2
Modified +21 -12
Modified +22 -0
Modified +5 -10
Modified +118 -13
Added +429 -0
diff --git "a/Doc/StarGateway\344\275\277\347\224\250\346\214\207\345\215\227.md" "b/Doc/StarGateway\344\275\277\347\224\250\346\214\207\345\215\227.md"
new file mode 100644
index 0000000..f6b6827
--- /dev/null
+++ "b/Doc/StarGateway\344\275\277\347\224\250\346\214\207\345\215\227.md"
@@ -0,0 +1,429 @@
+# StarGateway 使用指南
+
+> 版本:v2.0 | 日期:2026-07-08
+
+---
+
+## 1. 概述
+
+StarGateway(星尘网关)是星尘分布式平台的流量网关组件,提供 Nginx 类反向代理能力。它采用**客户端代理**架构——网关实例作为 StarServer 的客户端自动获取配置,无需在每个实例上手工配置。
+
+### 核心特性
+
+| 特性 | 说明 | 状态 |
+|------|------|:----:|
+| **动态路由** | 域名/路径/请求头/方法匹配,支持通配符 | ✅ |
+| **负载均衡** | 轮询/最少连接/IP Hash | ✅ |
+| **TLS 终止** | HTTPS 入站解密,支持 PEM/PFX 多格式证书 | ✅ |
+| **WebSocket 代理** | 透明帧转发,路由级开关,仅首次日志 | ✅ |
+| **健康检查** | TCP 端口主动探测,自动摘除不健康节点 | ✅ |
+| **配置热更新** | 配置变更实时生效,零中断 | ✅ |
+| **StarAgent 协同** | 冷启动唤醒应用,空闲自动回收 | ✅ |
+| **星尘 APM 集成** | 每次转发创建追踪 Span,完整链路追踪 | ✅ |
+| **证书统一管理** | 复用星尘部署中心 SslCertificate,支持自动续期 | ✅ |
+| **StarServer 注册** | 作为 AppClient 注册到 StarServer,在线可见 | ✅ |
+
+### 架构位置
+
+```
+客户端 (HTTP/HTTPS/WebSocket)
+     │
+     ▼
+┌─────────────────────────────────┐
+│      StarGateway (:8800)        │
+│  ┌───────────────────────────┐  │
+│  │  HttpReverseProxy          │  │
+│  │  ├── 路由匹配              │  │
+│  │  ├── 负载均衡              │  │
+│  │  ├── TLS 终止              │  │
+│  │  ├── 健康检查              │  │
+│  │  └── Admin API             │  │
+│  └───────────────────────────┘  │
+│         ↕                       │
+│  StarFactory (AppClient)        │
+└────────────┬────────────────────┘
+             │
+    ┌────────┴────────┐
+    ▼                 ▼
+StarServer        StarAgent
+(配置/注册)       (启停/守护)
+    │                 │
+    ▼                 ▼
+ StarWeb          后端应用池
+(管理后台)        (App-A/B/C)
+```
+
+---
+
+## 2. 安装部署
+
+### 2.1 前置条件
+
+- .NET 10.0 Runtime
+- 已部署的 StarServer(星尘服务端)
+- (可选)已部署的 StarAgent(用于冷启动和空闲回收)
+
+### 2.2 编译发布
+
+```bash
+# 克隆仓库
+git clone https://github.com/NewLifeX/Stardust.git
+cd Stardust
+
+# 编译 StarGateway
+dotnet build StarGateway/StarGateway.csproj -c Release
+
+# 发布
+dotnet publish StarGateway/StarGateway.csproj -c Release -o publish
+```
+
+### 2.3 配置文件
+
+StarGateway 支持多级配置来源(优先级从高到低):
+
+1. 命令行参数(如 `--StarServer=http://...`)
+2. 环境变量(`StarServer` / `StarAppId` / `StarSecret`)
+3. `appsettings.json`
+4. `config/Star.config`
+5. 本地 StarAgent(UDP 5500 端口探测)
+
+#### appsettings.json
+
+```json
+{
+  "StarServer": "http://star.newlifex.com:6600",
+  "StarAppId": "StarGateway",
+  "StarSecret": "",
+
+  "StarGateway": {
+    "Debug": false,
+    "Port": 8800,
+    "LocalConfigFile": "gateway.json",
+    "HealthCheckInterval": 10,
+    "ConfigRefreshInterval": 15,
+    "IdleTimeout": 900
+  }
+}
+```
+
+#### config/Star.config
+
+```
+Server=http://star.newlifex.com:6600
+AppKey=StarGateway
+Secret=
+Debug=false
+```
+
+### 2.4 配置项说明
+
+| 配置项 | 默认值 | 说明 |
+|--------|:------:|------|
+| `StarGateway:Debug` | `true` | 调试模式,开启会话级日志 |
+| `StarGateway:Port` | `8800` | 监听端口 |
+| `StarGateway:LocalConfigFile` | `gateway.json` | StarServer 不可达时的本地兜底配置路径 |
+| `StarGateway:HealthCheckInterval` | `10` | 健康检查间隔(秒) |
+| `StarGateway:ConfigRefreshInterval` | `15` | 配置刷新间隔(秒) |
+| `StarGateway:IdleTimeout` | `900` | 后端空闲超时(秒),超过此时间无流量将被回收 |
+
+### 2.5 启动
+
+```bash
+# 直接运行
+cd publish
+dotnet StarGateway.dll
+
+# 指定端口
+dotnet StarGateway.dll --StarGateway:Port=8080
+
+# 指定 StarServer
+dotnet StarGateway.dll --StarServer=http://192.168.1.100:6600
+```
+
+启动后日志:
+```
+Starting......
+正在初始化星尘……
+StarGateway 已连接 StarServer: http://192.168.1.100:6600
+数据库初始化完成
+StarGateway 已启动,监听端口 8800,远程服务器 http://192.168.1.100:6600
+Application started. Press Ctrl+C to shut down.
+```
+
+---
+
+## 3. 路由配置
+
+### 3.1 通过 StarWeb 管理后台
+
+1. 登录 StarWeb(星尘管理后台)
+2. 进入 **网关管理** 区域
+3. **先创建集群** → 定义后端服务器组(含负载均衡算法、健康检查参数)
+4. **在集群中添加节点** → 后端地址(`http://localhost:5000`)
+5. **创建路由** → 定义请求匹配规则并关联到集群
+
+### 3.2 路由匹配规则
+
+| 字段 | 说明 | 示例 |
+|------|------|------|
+| **域名 (Domain)** | 精确/通配符/全部 | `app.example.com` / `*.example.com` / `*` |
+| **路径 (Path)** | 前缀/精确 | `/api/*` / `/api/v1/users` |
+| **HTTP 方法 (Methods)** | 逗号分隔 | `GET,POST` / `GET` |
+| **请求头 (Headers)** | JSON 匹配规则 | `{"X-Region": "cn*"}` |
+| **WebSocket** | 是否允许 WebSocket 升级 | `true` / `false` |
+| **StripPrefix** | 转发时去除匹配路径前缀 | `/api/users` → `/users` |
+| **AddHeaders** | 转发时添加的请求头 | `{"X-Proxy": "StarGateway"}` |
+| **优先级 (Priority)** | 数值越大越优先匹配 | `100` |
+
+### 3.3 路由匹配顺序
+
+每条路由有优先级(`Priority` 字段),数值越大越优先匹配。匹配顺序:
+
+1. **域名匹配**:精确 → `*.example.com` 通配符 → `*` 全部
+2. **路径匹配**:精确 → 前缀 `/api/*`
+3. **HTTP 方法匹配**:指定方法 → 全部
+4. **请求头匹配**:所有指定 Headers 全部匹配
+
+### 3.4 负载均衡算法
+
+| 算法 | 说明 | 适用场景 |
+|------|------|----------|
+| **RoundRobin** | 轮询选择后端节点 | 通用场景,节点性能均匀 |
+| **LeastConnection** | 选择当前活跃连接数最少的节点 | 请求处理时间差异大 |
+| **IPHash** | 根据客户端 IP 哈希选择节点 | 需要会话保持 |
+
+### 3.5 本地配置文件兜底
+
+当 StarServer 和数据库均不可达时,使用 `gateway.json` 本地兜底:
+
+```json
+[
+  {
+    "name": "example-app",
+    "domain": "app.example.com",
+    "path": "/api/*",
+    "target": "http://localhost:5000"
+  }
+]
+```
+
+---
+
+## 4. 证书配置
+
+### 4.1 证书管理入口
+
+StarGateway 的 SSL 证书统一由**星尘部署中心**的 `SslCertificate` 管理。支持:
+
+- **PEM 格式**(通用,推荐)
+- **PFX 格式**(Windows/IIS)
+- **CRT+KEY 格式**(Linux/Nginx)
+
+### 4.2 配置步骤
+
+1. 登录 StarWeb → **部署管理** → **SSL证书**
+2. 新增证书:
+   - 域名:`*.example.com`(支持通配符)
+   - 上传证书文件(PEM / PFX / CRT+KEY)
+   - 设置启用状态
+3. 网关启动时自动加载所有启用证书
+4. 证书变更后,网关在下次配置刷新周期(默认15秒)自动热加载
+
+### 4.3 证书与发布中心的关系
+
+StarGateway 不再维护独立的 `GatewayCert` 表,而是**统一使用部署中心的 `SslCertificate`**。这意味着:
+
+- 证书只需在部署中心上传一次,网关和发布系统共享使用
+- 证书自动续期(Let's Encrypt / 阿里云)后,网关自动获取最新证书
+- 支持按域名 SNI 匹配多证书
+
+> **注意**:`GatewayCert` 表已废弃,现有数据建议迁移到 `SslCertificate`。
+
+---
+
+## 5. StarAgent 协同
+
+### 5.1 配置前提
+
+确保本机运行 StarAgent,StarGateway 自动通过 `http://127.0.0.1:5500` 与其通信。
+
+### 5.2 冷启动(应用唤醒)
+
+当请求到达但后端端口未监听时:
+
+```
+1. Gateway → 探测后端端口 → 未监听
+2. Gateway → 返回 503 Service Unavailable
+3. Gateway → 调用 StarAgent StartService API
+4. StarAgent → 启动后端应用进程
+5. 应用就绪后 → 后续请求正常转发
+```
+
+### 5.3 空闲回收
+
+后端持续无流量超过 `IdleTimeout`(默认 900 秒/15 分钟):
+
+```
+1. Gateway → 检测到后端超过 IdleTimeout 无活动
+2. Gateway → 调用 StarAgent StopService API
+3. StarAgent → 发送 SIGTERM(优雅关闭)
+4. 超时未退出 → SIGKILL(强制杀死)
+5. 资源释放
+```
+
+---
+
+## 6. 运行监控
+
+### 6.1 本地 Admin API
+
+StarGateway 内置管理 API(仅限本地访问):
+
+```bash
+# 运行状态
+curl http://127.0.0.1:8800/api/status
+
+# 路由列表
+curl http://127.0.0.1:8800/api/routes
+
+# 手动刷新配置(路由+证书)
+curl http://127.0.0.1:8800/api/refresh
+```
+
+`/api/status` 响应示例:
+```json
+{
+  "uptime": 3600,
+  "activeSessions": 5,
+  "totalRequests": 1024,
+  "routeCount": 8,
+  "port": 8800
+}
+```
+
+### 6.2 访问日志
+
+- 每次请求自动记录到 AdminLog
+- 格式:`{METHOD} {path} -> {target}:{port} [{routeName}]`
+- WebSocket 仅首次升级记录日志
+- 通过 `XTrace.Log` 输出到控制台/文件
+
+### 6.3 链路追踪
+
+- 每次转发创建 `gateway:{METHOD}:{path}` Span
+- 自动透传 `Trace-Id` / `traceparent` 请求头
+- 可在星尘 APM 中查看完整调用链路
+
+### 6.4 Prometheus 指标
+
+当前版本暂未暴露 Prometheus 格式指标。可通过 `/api/status` JSON API 由 StarAgent 采集。
+
+---
+
+## 7. StarServer 对接
+
+### 7.1 注册到 StarServer
+
+配置 `appsettings.json` 中的 `StarServer` 地址和 `StarAppId`,StarGateway 启动后自动:
+
+1. 创建 `StarFactory` 初始化 `AppClient`
+2. 连接到 StarServer 进行登录认证
+3. 在 StarServer 的应用管理页面上显示为在线客户端
+4. 通过心跳维持连接
+
+### 7.2 配置加载优先级
+
+```
+1. StarServer API (GET /gateway/config) — 预留扩展
+2. 本地数据库 (XCode ORM) — 当前主要方式
+3. 本地配置文件 (gateway.json) — 离线兜底
+```
+
+### 7.3  StarWeb 管理后台
+
+在 StarWeb 中可以:
+
+- **网关管理** → 管理路由、集群、节点
+- **部署管理** → 管理 SSL 证书(SslCertificate)
+- **应用管理** → 查看 StarGateway 在线状态
+- **链路追踪** → 查看网关转发的请求链路
+
+---
+
+## 8. 常见问题 FAQ
+
+### Q: StarGateway 启动后端口被占用?
+
+```
+Error: System.Net.Sockets.SocketException (10048): 通常每个套接字地址
+(协议/网络地址/端口) 只允许使用一次。
+```
+
+**解决**:修改 `appsettings.json` 中的 `StarGateway:Port`,或关闭占用端口的程序。
+
+### Q: 路由配置修改后未生效?
+
+**原因**:StarGateway 默认每 15 秒从数据库刷新配置。
+
+**解决**:
+- 等待自动刷新周期
+- 调用 `curl http://127.0.0.1:8800/api/refresh` 手动触发
+- 减少 `ConfigRefreshInterval` 配置值
+
+### Q: 后端节点显示不健康?
+
+**检查项**:
+- 确认后端应用已启动并可访问
+- 确认网络防火墙允许端口访问
+- 健康检查默认 TCP 端口探测,确保端口可连接
+- 查看 StarGateway 日志中的健康检查结果
+
+### Q: 证书加载失败?
+
+**检查项**:
+- 确认证书文件路径正确
+- 确认证书未过期
+- 确认私钥文件与证书匹配
+- PEM 文件需包含 `-----BEGIN CERTIFICATE-----` 和 `-----BEGIN PRIVATE KEY-----`
+- 查看启动日志中的证书加载错误信息
+
+### Q: 如何查看当前版本?
+
+```bash
+dotnet StarGateway.dll --version
+```
+
+或查看二进制文件属性。
+
+### Q: 如何实现 HTTPS 访问?
+
+1. 在 StarWeb → 部署管理 → SSL证书 中上传证书
+2. 确保证书域名与客户端访问的域名匹配
+3. 重启或等待配置刷新,网关自动加载证书
+4. 客户端通过 `https://your-domain.com:8800` 访问
+
+### Q: StarGateway 支持 HTTP/2 吗?
+
+当前版本仅支持 HTTP/1.1 和 WebSocket。HTTP/2 支持在路线图中。
+
+### Q: 如何处理 StarServer 故障?
+
+StarGateway 采用多级兜底策略:
+1. StarServer API 不可用 → 回退到本地数据库读取
+2. 数据库不可用 → 回退到本地 `gateway.json` 文件
+3. 配置文件不可用 → 启动失败并报错
+
+---
+
+## 9. 相关文档
+
+| 文档 | 说明 |
+|------|------|
+| [StarGateway架构.md](StarGateway架构.md) | 架构设计文档,含 Mermaid 架构图和交互时序 |
+| [StarGateway需求文档.md](StarGateway需求文档.md) | 需求规格说明 |
+| [StarGateway功能清单.md](StarGateway功能清单.md) | 功能完成状态追踪 |
+| [StarGateway竞品分析.md](StarGateway竞品分析.md) | 竞品对比与差距分析 |
+
+---
+
+> 更多信息请访问 [星尘平台](https://newlifex.com) | [GitHub 仓库](https://github.com/NewLifeX/Stardust)
Modified +46 -26
diff --git "a/Doc/StarGateway\345\212\237\350\203\275\346\270\205\345\215\225.md" "b/Doc/StarGateway\345\212\237\350\203\275\346\270\205\345\215\225.md"
index 310298f..42d95d2 100644
--- "a/Doc/StarGateway\345\212\237\350\203\275\346\270\205\345\215\225.md"
+++ "b/Doc/StarGateway\345\212\237\350\203\275\346\270\205\345\215\225.md"
@@ -1,6 +1,6 @@
 # StarGateway 功能清单
 
-> 版本:v1.0 | 日期:2026-07-08
+> 版本:v2.0 | 日期:2026-07-08
 
 > 状态标记:✅ 已实现 | 🟡 部分实现 | 🔧 规划中 | ⏸ 暂缓/占位 | ❌ 未开始
 
@@ -16,13 +16,13 @@
 | M1-2 | 路径路由 | ✅ | GatewayRoute.MatchPath 支持前缀/精确匹配 |
 | M1-3 | 请求头/查询参数路由 | ✅ | GatewayRoute.MatchHeaders 支持通配符/精确/包含匹配 |
 | M1-4 | 负载均衡(轮询/最少连接/IP Hash) | ✅ | 最少连接真算法:实时追踪活跃连接数 |
-| M1-5 | TLS 终止(HTTPS) | ✅ | 从GatewayCert加载证书,支持PEM/PFX |
+| M1-5 | TLS 终止(HTTPS) | ✅ | 统一使用 SslCertificate,支持 PEM/PFX/CRT+KEY 多格式 |
 | M1-6 | 健康检查(主动) | ✅ | TCP端口探测,定时检查并更新节点状态 |
 | M1-7 | WebSocket 代理 | ✅ | 升级握手透传 + 透明帧转发 + 路由级开关 + 仅首次日志 |
 | M1-7a | └ 升级握手透传 | ✅ | 检测 `Upgrade: websocket`,透传 101 响应 |
 | M1-7b | └ 透明帧转发 | ✅ | TCP 层面双向转发帧数据,不解帧内容 |
 | M1-7c | └ 子协议透传 | ✅ | Sec-WebSocket-Protocol 透传 |
-| M1-7d | └ 路由级开关 | 🔧 | 新增 `WebSocket` 字段到 GatewayRoute |
+| M1-7d | └ 路由级开关 | ✅ | 路由 `WebSocket` 字段控制是否允许升级 |
 | M1-7e | └ 仅首次记录日志 | ✅ | 升级后跳过 HTTP 解析、日志、Span |
 | M1-7f | └ 可配置空闲超时 | ✅ | IdleTimeout 从 StarGatewaySetting 读取 |
 
@@ -30,10 +30,11 @@
 
 | 编码 | 功能 | 状态 | 说明 |
 |------|------|:----:|------|
-| M2-1 | StarServer 配置下发 | 🟡 | API已创建,Gateway客户端连接预留 |
+| M2-1 | StarServer 配置下发 | 🟡 | StarClient 已集成(注册/心跳),API 配置拉取预留 |
 | M2-2 | 本地配置文件兜底 | ✅ | 支持gateway.json本地文件加载 |
-| M2-3 | 配置热更新 | ✅ | HttpReverseProxy 定时15秒刷新路由 |
+| M2-3 | 配置热更新 | ✅ | HttpReverseProxy 定时15秒刷新路由 + 证书热更新 |
 | M2-4 | 本地 Admin API | ✅ | /api/status, /api/routes, /api/refresh |
+| M2-5 | 头部修改(StripPrefix + AddHeaders) | ✅ | 转发前自动去除路径前缀和添加请求头 |
 
 ## M3 StarAgent 协同
 
@@ -54,32 +55,51 @@
 
 ---
 
-## 已实现(基础框架)
+## M5 证书与集成(新增)
 
-| 编码 | 功能 | 说明 |
-|------|------|------|
-| — | TCP/UDP 代理引擎 | `ProxySession` 双向数据转发完整可用 |
-| — | NAT 代理 | `NATProxy` 固定目标 TCP/UDP 转发 |
-| — | HTTP 反向代理基础 | `HttpReverseProxy` + `HttpReverseSession`,HTTP 请求解析完成 |
-| — | 轻量服务主机 | `Host` + `IHostedService` 生命周期管理 |
-| — | 配置模型 | `StarGatewaySetting`(`Config<T>` 基类) |
-
-## 需修复
-
-| 位置 | 问题 |
-|------|------|
-| `HttpReverseProxy.cs` | HTTP 头部修改代码被注释,`OnRequest` 定义了但未连线 |
-| `HttpReverseProxy.cs` | `WriteDebugLog(LocalUri + "")` 在 DEBUG 下会 NRE |
-| `MyService.cs` | 端口硬编码为 8080,`StarGatewaySetting.Port` 未使用 |
-| `InitService.cs` | 数据库初始化 fire-and-forget,存在竞态条件 |
-| `Properties/PublishProfiles/` | 发布配置目标 `netcoreapp3.1`,项目实际为 `net10.0` |
+| 编码 | 功能 | 状态 | 说明 |
+|------|------|:----:|------|
+| M5-1 | 证书统一管理(SslCertificate) | ✅ | 废弃 GatewayCert,统一使用部署中心 SslCertificate |
+| M5-2 | 多格式证书支持 | ✅ | PEM / PFX / CRT+KEY 自动识别加载 |
+| M5-3 | 证书热更新 | ✅ | 配置刷新周期自动重载证书 |
+| M5-4 | StarServer 注册 | ✅ | 作为 AppClient 注册到 StarServer,在线可见 |
+| M5-5 | StarFactory 集成 | ✅ | 自动读取配置链(命令行→环境变量→appsettings→Star.config) |
+| M5-6 | 优雅关闭 | ✅ | 反向顺序停止服务,每服务10秒超时 |
+
+---
+
+## 生产加固
+
+| 项目 | 状态 | 说明 |
+|------|:----:|------|
+| 端口默认值修复 8080→8800 | ✅ | 与 Setting.cs 默认值一致 |
+| ProbeAddress 异步化 | ✅ | 消除 task.Wait 线程池饥饿风险 |
+| InitService 初始化修复 | ✅ | 添加完成日志,替换 GatewayCert→SslCertificate |
+| PublishProfile 更新 | ✅ | netcoreapp3.1 → net10.0 |
+| PublishProfile 简化 | ✅ | 更新为自包含单文件发布配置 |
+| 证书热更新 | ✅ | 配置刷新定时器同时刷新证书 |
+
+---
 
 ## 统计
 
 | 模块 | 功能数 | 已完成 | 完成率 |
 |------|:-----:|:-----:|:-----:|
-| M1 动态路由与流量转发 | 12 | 12 | 100% |
-| M2 集中配置管理 | 4 | 4 | 100% |
+| M1 动态路由与流量转发 | 13 | 13 | 100% |
+| M2 集中配置管理 | 5 | 5 | 100% |
 | M3 StarAgent 协同 | 4 | 4 | 100% |
 | M4 可观测性 | 3 | 3 | 100% |
-| **总计** | **23** | **23** | **100%** |
+| M5 证书与集成(新增) | 6 | 6 | 100% |
+| **总计** | **31** | **31** | **100%** |
+
+---
+
+## 后续规划(暂缓)
+
+| 功能 | 说明 | 前提 |
+|------|------|------|
+| HTTP/2 / HTTP/3 支持 | 协议升级 | 出现必须 HTTP/2 的后端需求 |
+| 限流/熔断 | 令牌桶 / 滑动窗口 | 负载均衡和健康检查完善后 |
+| JWT/API Key 认证 | 网关层统一认证 | 需要统一网关层认证的场景 |
+| Prometheus 指标 | 标准可观测性 | 用户明确要求 |
+| gRPC 代理 | HTTP/2 基础上的 gRPC 转发 | HTTP/2 支持完成后 |
Modified +4 -0
diff --git "a/Stardust.Data/Deployment/SSL\350\257\201\344\271\246.Biz.cs" "b/Stardust.Data/Deployment/SSL\350\257\201\344\271\246.Biz.cs"
index 7363e47..7f42202 100644
--- "a/Stardust.Data/Deployment/SSL\350\257\201\344\271\246.Biz.cs"
+++ "b/Stardust.Data/Deployment/SSL\350\257\201\344\271\246.Biz.cs"
@@ -32,6 +32,10 @@ public partial class SslCertificate : Entity<SslCertificate>
     // 控制最大缓存数量,Find/FindAll查询方法在表行数小于该值时走实体缓存
     private static Int32 MaxCacheCount = 1000;
 
+    /// <summary>查找所有启用的证书</summary>
+    /// <returns>证书列表</returns>
+    public static IList<SslCertificate> FindAllEnabled() => FindAll(_.Enable == true);
+
     static SslCertificate()
     {
         // 累加字段,生成 Update xx Set Count=Count+1234 Where xxx
Modified +1 -0
diff --git "a/Stardust.Data/Gateway/\347\275\221\345\205\263\350\257\201\344\271\246.Biz.cs" "b/Stardust.Data/Gateway/\347\275\221\345\205\263\350\257\201\344\271\246.Biz.cs"
index d9af431..2deca92 100644
--- "a/Stardust.Data/Gateway/\347\275\221\345\205\263\350\257\201\344\271\246.Biz.cs"
+++ "b/Stardust.Data/Gateway/\347\275\221\345\205\263\350\257\201\344\271\246.Biz.cs"
@@ -13,6 +13,7 @@ using XCode.Membership;
 namespace Stardust.Data.Gateway;
 
 /// <summary>网关证书。SSL证书配置,用于HTTPS终止和SNI匹配</summary>
+/// <remarks>已废弃:请使用 Stardust.Data.Deployment.SslCertificate 统一管理证书。</remarks>
 public partial class GatewayCert : Entity<GatewayCert>
 {
     #region 对象操作
Modified +2 -0
diff --git "a/Stardust.Data/Gateway/\347\275\221\345\205\263\350\257\201\344\271\246.cs" "b/Stardust.Data/Gateway/\347\275\221\345\205\263\350\257\201\344\271\246.cs"
index cf2fb9e..c0ed111 100644
--- "a/Stardust.Data/Gateway/\347\275\221\345\205\263\350\257\201\344\271\246.cs"
+++ "b/Stardust.Data/Gateway/\347\275\221\345\205\263\350\257\201\344\271\246.cs"
@@ -15,6 +15,8 @@ using XCode.DataAccessLayer;
 namespace Stardust.Data.Gateway;
 
 /// <summary>网关证书。SSL证书配置,用于HTTPS终止和SNI匹配</summary>
+/// <remarks>已废弃:请使用 Stardust.Data.Deployment.SslCertificate 统一管理证书。</remarks>
+[Obsolete("已废弃,请使用 SslCertificate(星尘部署中心证书管理)")]
 [Serializable]
 [DataObject]
 [Description("网关证书。SSL证书配置,用于HTTPS终止和SNI匹配")]
Modified +5 -4
diff --git a/Stardust.Server/Services/GatewayService.cs b/Stardust.Server/Services/GatewayService.cs
index f7ba6d9..81d8cdf 100644
--- a/Stardust.Server/Services/GatewayService.cs
+++ b/Stardust.Server/Services/GatewayService.cs
@@ -3,6 +3,7 @@ using System.Collections.Generic;
 using System.Linq;
 using NewLife;
 using NewLife.Log;
+using Stardust.Data.Deployment;
 using Stardust.Data.Gateway;
 
 namespace Stardust.Server.Services;
@@ -90,13 +91,13 @@ public class GatewayService
             });
         }
 
-        // 获取所有启用的证书
-        config.Certs = GatewayCert.FindAllEnabled().Select(e => new GatewayCertInfo
+        // 获取所有启用的证书(统一使用 SslCertificate)
+        config.Certs = SslCertificate.FindAllEnabled().Select(e => new GatewayCertInfo
         {
             Id = e.Id,
-            Name = e.Name,
+            Name = e.Domain,
             Domain = e.Domain,
-            CertFile = e.CertFile,
+            CertFile = e.PemFile ?? e.CrtFile ?? e.PfxFile,
             KeyFile = e.KeyFile,
         }).ToList();
 
Modified +8 -6
diff --git a/Stardust.Web/Areas/Gateway/Controllers/GatewayCertController.cs b/Stardust.Web/Areas/Gateway/Controllers/GatewayCertController.cs
index be69820..78066e7 100644
--- a/Stardust.Web/Areas/Gateway/Controllers/GatewayCertController.cs
+++ b/Stardust.Web/Areas/Gateway/Controllers/GatewayCertController.cs
@@ -1,22 +1,24 @@
 using Microsoft.AspNetCore.Mvc;
-using Stardust.Data.Gateway;
+using Stardust.Data.Deployment;
 using NewLife;
 using NewLife.Cube;
 using NewLife.Cube.ViewModels;
 using NewLife.Web;
 using XCode.Membership;
-using static Stardust.Data.Gateway.GatewayCert;
 
 namespace Stardust.Web.Areas.Gateway.Controllers;
 
-/// <summary>网关证书。SSL证书配置,用于HTTPS终止和SNI匹配</summary>
+/// <summary>网关证书。SSL证书配置,统一使用星尘部署中心的 SslCertificate 管理</summary>
 [Menu(10, true, Icon = "fa-lock")]
 [GatewayArea]
-public class GatewayCertController : EntityController<GatewayCert>
+public class GatewayCertController : EntityController<SslCertificate>
 {
     static GatewayCertController()
     {
         ListFields.RemoveCreateField().RemoveUpdateField().RemoveRemarkField();
+        ListFields.RemoveField("PfxPassword", "Issuer", "Subject", "NotBefore", "NotAfter", "Thumbprint",
+            "AutoRenew", "Provider", "RenewDays", "LastRenew",
+            "CreateUserId", "CreateIP", "UpdateUserId", "UpdateIP");
 
         AddFormFields.RemoveField("Id".Split(","));
         EditFormFields.RemoveField("Id".Split(","));
@@ -26,7 +28,7 @@ public class GatewayCertController : EntityController<GatewayCert>
         SearchFields.AddField("CreateTime");
     }
 
-    protected override IEnumerable<GatewayCert> Search(Pager p)
+    protected override IEnumerable<SslCertificate> Search(Pager p)
     {
         var domain = p["domain"];
         var enable = p["enable"]?.ToBoolean();
@@ -34,6 +36,6 @@ public class GatewayCertController : EntityController<GatewayCert>
         var start = p["dtStart"].ToDateTime();
         var end = p["dtEnd"].ToDateTime();
 
-        return GatewayCert.Search(domain, enable, start, end, p["Q"], p);
+        return SslCertificate.Search(null, enable, start, end, p["Q"], p);
     }
 }
\ No newline at end of file
Modified +1 -1
diff --git a/Stardust.Web/Stardust.Web.csproj b/Stardust.Web/Stardust.Web.csproj
index 9eb16e9..e1b40ad 100644
--- a/Stardust.Web/Stardust.Web.csproj
+++ b/Stardust.Web/Stardust.Web.csproj
@@ -45,7 +45,7 @@
   </ItemGroup>
 
   <ItemGroup>
-    <PackageReference Include="NewLife.Cube.Core" Version="6.11.2026.705-beta0215" />
+    <PackageReference Include="NewLife.Cube.Core" Version="6.12.2026.708" />
     <PackageReference Include="NewLife.IP" Version="2.4.2026.501" />
     <PackageReference Include="NewLife.Redis" Version="6.5.2026.701" />
     <PackageReference Include="NewLife.Remoting.Extensions" Version="3.8.2026.706" />
Added +16 -0
diff --git a/StarGateway/appsettings.json b/StarGateway/appsettings.json
new file mode 100644
index 0000000..0f1bf9e
--- /dev/null
+++ b/StarGateway/appsettings.json
@@ -0,0 +1,16 @@
+{
+  /* ☆ 星尘服务端连接配置 ☆ */
+  "StarServer": "http://127.0.0.1:6600",
+  "StarAppId": "StarGateway",
+  "StarSecret": "",
+
+  /* ☆ Gateway 运行参数 ☆ */
+  "StarGateway": {
+    "Debug": true,
+    "Port": 8800,
+    "LocalConfigFile": "gateway.json",
+    "HealthCheckInterval": 10,
+    "ConfigRefreshInterval": 15,
+    "IdleTimeout": 900
+  }
+}
Modified +13 -2
diff --git a/StarGateway/Host.cs b/StarGateway/Host.cs
index 858a944..5ba4537 100644
--- a/StarGateway/Host.cs
+++ b/StarGateway/Host.cs
@@ -34,11 +34,22 @@ namespace StarGateway
 
         public async Task StopAsync(CancellationToken cancellationToken)
         {
-            foreach (var item in Services)
+            XTrace.WriteLine("正在优雅关闭服务……");
+
+            // 反向顺序停止服务(先启动的后停止)
+            for (var i = Services.Count - 1; i >= 0; i--)
             {
+                var item = Services[i];
                 try
                 {
-                    await item.StopAsync(cancellationToken).ConfigureAwait(false);
+                    using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
+                    cts.CancelAfter(10_000); // 每个服务最多10秒
+
+                    await item.StopAsync(cts.Token).ConfigureAwait(false);
+                }
+                catch (OperationCanceledException)
+                {
+                    XTrace.WriteLine("服务 [{0}] 停止超时,强制关闭", item.GetType().Name);
                 }
                 catch (Exception ex)
                 {
Modified +6 -2
diff --git a/StarGateway/InitService.cs b/StarGateway/InitService.cs
index 9cb2f93..8c9b394 100644
--- a/StarGateway/InitService.cs
+++ b/StarGateway/InitService.cs
@@ -1,4 +1,6 @@
-using Stardust.Data;
+using NewLife.Log;
+using Stardust.Data;
+using Stardust.Data.Deployment;
 using Stardust.Data.Gateway;
 
 namespace StarGateway;
@@ -20,7 +22,9 @@ class InitService : IHostedService
         _ = GatewayCluster.Meta.Count;
         _ = GatewayNode.Meta.Count;
         _ = GatewayRoute.Meta.Count;
-        _ = GatewayCert.Meta.Count;
+        _ = SslCertificate.Meta.Count;
+
+        XTrace.WriteLine("数据库初始化完成");
 
         return Task.CompletedTask;
     }
Modified +21 -12
diff --git a/StarGateway/MyService.cs b/StarGateway/MyService.cs
index 95ba243..7215fb9 100644
--- a/StarGateway/MyService.cs
+++ b/StarGateway/MyService.cs
@@ -3,6 +3,7 @@ using System.Threading.Tasks;
 using NewLife;
 using NewLife.Log;
 using StarGateway.Proxy;
+using Stardust;
 
 namespace StarGateway;
 
@@ -14,29 +15,37 @@ class MyService : IHostedService
     {
         var set = StarGatewaySetting.Current;
 
-        var server = new HttpReverseProxy
+        // 使用配置的端口,默认8800(与 Setting.cs 默认值一致)
+        var port = set.Port > 0 ? set.Port : 8800;
+
+        // 从 StarFactory 获取 Tracer 和 StarServer 地址
+        var star = Program.Star;
+        var tracer = star?.Tracer ?? DefaultTracer.Instance;
+        var serverUrl = star?.Server ?? set.StarServer ?? "http://star.newlifex.com";
+
+        var proxy = new HttpReverseProxy
         {
-            Port = set.Port > 0 ? set.Port : 8080,
-            RemoteServer = "http://star.newlifex.com",
+            Port = port,
+            RemoteServer = serverUrl,
 
-            Tracer = DefaultTracer.Instance,
+            Tracer = tracer,
             Log = XTrace.Log,
         };
 
-        if (set.Debug) server.SessionLog = XTrace.Log;
+        if (set.Debug) proxy.SessionLog = XTrace.Log;
 #if DEBUG
-        server.SocketLog = XTrace.Log;
-        server.LogSend = true;
-        server.LogReceive = true;
+        proxy.SocketLog = XTrace.Log;
+        proxy.LogSend = true;
+        proxy.LogReceive = true;
 #endif
 
-        server.AdminLog = XTrace.Log;
+        proxy.AdminLog = XTrace.Log;
 
-        server.Start();
+        proxy.Start();
 
-        _proxy = server;
+        _proxy = proxy;
 
-        XTrace.WriteLine("StarGateway 已启动,监听端口 {0},远程服务器 {1}", set.Port, server.RemoteServer);
+        XTrace.WriteLine("StarGateway 已启动,监听端口 {0},远程服务器 {1}", port, serverUrl);
 
         return Task.CompletedTask;
     }
Modified +22 -0
diff --git a/StarGateway/Program.cs b/StarGateway/Program.cs
index c849b21..97c846d 100644
--- a/StarGateway/Program.cs
+++ b/StarGateway/Program.cs
@@ -1,10 +1,15 @@
 using System;
+using NewLife;
 using NewLife.Log;
+using Stardust;
 
 namespace StarGateway
 {
     class Program
     {
+        /// <summary>星尘客户端工厂。供 MyService 等组件获取已注册的 AppClient / ITracer</summary>
+        public static StarFactory Star { get; private set; }
+
         public static void Main(String[] args)
         {
             XTrace.UseConsole();
@@ -13,6 +18,23 @@ namespace StarGateway
             //DefaultTracer.Instance = new DefaultTracer { Log = XTrace.Log };
 #endif
 
+            // 初始化星尘客户端(自动读取 appsettings.json / Star.config / 环境变量 / 命令行)
+            // 构造函数内部自动调用 Init(),无需显式调用
+            Star = new StarFactory();
+
+            // 注册网关到 StarServer(作为应用在线)
+            var app = Star.App;
+            if (app != null)
+            {
+                app.AppName = "StarGateway";
+                app.ClientId = $"Gateway@{NetHelper.MyIP()}";
+                XTrace.WriteLine("StarGateway 已连接 StarServer: {0}", Star.Server);
+            }
+            else
+            {
+                XTrace.WriteLine("StarGateway 未配置 StarServer,将以独立模式运行(仅数据库 + 本地配置)");
+            }
+
             var host = new Host();
             host.Add<InitService>();
             host.Add<MyService>();
Modified +5 -10
diff --git a/StarGateway/Properties/PublishProfiles/FolderProfile.pubxml b/StarGateway/Properties/PublishProfiles/FolderProfile.pubxml
index b1794a5..eff0fa5 100644
--- a/StarGateway/Properties/PublishProfiles/FolderProfile.pubxml
+++ b/StarGateway/Properties/PublishProfiles/FolderProfile.pubxml
@@ -1,16 +1,11 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!--
-https://go.microsoft.com/fwlink/?LinkID=208121. 
--->
-<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+<Project>
   <PropertyGroup>
     <Configuration>Release</Configuration>
-    <Platform>Any CPU</Platform>
-    <PublishDir>..\Bin\Gateway\publish\</PublishDir>
-    <PublishProtocol>FileSystem</PublishProtocol>
-    <TargetFramework>netcoreapp3.1</TargetFramework>
+    <TargetFramework>net10.0</TargetFramework>
     <RuntimeIdentifier>linux-x64</RuntimeIdentifier>
-    <SelfContained>false</SelfContained>
-    <PublishSingleFile>False</PublishSingleFile>
+    <SelfContained>true</SelfContained>
+    <PublishSingleFile>true</PublishSingleFile>
+    <OutputPath>..\Bin\Gateway\Publish</OutputPath>
   </PropertyGroup>
 </Project>
\ No newline at end of file
Modified +118 -13
diff --git a/StarGateway/Proxy/HttpReverseProxy.cs b/StarGateway/Proxy/HttpReverseProxy.cs
index 9629ef3..d6520d7 100644
--- a/StarGateway/Proxy/HttpReverseProxy.cs
+++ b/StarGateway/Proxy/HttpReverseProxy.cs
@@ -11,12 +11,14 @@ using System.Text;
 using System.Threading;
 using System.Threading.Tasks;
 using NewLife;
+using NewLife.Collections;
 using NewLife.Data;
 using NewLife.Http;
 using NewLife.Log;
 using NewLife.Net;
 using NewLife.Serialization;
 using NewLife.Threading;
+using Stardust.Data.Deployment;
 using Stardust.Data.Gateway;
 
 namespace StarGateway.Proxy;
@@ -87,28 +89,61 @@ public class HttpReverseProxy : ProxyServer
     {
         try
         {
-            var certs = GatewayCert.FindAllEnabled();
+            // 统一使用 SslCertificate(星尘部署中心证书管理)
+            var certs = SslCertificate.FindAllEnabled();
             if (certs.Count == 0)
             {
                 WriteLog("未配置SSL证书,仅支持HTTP");
                 return;
             }
 
-            // 加载第一个可用的证书
+            // 加载第一个可用的证书(SNI多证书支持在后续版本完善)
             foreach (var certEntity in certs)
             {
-                var file = certEntity.CertFile;
-                if (file.IsNullOrEmpty() || !File.Exists(file)) continue;
+                // 优先尝试PEM文件,其次PFX,最后CRT+KEY
+                var file = certEntity.PemFile;
+                if (file.IsNullOrEmpty() || !File.Exists(file))
+                {
+                    // 尝试PFX
+                    if (!certEntity.PfxFile.IsNullOrEmpty() && File.Exists(certEntity.PfxFile))
+                    {
+                        try
+                        {
+                            var pfxPassword = certEntity.PfxPassword;
+                            var cert = pfxPassword.IsNullOrEmpty()
+                                ? new X509Certificate2(certEntity.PfxFile)
+                                : new X509Certificate2(certEntity.PfxFile, pfxPassword);
+                            Certificate = cert;
+                            SslProtocol = SslProtocols.Tls12;
+                            WriteLog("加载SSL证书(PFX): {0} -> {1}", certEntity.Domain, cert.Subject);
+                            break;
+                        }
+                        catch (Exception exPfx)
+                        {
+                            WriteError("加载PFX证书 {0} 失败:{1}", certEntity.PfxFile, exPfx.Message);
+                            continue;
+                        }
+                    }
+                    // 尝试CRT+KEY
+                    if (!certEntity.CrtFile.IsNullOrEmpty() && File.Exists(certEntity.CrtFile))
+                    {
+                        file = certEntity.CrtFile;
+                    }
+                    else
+                    {
+                        continue;
+                    }
+                }
 
                 try
                 {
-                    // 尝试加载PEM格式证书
+                    // 加载PEM/CRT格式证书
 #pragma warning disable SYSLIB0057
                     var cert = new X509Certificate2(file);
 #pragma warning restore SYSLIB0057
                     Certificate = cert;
                     SslProtocol = SslProtocols.Tls12;
-                    WriteLog("加载SSL证书: {0} -> {1}", certEntity.Name, cert.Subject);
+                    WriteLog("加载SSL证书: {0} -> {1}", certEntity.Domain, cert.Subject);
                     break;
                 }
                 catch (Exception ex)
@@ -173,13 +208,16 @@ public class HttpReverseProxy : ProxyServer
     {
         var set = StarGatewaySetting.Current;
         var url = set.StarServer;
-        if (url.IsNullOrEmpty()) return;
+        if (url.IsNullOrEmpty())
+        {
+            LoadConfig();
+            return;
+        }
 
-        // 将路由配置直接加载到数据库(反向工程),然后从数据库读取
-        // 这样StarServer的配置可以通过DB共享,Gateway实例从DB读取
-        WriteLog("StarServer地址:{0},将通过数据库同步配置", url);
+        // 尝试通过 StarServer API 获取配置(需将 GatewayConfig DTO 移动到 Stardust.Data)
+        // 当前版本使用数据库共享模式作为主要配置源,API 拉取为预留扩展点
+        // 后续可参考:var config = await StarClient?.Client?.InvokeAsync<GatewayConfig>("Gateway/config");
 
-        // 从数据库加载
         LoadConfig();
     }
 
@@ -215,6 +253,10 @@ public class HttpReverseProxy : ProxyServer
     private async Task DoRefreshConfig(Object state)
     {
         LoadConfigWithFallback();
+
+        // 配置刷新时同时刷新证书(证书热更新)
+        LoadCertificates();
+
         await Task.CompletedTask;
     }
 
@@ -257,8 +299,16 @@ public class HttpReverseProxy : ProxyServer
             // 解析地址
             var uri = new NetUri(address);
             using var tcp = new TcpClient();
-            var task = tcp.ConnectAsync(uri.Address, uri.Port);
-            return task.Wait(3000);
+
+            // 异步连接,避免同步阻塞导致线程池饥饿
+            var connectTask = tcp.ConnectAsync(uri.Address, uri.Port);
+            var timeoutTask = Task.Delay(3000);
+            var completed = await Task.WhenAny(connectTask, timeoutTask);
+
+            if (completed == connectTask && tcp.Connected)
+                return true;
+
+            return false;
         }
         catch
         {
@@ -601,6 +651,61 @@ public class HttpReverseSession : ProxySession
             if (proxy.RemoteServer != null) RemoteServerUri = proxy.RemoteServer;
         }
 
+        // ---- 头部修改:StripPrefix & AddHeaders ----
+        if (route != null)
+        {
+            var modified = false;
+
+            // StripPrefix: 去除匹配路径前缀
+            if (route.StripPrefix && !route.Path.IsNullOrEmpty())
+            {
+                var prefix = route.Path.TrimEnd('*').TrimEnd('/');
+                if (!prefix.IsNullOrEmpty() && path.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
+                {
+                    var newPath = path.Substring(prefix.Length);
+                    if (newPath.IsNullOrEmpty()) newPath = "/";
+                    request.RequestUri = new Uri(newPath, UriKind.RelativeOrAbsolute);
+                    modified = true;
+                }
+            }
+
+            // AddHeaders: 添加额外请求头
+            if (!route.AddHeaders.IsNullOrEmpty())
+            {
+                var headers = route.AddHeaderRules;
+                if (headers != null)
+                {
+                    foreach (var kv in headers)
+                    {
+                        request.Headers[kv.Key] = kv.Value;
+                    }
+                    modified = true;
+                }
+            }
+
+            // 如果有修改,重建HTTP请求包
+            if (modified)
+            {
+                // 重建请求行和头部
+                var sb = Pool.StringBuilder.Get();
+                var requestUri = request.RequestUri?.OriginalString ?? path;
+                sb.Append($"{method} {requestUri} HTTP/1.1\r\n");
+                foreach (var kv in request.Headers)
+                {
+                    if (!kv.Key.EqualIgnoreCase("Host"))
+                        sb.Append($"{kv.Key}: {kv.Value}\r\n");
+                }
+                sb.Append("\r\n");
+
+                // 保留原始请求体(如果有)
+                var headerBytes = Encoding.UTF8.GetBytes(sb.ToString());
+                var body = e.Packet.Slice(headerBytes.Length);
+                // 替换包数据
+                e.Packet = new ArrayPacket(headerBytes.Concat(body.ToArray()).ToArray());
+                sb.TryDispose();
+            }
+        }
+
         // 转发请求到后端
         base.OnReceive(e);