StarWork 云控服务端技术架构
依据 star-work-cloud 仓库源码与工程内文档(README.md / PROTOCOL.md)编制。文中四张图表均为交互式:支持缩放、语义视图、关系追踪与 PNG/SVG 导出。
一、项目概览
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 选主)。
二、总体架构
单进程内三个协作者:TLS 长连接服务(握手 / AUTH / 信封协议 / 会话表)、HTTP API(JWT + casbin 鉴权的 10 组管理接口)、后台巡检 Worker(心跳、补推、归档、选主)。入口分两条:设备走 L4 直达(NodePort / LB 的 TCP 8443,HTTP Ingress 无法转发 4 层 TLS),管理端走 Ingress 的 /starwork/api 前缀;前端 SPA 与后端同源部署,刻意不配置 CORS。
探针三件套(startup / readiness / liveness)全部打 8090 的 /healthz——8443 是裸 TLS 长连接,kubelet 探不了。/healthz 只回 {"status":"ok"} 不查库,库挂了不会因此被摘流量。
三、统一信封协议(Protocol v1)
一套信封覆盖上报 / 下发 / 配置同步 / 心跳,传输与业务分离:信封(怎么传)一次写死,业务能力以 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 压缩)。
四、配置同步与定向下发
配置是云控的自举基座——客户端连接地址本身就在配置里,因此「地址可远程更新」是第一需求。服务端维护全局配置表(configs:每份配置独立 configId 与单调递增版本号,不按用户区分),客户端本地原子持久化(cloud-config.json),启动 / 重连必执行 config.sync 对账。
三个核心设计:
- 双通道兜底:
config.push(服务端定向推送,在线即时生效)+config.sync(客户端拉取,离线攒下的新配置上线即补)。配置类消息靠 sync 全量对账兜底,离线队列只保留给一次性指令(重启、升级等)。 - 全量下发 + 按字段合并:服务端始终下发完整配置对象,客户端按顶层字段合并(收到覆盖、未收到保留)。自带自愈——任何丢失或本地文件损坏,下次连接全量对账即恢复;配合原子写(临时文件 + rename)与版本守卫(旧版本拒绝覆盖新版本)。
- 定向匹配:
to描述支持platforms/deviceIds/minVersion的 AND 组合——同一账号的 mac 更新只命中 mac 设备。每次下发落config_deliveries+ 逐设备明细(msg_id关联 ack,状态 pending / delivering / done / failed),删除下发记录即撤销。
五、技能包与版本包分发
技能(zip,内含 SKILL.md)与版本安装包共用同一套 TLS 分片分发套路:base64 分片(128KB 原始字节/片)、共用命令 id 幂等、收齐重组后与元信息 sha256 强比对。设备端不暴露公网下载地址,HTTP 下载接口仅限管理端(JWT)。
关键防线与落地策略:
- 版本更新走配置携带:
release表(version / platform / arch / sha256)绑定到配置,随config.push/config.sync全量下发;客户端做平台/架构匹配(不符不弹窗)、版本比较(不高于当前忽略)、单会话去重后才弹确认窗。 - 技能按
category六类落盘:global(全部编辑器)/docs/sheets/slides/pdf/markdown各自目录,非白名单取值兜底为global;同目录原子替换 +.cloud-skill.json登记,装完即用无需重启;后台改分类后,已装设备「更新」时自动迁移目录并清旧副本。 - 解压安全:防 zip-slip 路径穿越、校验
SKILL.md必须存在,非法即回滚。
六、设备连接生命周期
连接治理三参数:握手读超时(10s)、空闲读超时(600s)、并发连接上限(默认 10000);可选 DEVICE_AUTH_SECRET 二次鉴权。心跳按连接时长四阶段退避(15s → 30s → 60s → 5min),在线判定随策略动态计算:now - lastSeen < onlineTimeoutFactor × 当前阶段期望间隔(默认 2 倍系数)——Dashboard 不写死超时窗口,直接消费服务端状态字段。
断开时在线状态实时写库(不依赖心跳窗口);心跳过期的半开连接由 worker 每 60s 扫描兜底置离线。断线期间本地配置保持不变,重连后重新握手 → config.sync 立即补上离线期间的新配置。
七、数据模型
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 选主,多副本只跑一份) |
八、安全设计
启动强校验(不满足直接退出):JWT_SECRET ≥ 32 字符且非默认值;DATA_ENC_KEY 必须为 base64 的 32 字节。登录密码经 RSA 公钥加密传输(/auth/public-key 分发公钥,私钥服务端持有,生产务必挂载固定私钥文件)。设备侧可选 AUTH 二次鉴权;上传体积(UPLOAD_MAX_MB)与连接治理参数防滥用。刻意不配置 CORS:前后端同源部署于同一 Ingress host(/starwork 前端 SPA、/starwork/api 后端),浏览器只见一个 origin。
九、部署架构
- 镜像:
CGO_ENABLED=0静态二进制(amd64 / arm64 交叉编译),buildergolang:1.26.3+ runtimealpine:3.12;vendor/入库离线构建(-mod=vendor),构建机无外网可编译。alpine 无 tzdata,二进制内嵌time/tzdata支撑DB_TIMEZONE=Asia/Shanghai。 - K8s(Helm):两端口同 Service(
tls8443 /http8090,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 选主,只跑一份 |
副本数目前仍建议 1:nfs-client 是 RWO(多副本需换 RWX)、podAntiAffinity 为 required + hostname(需 ≥2 可调度节点)、8443 的 L4 转发需确认非「单后端」。helm uninstall 会删除 chart 自建的 PVC。
十、已知约束与演进
scripts/docker-deploy.sh依赖的docker-compose.yml尚未入库。- builder 镜像
golang:1.26.3tag 需 DevOps 在 Harbor 确认或新建。 vendor/约 270 MB(含已不被引用的 sqlite 依赖),清理需外网重跑go mod vendor,或改私有 proxy。- 8443 需运维单独放通(NodePort / LoadBalancer / TCP 转发其一),HTTP Ingress 转发不了 4 层 TLS。
- Ingress 的
serviceName与前端仓库耦合(/admin/static指向star-work-cloud-front),改 release 名需两边同步。 - 路由前缀
/staroffice→/starwork的旧库迁移已自动化:种子菜单就地更新、casbin 旧前缀策略清理、接口条目按新路由重建,旧库重启即自动跟上。