# StarWork 云控服务端技术架构

> star-work-cloud 仓库全景解读：单进程双服务（TLS 长连接 + gin HTTP）、统一信封协议、配置定向下发、技能/版本包分片分发、设备连接生命周期与多副本就绪的部署架构，附四张交互式图表。

---

LLMS 索引： [llms.txt](/llms.txt)

---

依据 `star-work-cloud` 仓库源码与工程内文档（`README.md` / `PROTOCOL.md`）编制。文中四张图表均为交互式：支持缩放、语义视图、关系追踪与 PNG/SVG 导出。

## 一、项目概览 {#overview}

**star-work-cloud** 是桌面端（StarWork / Milo 客户端）的**云控服务端**：单个 Go 进程同时对外提供两条服务，共用一份 PostgreSQL 与同一套业务层。

| 服务 | 默认端口 | 面向 | 职责 |
|---|---|---|---|
| TLS 长连接 | `8443` | 桌面客户端 | 设备上报 / 配置下发 / 技能包与版本包分片下发，一行业务消息一行 JSON |
| HTTP API (gin) | `8090` | 管理端 | 登录、菜单、设备、配置、模型、技能、版本、日志、角色用户 |

| 组件 | 选型 |
|---|---|
| 语言 | Go 1.26（`go.mod` 要求 ≥ 1.26.3） |
| HTTP 框架 | gin v1.12 |
| ORM | GORM v1.31 + pgx v5（纯 Go，`CGO_ENABLED=0` 静态编译） |
| 数据库 | PostgreSQL（运维提供，**无外键**，关联在应用层维护） |
| 权限 | casbin v3 + gorm-adapter（策略落库） |
| 鉴权 | golang-jwt v5 |
| 加密 | AES-256-GCM（字段加密）+ RSA（登录密码传输） |

代码采用经典分层：`handler`（解析请求/统一响应）→ `service`（业务逻辑）→ `repository`（数据访问）→ `model`（GORM 模型）；云控长连接独立成 `internal/cloud`（信封、会话表、topic 注册表、分片下发），TLS 治理在 `internal/tls`，后台巡检在 `internal/worker` + `internal/leader`（PG advisory lock 选主）。

## 二、总体架构 {#architecture}

单进程内三个协作者：**TLS 长连接服务**（握手 / AUTH / 信封协议 / 会话表）、**HTTP API**（JWT + casbin 鉴权的 10 组管理接口）、**后台巡检 Worker**（心跳、补推、归档、选主）。入口分两条：设备走 L4 直达（NodePort / LB 的 TCP 8443，HTTP Ingress 无法转发 4 层 TLS），管理端走 Ingress 的 `/starwork/api` 前缀；前端 SPA 与后端同源部署，刻意不配置 CORS。

<iframe src="/ai/xingchen/17-starwork-system-architecture.html" title="star-work-cloud 系统总体架构" loading="lazy" style="width:100%;height:700px;border:1px solid #e5e7eb;border-radius:8px;"></iframe>

<a href="/ai/xingchen/17-starwork-system-architecture.html" target="_blank">全屏打开 ↗</a>

> [!NOTE]
> 探针三件套（startup / readiness / liveness）全部打 8090 的 `/healthz`——8443 是裸 TLS 长连接，kubelet 探不了。`/healthz` 只回 `{"status":"ok"}` 不查库，库挂了不会因此被摘流量。

## 三、统一信封协议（Protocol v1） {#protocol}

一套信封覆盖**上报 / 下发 / 配置同步 / 心跳**，传输与业务分离：信封（怎么传）一次写死，业务能力以 `topic` 扩展——新增功能 = 注册表加一行 + 两端各一个处理函数，协议本身不动。

| 字段 | 必填 | 说明 |
|---|---|---|
| `ver` / `id` / `ts` | 是 | 协议版本 / 消息 ID（UUID，幂等去重 + 应答关联）/ 时间戳 |
| `type` | 是 | 固定 5 种：`cmd` / `resp` / `report` / `ack` / `event` |
| `topic` | 是 | 业务主题（`config.sync`、`skill.install`、`release.download`…） |
| `from` / `to` | 条件 | `to` 为定向描述（§四），支持用户 / 平台 / 设备 / 版本过滤 |
| `payload` / `code` / `meta` | — | 业务数据 / 应答码 / `timeout`·`retry`·`seq`·`total`·`compress` |

可靠性三件套：**幂等**（客户端记录已处理 `id`，重复消息直接 ack）、**应答超时重试**（按 `meta.timeout/retry` 重发，只补发未确认分片）、**大 payload 分片**（`meta.seq/total` 重组，可选 gzip 压缩）。

## 四、配置同步与定向下发 {#config}

