# HY Voice Room HY Voice Room 是语音房后端 monorepo。当前架构不再自研 IM 长连接服务:房间业务状态由 `room-service` 承担,客户端实时消息接入腾讯云 IM,服务端只负责签发 UserSig、创建房间群组、发送房间系统消息。 ## Architecture 核心边界: 任何开发,不需要历史兼容,目前只是在开发阶段 - `gateway-service` 是客户端 HTTP JSON 入口,负责鉴权、协议转换、签发腾讯云 IM UserSig,以及调用内部 gRPC 服务。 - `room-service` 是房间业务状态 owner,负责 Room Cell、麦位、房间 presence、本地礼物榜、送礼落房间、snapshot、command log、outbox、Redis lease,以及腾讯云 IM 群组/系统消息桥接。 - `wallet-service` 当前提供 `DebitGift` 送礼扣费语义,余额、流水和幂等记录落 MySQL;`room-service` 的 `SendGift` 同步调用它。 - `user-service` 当前承接登录、三方注册、密码、refresh session、用户主数据、默认短号和临时靓号基础能力。 - `activity-service` 当前承接每日任务、活动/系统消息基础能力和房间事件 consumer 边界;`room_outbox` 到 activity/audit 的实际消费链路按独立消费位点接入。 - `cron-service` 当前负责后台调度、任务运行记录,以及通过内部 gRPC 触发 owner service 批处理;它不直接拥有房间、账务、用户或活动业务状态。 - 腾讯云 IM 承担客户端长连接、群消息、公屏、单聊、离线/漫游和全球接入能力;本仓库不再部署 `im-service`。 - 多 App 共用同一套后端服务,入口由 `gateway-service` 通过包名或 `app_code` 解析租户;所有业务库表按 `app_code` 隔离,设计细节见 `docs/多App租户架构.md`。 协议边界: - 客户端命令面:`gateway-service` 暴露 HTTP JSON。 - 客户端实时面:腾讯云 IM SDK,客户端用 gateway 签发的 UserSig 登录。 - 服务内部调用:gRPC + protobuf,契约在独立 Go module `api`,业务代码通过 `hyapp.local/api/proto/...` 引用生成包。 - 房间外事件:protobuf outbox,当前先落 MySQL。 ## Time Model 项目全局时间模型固定为 UTC + epoch milliseconds: - 跨服务、数据库和 HTTP/gRPC 响应里的业务时间统一使用 Unix epoch milliseconds,字段名保持 `*_ms`,例如 `created_at_ms`、`expire_at_ms`、`start_at_ms`、`end_at_ms`、`next_refresh_at_ms`。 - 后端业务自然日统一按 UTC 切日。每日任务的 `task_day` 是 UTC `YYYY-MM-DD`,`next_refresh_at_ms` 是下一次 UTC 00:00:00 的毫秒时间戳。 - 统计和账务时间范围统一使用 `[start_ms, end_ms)`,也就是包含开始、不包含结束。查询“昨天充值多少”时,调用方必须先把“昨天”转换成明确的 UTC 毫秒范围,再交给后端查询。 - MySQL DSN 必须使用 `loc=UTC`。Docker 环境设置 `TZ=UTC`,MySQL 使用 `--default-time-zone=+00:00`,避免依赖服务器所在地或容器本地时区。 - 用户所在时区、设备时区或 IP 推断时区只能用于展示、风控或资料字段;不能作为结算、统计、任务刷新和幂等周期的业务切日来源。 ## Ports 本地和 Docker 编排统一使用 `13xxx` 端口: | Component | Port | Purpose | | ------------------ | --------------: | ----------------- | | `gateway-service` | `13000` | HTTP JSON | | `room-service` | `13001` | gRPC | | `wallet-service` | `13004` | gRPC | | `user-service` | `13005` | gRPC | | `activity-service` | `13006` | gRPC | | `cron-service` | `13007` | gRPC | | MySQL | `23306 -> 3306` | local Docker only | | Redis | `13379 -> 6379` | local Docker only | ## Run Locally Use Docker for local and test environments: ```bash make up ``` Service aliases: `gs/gateway`, `rs/room`, `ws/wallet`, `us/user`, `as/activity`, `cs/cron`, `mysql`, `redis`, plus full service names. Common commands: ```bash make test make build make admin-up make admin make addr make logs make rebuild rs make down ``` `make admin-up` 会先启动后台本地依赖 MySQL、Redis 和 `user-service`,再以前台进程启动 `server/admin`。依赖已经存在时,可以直接运行 `make admin`。 For direct host execution, each service reads its own `services//configs/config.yaml`; `room-service`、`user-service`、`wallet-service`、`activity-service` 和 `cron-service` 都要求 MySQL 可用。 ## Tencent IM 客户端获取腾讯云 IM 登录票据: ```text GET /api/v1/im/usersig Authorization: Bearer ``` 响应 `data` 包含: ```json { "sdk_app_id": 1400000000, "user_id": "10001", "user_sig": "xxx", "expire_at_ms": 1710000000000 } ``` 房间流程: - 客户端先经 gateway 调 `JoinRoom`,让用户进入 `room-service` presence。 - 客户端再用腾讯云 IM SDK 登录并加入 `room_id` 对应的群组。 - `gateway-service` 创建房间时从 access token 取当前 `user_id` 写入 `RequestMeta.actor_user_id`;`room-service` 由该 actor 收敛 owner 和默认 host,外部 CreateRoom 请求体不接收 `owner_user_id` 和 `host_user_id`。 - `X-Request-ID`/`request_id` 由后端生成,只做链路追踪;写操作的 `command_id` 由前端按用户动作生成,用于服务端幂等。 - `room-service` 在 `CreateRoom` 时通过腾讯云 IM REST API 创建同名群组。 - `room-service` 在上下麦、禁言、踢人、送礼等命令提交后,通过腾讯云 IM REST API 发送房间系统自定义消息。 - `gateway-service` 已提供 `/api/v1/tencent-im/callback`,支持腾讯云 IM 入群前和发言前回调,并分别回查 `VerifyRoomPresence` 和 `CheckSpeakPermission`。 腾讯云 IM 配置分别在: ```text services/gateway-service/configs/config.yaml services/room-service/configs/config.yaml services/room-service/configs/config.tencent.example.yaml ``` 本地联调腾讯云 IM 时,`gateway-service` 和 `room-service` 必须使用同一组 `sdk_app_id`、`secret_key`,`room-service` 还必须配置匹配的 `admin_identifier` 和区域 `endpoint`。 基础闭环实现顺序见: ```text docs/语音房基础闭环实现.md ``` ## Storage Model MySQL 是本地和线上运行的持久化底座: - `room-service`: `hyapp_room`,保存 room meta、snapshot、command log 和 outbox,业务行按 `app_code` 隔离。 - `user-service`: `hyapp_user`,保存 `apps` 注册表、用户主数据、注册资料快照、登录身份、session、默认短号、靓号租约和 host 关系事实,业务行按 `app_code` 隔离。 - `wallet-service`: `hyapp_wallet`,保存余额、账务流水、礼物价格和扣费幂等记录,业务行按 `app_code` 隔离。 - `activity-service`: `hyapp_activity`,保存任务定义、任务进度、奖励领取、活动状态、事件消费幂等和 activity outbox,业务行按 `app_code` 隔离。 - `cron-service`: `hyapp_cron`,保存后台任务定义、运行记录和调度锁;实际业务批处理仍由各 owner service 执行。 - `hyapp_admin`: 后台管理后端独立库,只保存后台账号、权限和审计;不要把后台审计耦合进 App 业务库。 `room-service` uses MySQL as the room recovery 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:{app_code + "\0" + room_id} room:node:{node_id} ``` Redis lease 只保存当前执行权和节点转发地址,不能作为恢复来源。恢复来源是 MySQL snapshot + command log。 ## Room Recovery A room is not permanently bound to one process. A node is only the current executor while its Redis lease is valid. 在多机部署下,gateway 可以把 room gRPC 请求打到 `room-service` 内网 CLB。非 owner 节点会读取 `room:route:*` 和 `room:node:*`,把房间命令、IM guard 和完整快照查询转发到当前 owner 实例。每个 room 节点必须配置唯一 `node_id` 和实例级 `advertise_addr`;`advertise_addr` 不能写负载均衡地址。无 Kubernetes 的部署步骤见 `docs/多机部署与无感发布.md`。 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. ## Project Layout ```text go.work local workspace for app backend, api contracts and admin backend api/ independent Go module, protobuf contracts and generated code pkg/ app backend shared packages, including Tencent IM signing/client server/admin/ independent admin backend module services/gateway-service/ HTTP entry service services/room-service/ Room Cell and durable room state services/wallet-service/ wallet DebitGift and ledger foundation services/user-service/ user service services/activity-service/ activity foundation and room event consumer boundary services/cron-service/ background scheduling and owner-service batch trigger ``` ## Current Limits - 腾讯云 IM 回调入口已经落在 gateway;线上仍必须在腾讯云 IM 控制台启用 `Group.CallbackBeforeApplyJoinGroup` 和 `Group.CallbackBeforeSendMsg`,并配置公网 callback URL 与鉴权 token。 - 前端对同一次用户写动作的 HTTP 重试必须复用同一个 `command_id`;`X-Request-ID` 不需要也不应该由前端传入。 - `wallet-service` App 运行路径使用 MySQL 扣费事务;本地需要先通过 compose initdb 建好 `hyapp_wallet`。 - `activity-service` 已承接每日任务和消息/活动基础能力;`room_outbox` 到 activity/audit 的实际消费链路仍需按独立消费位点补齐。 - `room-service` App 已启动 `room_outbox` 补偿 worker;该状态只表示腾讯云 IM 系统消息补偿结果,activity/audit 后续必须使用独立消费位点。 - JWT、腾讯云 IM secret 等本地配置只能用开发值,线上必须走安全配置源。