# collab-go 实时协作文档编辑的 WebSocket 网关。负责 JWT 鉴权并将协作流量代理到 `collab-core`。 ## 概述 `collab-go` 是一个 Go 服务,作为 Yoresee Doc 协作编辑平台中浏览器 WebSocket 连接的入口。主要职责: - JWT 令牌校验(HS256/384/512) - 通过 gRPC 调用后端校验文档是否存在 - 与 `collab-core`(Node.js Yjs 服务)之间的双向 WebSocket 代理 - 健康检查与优雅停机 ## 连接流程 ``` 浏览器 --ws://host/ws/doc/{docId}?token={jwt}--> collab-go --> collab-core ``` 1. 浏览器连接 `/ws/doc/{docId}`,JWT 通过 query 参数传递 2. `collab-go` 校验 JWT 令牌 3. `collab-go` 调用后端 gRPC `DocumentService.GetDocumentSettings()` 验证文档存在 4. 成功后将 HTTP 升级为 WebSocket 5. 拨号连接 `collab-core`:`{COLLAB_CORE_URL}/doc-{docId}` 6. 在浏览器与 `collab-core` 之间双向代理消息 ## 前置依赖 ### Proto 桩代码 `pkg/gen/` 包含生成的 protobuf 代码,已被 gitignore。构建前必须先生成: ```bash # 推荐:使用父仓库脚本 bash ../deploy/script/gen_proto.sh # 或手动 protoc mkdir -p pkg/gen protoc -I ../proto \ --go_out=pkg/gen --go_opt=paths=source_relative \ --go-grpc_out=pkg/gen --go-grpc_opt=paths=source_relative \ ../proto/yoresee_doc/v1/yoresee_doc.proto ``` 所需工具: - `protoc-gen-go`:`go install google.golang.org/protobuf/cmd/protoc-gen-go@latest` - `protoc-gen-go-grpc`:`go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest` ### Go Go 1.24 或更高版本。 ## 构建与运行 ```bash # 构建 go build -o collab-go main.go # 运行 ./collab-go ``` 或直接运行: ```bash go run main.go ``` 默认端口:`1234` ## 配置 环境变量: | 变量 | 默认值 | 必填 | 说明 | |------|--------|------|------| | `ADDR` | `:1234` | 否 | 监听地址 | | `JWT_SECRET` | _(空)_ | 是 | JWT 签名密钥。为空 = 接受任何可解析的 JWT(开发模式) | | `COLLAB_CORE_URL` | `ws://collab-core:1234` | 否 | `collab-core` 的 WebSocket 地址 | | `BACKEND_GRPC_ADDR` | `backend:9090` | 否 | 后端 gRPC 地址 | | `INTERNAL_RPC_KEY` | _(空)_ | 否 | 作为 `x-internal-key` gRPC 元数据发送,用于内部鉴权 | ### JWT 模式 **生产模式**(设置了 `JWT_SECRET`): - 使用 HS256/384/512 校验 JWT 签名 - 令牌必须有效且签名正确 **开发模式**(`JWT_SECRET` 为空): - 解析 JWT 但不校验签名 - 接受任何结构有效的 JWT - 适用于本地开发时无共享密钥的场景 ## API 端点 ### WebSocket ``` GET /ws/doc/{docId}?token={jwt} ``` - 成功后升级为 WebSocket - JWT 无效返回 `401 Unauthorized` - 文档不存在返回 `404 Not Found` - 后端 gRPC 不可达返回 `503 Service Unavailable` ### 健康探针 ``` GET /health GET /readyz GET /livez ``` 均返回 JSON: ```json { "status": "ok", "detail": "", "backend": "ok" } ``` - `/health`:始终返回 `200 OK`。包含后端状态(如可用)。 - `/readyz`:停机 draining 阶段或后端不健康时返回 `503 Service Unavailable`。 - `/livez`:始终返回 `200 OK`。用于存活检查。 ## 架构 ``` collab-go/ ├── auth/ JWT 校验 ├── config/ 基于环境变量的配置 ├── handler/ WebSocket 处理器:鉴权 + 文档校验 + 代理 ├── health/ 健康探针处理器 ├── proxy/ 双向 WebSocket 代理 ├── pkg/gen/ 生成的 protobuf 桩代码(gitignored) └── main.go 入口文件 ``` ### 核心组件 **auth/** - `Authenticator` 校验 JWT 令牌 - 支持 HS256、HS384、HS512 签名方式 - 密钥为空时降级为不校验签名的解析 **config/** - 使用 `caarlos0/env/v11` 解析环境变量 - 所有配置通过环境变量传入 **handler/** - `WSHandler` 实现 WebSocket 连接的 `http.Handler` - 从 `?token=` query 参数获取 JWT - 调用后端 gRPC 校验文档是否存在 - 升级连接后委托给代理 **health/** - 实现 Kubernetes 风格的健康探针 - readiness 检查后端 gRPC 健康状态 - 支持优雅停机 draining **proxy/** - `Proxy` 管理与 `collab-core` 的 WebSocket 连接 - `ProxyWS` 启动两个 goroutine 进行双向消息转发 - 连接路径:`collab-core` 上的 `/doc-{docId}` ## 优雅停机 收到 `SIGINT` 或 `SIGTERM` 信号后: 1. 健康检查器进入 draining 模式(readiness 返回 `not_ready`) 2. 停止接受新连接 3. 等待已有连接关闭,最长 15 秒 4. 退出 负载均衡器应在 `/readyz` 返回非 200 时停止路由流量。 ## Docker ### 开发环境 ```bash docker build -t collab-go-dev -f Dockerfile . docker run -p 1234:1234 --env-file .env collab-go-dev ``` 运行 `go run main.go`(无构建缓存)。 ### 生产环境 ```bash # 从父目录构建 docker build -t collab-go -f Dockerfile.prod .. ``` 注意:`Dockerfile.prod` 期望构建上下文在父目录(`..`),因为它需要复制: - `collab-go/` 源码 - `proto/` 用于 protobuf 生成 - 在容器内部生成 proto 桩代码 多阶段构建: 1. Builder 阶段:生成 proto 桩代码,编译二进制 2. Runner 阶段:将二进制复制到最小化 Alpine 镜像 ## 协作栈集成 `collab-go` 是双服务协作栈的一部分: 1. **collab-core**(Node.js):在内存 + Redis 中维护 Yjs 文档状态 2. **collab-go**(Go):WebSocket 网关,鉴权,代理 网关模式实现了职责分离: - 鉴权与授权(Go,快速) - 文档状态管理(Node.js,Yjs 运行时) - 水平扩展(多个网关实例,每个文档一个 core) ## 日志 所有日志使用 `collab-gateway` 前缀: ``` collab-gateway listening on :1234 collab-gateway unauthorized path=/ws/doc/abc remote=127.0.0.1:12345 err=... collab-gateway doc check failed docID=abc err=... ``` ## 测试 当前无测试。可通过以下命令验证: ```bash go vet ./... go build ./... ``` ## 相关文档 - 父项目:`../AGENTS.md` - 完整系统文档:`../../docs/README.md` - 中文版本:`../../docs/README_zh.md`