配置是云控的**自举基座**——客户端连接地址本身就在配置里，因此「地址可远程更新」是第一需求。服务端维护全局配置表（`configs`：每份配置独立 `configId` 与单调递增版本号，不按用户区分），客户端本地原子持久化（`cloud-config.json`），启动 / 重连必执行 `config.sync` 对账。

<iframe src="/ai/xingchen/18-starwork-config-delivery.html" title="配置同步与定向下发时序" loading="lazy" style="width:100%;height:700px;border:1px solid #e5e7eb;border-radius:8px;"></iframe>

<a href="/ai/xingchen/18-starwork-config-delivery.html" target="_blank">全屏打开 ↗</a>

三个核心设计：

1. **双通道兜底**：`config.push`（服务端定向推送，在线即时生效）+ `config.sync`（客户端拉取，离线攒下的新配置上线即补）。配置类消息靠 sync 全量对账兜底，**离线队列只保留给一次性指令**（重启、升级等）。
2. **全量下发 + 按字段合并**：服务端始终下发完整配置对象，客户端按顶层字段合并（收到覆盖、未收到保留）。自带自愈——任何丢失或本地文件损坏，下次连接全量对账即恢复；配合原子写（临时文件 + rename）与版本守卫（旧版本拒绝覆盖新版本）。
3. **定向匹配**：`to` 描述支持 `platforms` / `deviceIds` / `minVersion` 的 AND 组合——同一账号的 mac 更新只命中 mac 设备。每次下发落 `config_deliveries` + 逐设备明细（`msg_id` 关联 ack，状态 pending / delivering / done / failed），**删除下发记录即撤销**。

## 五、技能包与版本包分发 {#packages}

技能（zip，内含 `SKILL.md`）与版本安装包共用同一套 TLS 分片分发套路：base64 分片（128KB 原始字节/片）、共用命令 `id` 幂等、收齐重组后与元信息 `sha256` 强比对。设备端不暴露公网下载地址，HTTP 下载接口仅限管理端（JWT）。

<iframe src="/ai/xingchen/19-starwork-package-flow.html" title="技能包与版本包分发流程" loading="lazy" style="width:100%;height:700px;border:1px solid #e5e7eb;border-radius:8px;"></iframe>

<a href="/ai/xingchen/19-starwork-package-flow.html" target="_blank">全屏打开 ↗</a>

关键防线与落地策略：

- **版本更新走配置携带**：`release` 表（version / platform / arch / sha256）绑定到配置，随 `config.push` / `config.sync` 全量下发；客户端做平台/架构匹配（不符不弹窗）、版本比较（不高于当前忽略）、单会话去重后才弹确认窗。
- **技能按 `category` 六类落盘**：`global`（全部编辑器）/ `docs` / `sheets` / `slides` / `pdf` / `markdown` 各自目录，非白名单取值兜底为 `global`；同目录原子替换 + `.cloud-skill.json` 登记，装完即用无需重启；后台改分类后，已装设备「更新」时自动迁移目录并清旧副本。
- **解压安全**：防 zip-slip 路径穿越、校验 `SKILL.md` 必须存在，非法即回滚。

## 六、设备连接生命周期 {#lifecycle}

连接治理三参数：握手读超时（10s）、空闲读超时（600s）、并发连接上限（默认 10000）；可选 `DEVICE_AUTH_SECRET` 二次鉴权。心跳按连接时长**四阶段退避**（15s → 30s → 60s → 5min），在线判定随策略动态计算：`now - lastSeen < onlineTimeoutFactor × 当前阶段期望间隔`（默认 2 倍系数）——Dashboard 不写死超时窗口，直接消费服务端状态字段。

<iframe src="/ai/xingchen/20-starwork-device-lifecycle.html" title="设备连接生命周期" loading="lazy" style="width:100%;height:700px;border:1px solid #e5e7eb;border-radius:8px;"></iframe>

<a href="/ai/xingchen/20-starwork-device-lifecycle.html" target="_blank">全屏打开 ↗</a>

> [!TIP]
> 断开时在线状态**实时写库**（不依赖心跳窗口）；心跳过期的半开连接由 worker 每 60s 扫描兜底置离线。断线期间本地配置保持不变，重连后重新握手 → `config.sync` 立即补上离线期间的新配置。

## 七、数据模型 {#data}

16 张业务表 + casbin 策略表，全部 GORM AutoMigrate 自动建表，**无外键约束**（运维规范），关联仅应用层维护：

