节点在线、应用在线、配置在线使用令牌查询
大石头 authored at 2021-12-16 19:49:30
11.02 KiB
Stardust
# SRV — App 服务端架构 > **版本**:v3.8 | **日期**:2026-07-26 > **覆盖模块**:AppController + RegistryService(应用接入服务端) > **关联文档**:[Node服务端架构](/NewLife/Stardust/Blob/master/Doc/SRV-Node服务端架构.md) · [Remoting服务端架构](/NewLife/Stardust/Blob/master/Doc/SRV-Remoting架构.md) > **定位**:本文档用于梳理 AppClient 应用从接入到下线的服务端全链路串联关系,非 API 参考手册。 --- ## 1. 分层架构总览 ``` AppController RegistryService CMD 层 (HTTP 入口) (业务逻辑) (长连接下发) /App/Login ──→ RegistryService.Login ──→ SessionManager /App/Ping ──→ RegistryService.Ping ├─ AppSessionManager /App/Logout ──→ RegistryService.Logout └─ AppCommandSession /App/Notify ──→ RegistryService.SetOnline (WebSocket 全双工) /App/SendCmd ──→ RegistryService.SendCommand /App/PostEvent ──→ RegistryService.PostEvents ``` **关键关系**: - `AppController` 继承 `BaseController`,接入 `BaseController.OnActionExecuting` 管线 - `RegistryService` 继承 `DefaultDeviceService<App, AppOnline>`,但与 NodeService 有显著差异 - `AppSessionManager` 继承 `SessionManager`,Topic=`AppCommands`,话题隔离 - `AppOnlineService` 独立维护应用在线记录的内存缓存(不经过 DefaultDeviceService 缓存层) --- ## 2. 控制器管线(每请求) 与 [Remoting 服务端架构](/NewLife/Stardust/Blob/master/Doc/SRV-Remoting架构.md) 第 2 节一致。每个请求到达时: ``` OnActionExecuting ├─ 从对象池取 DeviceContext ├─ 提取 Token → DecodeToken → Jwt.Subject = app.Name ├─ GetDevice(name) → App.FindByName → ctx.Device = app └─ GetOnline(ctx) → AppOnline.FindBySessionID → ctx.Online = online ``` App 特有:`OnAuthorize` 中额外执行 IP 黑白名单校验(`app.MatchIp` + `project.MatchIp`),而非硬件指纹。 --- ## 3. Node vs App 关键差异总览 | 维度 | NodeService | RegistryService | |------|-------------|-----------------| | **设备实体** | `Node` / `NodeOnline` | `App` / `AppOnline` | | **SessionId 规则** | `ctx.Code`(节点编码 `359AB559`) | `ctx.ClientId`(应用实例 ID) | | **GetOnline** | 按 Code 查 | 按 ClientId 查 | | **鉴权方式** | 硬件指纹 `CheckNode` + 密钥 | IP 黑白名单 + 密钥 | | **注册方式** | `BuildCode`(UUID散列→编码) | 应用名直接注册 | | **登录 OnLogin** | 不自行持久化(交基类) | 自行调 `online.Update()`(需关联节点) | | **心跳** | 更新 Node 表(Frameworks等)+ NodeOnline | 仅更新 AppOnline | | **注销** | 检查下线告警 | 清理 AppOnlineService 内存缓存 | | **额外功能** | DDNS、升级推送、dotNet安装 | **服务注册发现、健康检查** | | **集群角色** | 被管理节点 | 服务提供者/消费者 | --- ## 4. 核心业务流程 ### 4.1 登录 → 注册 ``` POST /App/Login (Body: AppModel JSON, [AllowAnonymous]) │ ▼ AppController.Login(model) ├─ App.FindByName(model.AppId) → ctx.Device = app │ └─ registryService.Login(ctx, request, "Http") │ ├─ Authorize(ctx, request) ← RegistryService 重写 │ ├─ app.MatchIp(ip) — IP 黑白名单 │ ├─ app.Project.MatchIp(ip) — 项目 IP 过滤 │ ├─ !app.Enable → 拒绝 │ └─ 密钥验证(明文 → passwordProvider) │ ├─ Register(ctx, request) ← 动态注册 │ ├─ App.GetOrAdd(name) → 新应用 │ ├─ 生成 Secret(Rand.NextString(16)) │ └─ Enable = AppAutoRegister 配置 │ ├─ OnLogin(ctx, request) ← RegistryService 重写 │ ├─ app.DisplayName/Compile/Version 更新 │ ├─ app.LastLogin/LastIP → app.Update() │ ├─ ctx.Online = GetOnline(ctx) ?? CreateOnline(ctx) │ ├─ online.NodeId = Node.FindByCode(inf.NodeCode)?.Id │ │ └─ 回退:Node.SearchByIP(inf.IP).FirstOrDefault() │ ├─ online.Version/Compile 更新 │ └─ online.Update() ← 自行持久化(含节点关联) │ ├─ (device as IEntity)?.Update() ← 基类 Login 持久化设备 └─ (ctx.Online as IEntity)?.Update() ← 基类 Login 持久化在线 ``` > **差异**:`RegistryService.OnLogin` 自行调用 `online.Update()`,因为需要关联节点信息(`online.NodeId`)。`NodeService.OnLogin` 不自行 Update,交给基类统一处理。 ### 4.2 心跳流程 ``` POST /App/Ping (Body: AppInfo JSON) │ ▼ AppController.Ping(inf) └─ registryService.Ping(ctx, inf, null) └─ base.OnPing(ctx, request) ← DefaultDeviceService └─ online2.Save(request, ctx) ├─ Fill(appInfo) + Save └─ AppMeter.WriteData(性能数据) │ └─ AcquireCommands(ctx) ← 查询待下发命令 ``` 与 Node 心跳的差异: - 不更新 App 表(Node 需要更新 Frameworks) - 额外写 `AppMeter` 性能数据 - `Ping` 请求体中包含服务注册信息(`Service` 数组) ### 4.3 注销流程 ``` GET /App/Logout?reason=xxx │ ▼ registryService.Logout(ctx, reason, "Http") ├─ base.Logout(ctx, reason, "Http") ← DefaultDeviceService │ └─ SettleOnline + LongLink=false → Update │ └─ _appOnline.RemoveOnline(ctx.ClientId) ← AppOnlineService 内存缓存清理 ``` --- ## 5. 服务注册与发现(App 特有核心功能) ### 5.1 服务注册 App 心跳时携带 `Service[]` 数组,每个元素描述一个提供的服务: ``` Ping → RegisterService(app, service, model, online, ip) │ ├─ 查找已有 AppService(按 ServiceId + Client 匹配) ├─ Singleton 模式:Client = 本地 IP(去进程 ID) │ ├─ 新建/更新: │ ├─ Enable = app.AutoActive │ ├─ Scope = AppRule.CheckScope(作用域匹配) │ ├─ Tag / Version / Address 更新 │ ├─ 地址处理: │ │ ├─ Extranet → 来源 IP │ │ ├─ 其他 → model.IP → localIp → 来源 IP │ │ └─ "://*" → 替换为实际 IP │ └─ svc.Save() │ ├─ service.Providers = services.Count → Save └─ 新增实例 → HealthCheck(svc) 异步健康检测 ``` ### 5.2 服务发现 ``` App Ping → Service[] 中的 Consume 字段 └─ ResolveService(service, model, scope) ├─ AppService.FindAllByService(service.Id) ├─ 过滤:Enable + Healthy + Match(minVersion, scope, tags) ├─ 每个匹配实例 → ServiceModel { Address, Version, ... } └─ 返回 ServiceModel[] ``` ### 5.3 服务注销 ``` App Logout → UnregisterService(app, service, model, ip) ├─ 按 ServiceId + Client 查找 AppService ├─ svc.Enable = false → svc.Update() └─ service.Providers 更新 → Save ``` ### 5.4 健康检查 ``` RegisterService 中新实例 → HealthCheck(svc) ├─ 从 service.HealthAddress 拼装健康检查 URL ├─ HttpClient.GetStringAsync(url) ├─ 成功 → svc.Healthy = true, svc.CheckResult = response └─ 失败 → svc.Healthy = false, svc.CheckResult = exception └─ svc.CheckTimes++; svc.LastCheck = now → Update ``` ### 5.5 服务变更通知 ``` RegisterService/UnregisterService → NotifyConsumers(service, command) ├─ AppConsume.FindAllByService → 找出所有订阅者 ├─ 对每个订阅应用 → SendCommand(app, "registry/register", model) └─ 消费者收到命令 → 重新 ResolveService → 刷新服务地址列表 ``` --- ## 6. 指令下发管线 ``` 外部平台 → POST /App/SendCommand (CommandInModel) │ ▼ [AllowAnonymous] 但需应用令牌 AppController.SendCommand(model) └─ registryService.SendCommand(ctx, model) ├─ App.FindByName(model.Code) → 验证应用 ├─ DecodeToken → Jwt → App.FindByName → 验证权限 ├─ AllowControlNodes 权限检查 └─ 回退到 SendCommand(app, model, user) 内部方法 ├─ AppCommand 实体(就绪状态)→ Insert └─ sessionManager.PublishAsync(code, cmd, timeout) │ ▼ 事件总线广播 AppCommandSession 收到 └─ WebSocket.SendAsync(commandJson) │ ▼ 应用执行 POST /App/CommandReply (HTTP) └─ registryService.CommandReply(ctx, model) ├─ AppCommand.Status/Result 更新 └─ PublishResponseAsync → 广播回发起方 ``` --- ## 7. 会话与令牌 ### 令牌颁发 App 登录成功后由 Controller 自行颁发令牌(不同于 Node 由基类颁发): ``` AppController.Login ├─ registryService.Login(...) → 鉴权成功 ├─ app.Enable → TokenService.IssueToken(app.Name, clientId) └─ LoginResponse { Token, Expire, Code?, Secret? } ``` ### WebSocket 通知 与 Node 同构,`AppController.Notify()` → `HandleNotify(socket)`: ``` AppCommandSession ├─ Code = $"{app.Name}@{ctx.ClientId}" ├─ SetOnline = online => registryService.SetOnline(ctx, online) └─ WaitAsync() ← 长期持有 ``` WebSocket 升级请求同样走完整 HTTP 管线(`OnAuthorize` → `ctx.Online` 已设置)。 --- ## 8. RegistryService 重写清单 | 方法 | 用途 | 核心逻辑 | |------|------|---------| | `Authorize` | 鉴权 | IP 黑白名单 `app.MatchIp` + 密钥验证 | | `Register` | 自动注册 | `App.GetOrAdd(name)` + 生成 Secret | | `OnLogin` | 登录处理 | `app.Update`、关联 NodeId、`online.Update` 自行持久化 | | `Logout` | 注销 | 基类结算 + `appOnline.RemoveOnline` 清理缓存 | | `QueryDevice` | 查设备 | `App.FindByName` | | `QueryOnline` | 查在线 | `AppOnline.FindBySessionId` | | `GetSessionId` | 会话标识 | 返回 `ctx.ClientId`(应用实例 ID) | | `AcquireCommands` | 查询命令 | 缓存 1000 条 + AppId 匹配过滤 | | `CommandReply` | 命令响应 | AppCommand.Status/Result 更新 | | `CreateEvent` | 事件实体 | 创建 AppHistory 并关联 NodeId/Client | | `WriteHistory` | 写历史 | 创建 AppHistory + NodeId/Client 关联 | ### 业务方法(非虚方法重写) | 方法 | 用途 | |------|------| | `RegisterService` | 服务注册 → AppService 新建/更新 | | `UnregisterService` | 服务注销 → svc.Enable=false | | `ResolveService` | 服务发现 → 过滤返回可用实例 | | `HealthCheck` | 健康检查 → HTTP GET health URL | | `NotifyConsumers` | 服务变更通知 → SendCommand 给所有订阅者 | | `SendCommand(App, ...)` | 内部命令下发 → NodeCommand.Insert + PublishAsync |