# HY Voice Room HY Voice Room 是一个语音房后端 monorepo。当前重点不是做通用 IM 平台,而是把语音房的实时业务闭环跑起来:HTTP 命令入口、房间 Room Cell、WebSocket 消息投递、MySQL 状态恢复、Redis 房间路由。 ## Architecture 核心边界: - `gateway-service` 是客户端 HTTP JSON 入口,负责鉴权、协议转换和调用 `room-service`。它不持有房间状态,也不终止 WebSocket。 - `room-service` 是房间业务状态 owner,负责 Room Cell、麦位、房间 presence、本地礼物榜、送礼落房间、snapshot、command log、outbox、Redis lease 路由。 - `im-service` 是连接和消息 owner,负责 WebSocket、房间频道订阅、公屏、房间系统消息、单聊、ACK、最近房间历史。 - `wallet-service` 当前提供送礼扣费 stub,`room-service` 的 `SendGift` 同步调用它。 - `user-service` 和 `activity-service` 当前是独立进程骨架,后续分别承接用户主数据和活动消费。 外部协议和内部协议: - 客户端命令面:`gateway-service` 暴露 HTTP JSON。 - 客户端实时面:`im-service` 暴露 WebSocket JSON。 - 服务内部调用:gRPC + protobuf,契约在 `api/proto`。 - 房间外事件:protobuf outbox,当前先落 MySQL。 ## Ports 本地和 Docker 编排统一使用 `13xxx` 端口: | Component | Port | Purpose | | --- | ---: | --- | | `gateway-service` | `13000` | HTTP JSON | | `room-service` | `13001` | gRPC | | `im-service` | `13002` | HTTP/WebSocket | | `im-service` | `13003` | gRPC | | `wallet-service` | `13004` | gRPC | | `user-service` | `13005` | gRPC | | `activity-service` | `13006` | gRPC | | MySQL | `13306 -> 3306` | local Docker only | | Redis | `13379 -> 6379` | local Docker only | ## Run Locally Use Docker for local and test environments: ```bash make up ``` `make up` starts the stack in detached mode and prints host-local, host-LAN, and Docker-network addresses for every service port. Follow logs: ```bash make logs ``` Print addresses again without restarting services: ```bash make addr ``` Operate one service: ```bash make up rs make rebuild rs make restart rs make stop rs make logs rs make ps rs ``` Service aliases: `gs/gateway`, `rs/room`, `is/im`, `ws/wallet`, `us/user`, `as/activity`, `mysql`, `redis`. Stop services: ```bash make down ``` The compose stack starts MySQL and Redis. MySQL schema is initialized from: ```text services/room-service/deploy/mysql/initdb/001_room_service.sql ``` For direct host execution, each service reads its own `services//configs/config.yaml`. ## Development Commands ```bash make proto make test make build make up make addr make logs make ps make up rs make rebuild rs make restart rs make stop rs make logs rs make ps rs make down ``` `make proto` requires `protoc`, `protoc-gen-go`, and `protoc-gen-go-grpc`. ## Storage Model `room-service` uses MySQL as the durable source: - `rooms`: room metadata. - `room_snapshots`: latest room state snapshot. - `room_command_log`: replayable command log for recovery. - `room_outbox`: async event records. `room-service` uses Redis for current room ownership: ```text room:route:{room_id} ``` The Redis value stores `room_id`, `node_id`, `lease_token`, and `expires_at_ms`. Lease acquisition is atomic through Lua in `internal/router/redis_directory.go`. ## Room Recovery A room is not permanently bound to one process. A node is only the current executor while its Redis lease is valid. Recovery path: 1. A request reaches a node that can own the room. 2. The node acquires or renews `room:route:{room_id}`. 3. If the local Room Cell does not exist, it loads the latest MySQL snapshot. 4. It replays command log records after the snapshot version. 5. It installs a local Room Cell and continues processing. This is single-active recovery, not hot standby. Short retry/reconnect windows are expected during node failure. ## Cloud Deployment Local Docker uses: ```text services/room-service/configs/config.docker.yaml ``` Tencent Cloud deployment should follow: ```text services/room-service/configs/config.tencent.example.yaml ``` Set the real Tencent Cloud MySQL DSN, Redis address, Redis password, and internal service DNS names. Keep `mysql_auto_migrate: false` online; schema changes should run through a controlled migration process. Deployment lifecycle requirements are documented in: ```text docs/health-checks.md docs/graceful-shutdown.md ``` ## API Entry Points Gateway HTTP routes: - `POST /api/v1/rooms/create` - `POST /api/v1/rooms/join` - `POST /api/v1/rooms/leave` - `POST /api/v1/rooms/mic/up` - `POST /api/v1/rooms/mic/down` - `POST /api/v1/rooms/mic/change` - `POST /api/v1/rooms/user/mute` - `POST /api/v1/rooms/user/kick` - `POST /api/v1/rooms/gift/send` Gateway business responses use one envelope: ```json { "code": "OK", "message": "ok", "request_id": "req_xxx", "data": {} } ``` `request_id` is generated by gateway unless the client sends `X-Request-ID`. The same value is propagated to internal gRPC `RequestMeta.request_id`. IM WebSocket path: ```text ws://127.0.0.1:13002/ws ``` The first WS frame must be `auth`; later frames include `subscribe_room`, `unsubscribe_room`, `room_chat_send`, `peer_send`, `ack`, and `pull_history`. ## Project Layout ```text api/proto/ protobuf contracts pkg/ small shared packages services/gateway-service/ HTTP entry service services/room-service/ Room Cell and durable room state services/im-service/ WebSocket and message delivery services/wallet-service/ wallet stub services/user-service/ user stub services/activity-service/ activity stub ``` ## Current Limits - `wallet-service`, `user-service`, and `activity-service` are still stubs. - `room-service` outbox is durable, but external activity consumption is not implemented yet. - `im-service` keeps message/session state in memory; scaling it across nodes still needs shared online routing or a delivery bus. - JWT secrets in local config are development values only.