| 域 | 表 | 说明 |
|---|---|---|
| 账号权限 | `users` / `roles` / `menus` / `role_menus` / `casbin_rule` | 菜单/按钮/接口三级权限；启动扫描路由自动同步「接口权限」条目 |
| 设备 | `client_users` / `client_devices` | 设备在线状态、`last_seen_at` 心跳窗口 |
| 配置下发 | `configs` / `config_deliveries` / `config_delivery_items` | 全局配置版本链 + 下发留痕（复合索引支撑按设备轮询待下发） |
| 包分发 | `skills` / `releases` | sha256 / platform / arch / 分类登记 |
| 模型配置 | `model_protocols` / `model_endpoints` / `models` / `config_models` | 三级结构：协议 L1 → 端点 L2 → 模型 L3；密钥等敏感字段 AES-256-GCM 加密入库、接口掩码返回 |
| 日志 | `app_logs` | 客户端错误日志上报；超 `ARCHIVE_KEEP_DAYS`（默认 7 天）自动归档为 `.log` 并删库（PG advisory lock 选主，多副本只跑一份） |

## 八、安全设计 {#security}

启动强校验（不满足直接退出）：`JWT_SECRET` ≥ 32 字符且非默认值；`DATA_ENC_KEY` 必须为 base64 的 32 字节。登录密码经 **RSA 公钥**加密传输（`/auth/public-key` 分发公钥，私钥服务端持有，生产务必挂载固定私钥文件）。设备侧可选 `AUTH` 二次鉴权；上传体积（`UPLOAD_MAX_MB`）与连接治理参数防滥用。**刻意不配置 CORS**：前后端同源部署于同一 Ingress host（`/starwork` 前端 SPA、`/starwork/api` 后端），浏览器只见一个 origin。

## 九、部署架构 {#deployment}

- **镜像**：`CGO_ENABLED=0` 静态二进制（amd64 / arm64 交叉编译），builder `golang:1.26.3` + runtime `alpine:3.12`；`vendor/` 入库离线构建（`-mod=vendor`），构建机无外网可编译。alpine 无 tzdata，二进制内嵌 `time/tzdata` 支撑 `DB_TIMEZONE=Asia/Shanghai`。
- **K8s（Helm）**：两端口同 Service（`tls` 8443 / `http` 8090，ClusterIP）；`/app/data`（上传包、归档、RSA 私钥）挂 **PVC**（nfs-client 动态供给，20Gi RWO）；8443 证书由 chart 建 Secret、流水线 `--set-file` 注入；`JWT_SECRET` / `DATA_ENC_KEY` 必须环境变量注入。
- **Ingress 三前缀**（最长前缀优先）：`/starwork/api` → 后端 8090；`/starwork` → 前端 SPA；`/admin/static` → 前端旧入口（301 到 `/starwork/`）。
- **CI/CD**：Jenkins 三环境流水线（sit / uat / prd：拉码 → `make release-image.amd64` 构图推 Harbor → Helm 部署）；敏感参数 sit 明文 / uat·prd 打码。

### 多副本就绪

设备长连接只在持有它的副本内存里，方案是**不让副本共享会话（那要 Redis），每个副本自扫 + DB 兜底**：

| 能力 | 做法 |
|---|---|
| 心跳 | 每副本每 10s 写自己在线设备的 `last_seen_at`（在线 = online + 35s 窗口） |
| 离线清理 | 每 60s 只把心跳过期的置离线（不做启动全表清零） |
| 下发补推 | 每副本每 2s 查**自己**设备的待下发项（`msg_id` 为空）并推送 |
| 权限 | casbin 策略逐条落库（不用全表重写）；每 60s `LoadPolicy` 对齐 |
| 定时任务 | 归档 / 下发巡检用 PG advisory lock 选主，只跑一份 |

> [!WARNING]
> 副本数目前仍建议 1：`nfs-client` 是 RWO（多副本需换 RWX）、`podAntiAffinity` 为 required + hostname（需 ≥2 可调度节点）、8443 的 L4 转发需确认非「单后端」。`helm uninstall` 会删除 chart 自建的 PVC。

## 十、已知约束与演进 {#issues}

1. `scripts/docker-deploy.sh` 依赖的 `docker-compose.yml` 尚未入库。
2. builder 镜像 `golang:1.26.3` tag 需 DevOps 在 Harbor 确认或新建。
3. `vendor/` 约 270 MB（含已不被引用的 sqlite 依赖），清理需外网重跑 `go mod vendor`，或改私有 proxy。
4. 8443 需运维单独放通（NodePort / LoadBalancer / TCP 转发其一），HTTP Ingress 转发不了 4 层 TLS。
5. Ingress 的 `serviceName` 与前端仓库耦合（`/admin/static` 指向 `star-work-cloud-front`），改 release 名需两边同步。
6. 路由前缀 `/staroffice` → `/starwork` 的旧库迁移已自动化：种子菜单就地更新、casbin 旧前缀策略清理、接口条目按新路由重建，旧库重启即自动跟上。

---

反链：

- [星辰智能](/docs/ai/xingchen-intelligence/)
