2026-07-06 11:10:19 +08:00
2026-07-05 14:55:37 +08:00
2026-06-12 19:40:21 +08:00
2026-07-06 11:10:19 +08:00
2026-07-04 01:53:10 +08:00
2026-07-05 01:55:03 +08:00
2026-07-06 11:10:19 +08:00
2026-07-06 11:10:19 +08:00
2026-06-18 22:41:52 +08:00
2026-06-28 15:11:56 +08:00
2026-05-16 08:12:42 +08:00
2026-07-03 14:43:43 +08:00
2026-07-05 23:47:00 +08:00
2026-06-28 15:11:56 +08:00
2026-07-05 01:55:03 +08:00
2026-07-05 01:55:03 +08:00
2026-05-12 09:53:20 +08:00
2026-07-02 10:53:44 +08:00
2026-06-28 15:11:56 +08:00
2026-06-26 23:21:22 +08:00

HY Voice Room

HY Voice Room 是语音房后端 monorepo。当前架构不再自研 IM 长连接服务:房间业务状态由 room-service 承担,客户端实时消息接入腾讯云 IM服务端负责签发 UserSig、房间群消息桥接以及通过 notice-service 异步投递用户私有通知。

Architecture

核心边界:

  • gateway-service 是客户端 HTTP JSON 入口,负责鉴权、协议转换、签发腾讯云 IM UserSig以及调用内部 gRPC 服务。
  • room-service 是房间业务状态 owner负责 Room Cell、麦位、房间 presence、本地礼物榜、送礼落房间、语音房火箭、snapshot、command log、outbox、Redis lease、RocketMQ room outbox relay以及腾讯云 IM 群组/系统消息桥接。
  • wallet-service 当前提供 DebitGift 送礼扣费语义,余额、流水和幂等记录落 MySQLroom-serviceSendGift 同步调用它。
  • user-service 当前承接登录、三方注册、密码、refresh session、用户主数据、默认短号和临时靓号基础能力。
  • activity-service 当前承接每日任务、活动/系统消息基础能力和房间事件 consumer可通过 room outbox direct gRPC 或 RocketMQ hyapp_room_outbox 独立 consumer group 消费房间事实。
  • cron-service 当前负责后台调度、任务运行记录,以及通过内部 gRPC 触发 owner service 批处理;它不直接拥有房间、账务、用户或活动业务状态。
  • notice-service 当前消费 wallet_outbox 的余额变更事件和 room_outbox 的踢人事件,异步投递腾讯云 IM 单聊私有通知;房间群消息和群成员控制仍由 room-service 的房间 IM bridge 处理MQ 模式下通过独立 consumer group 解耦。
  • statistics-service 当前消费 user/wallet/room/game outbox 的 RocketMQ 事实,写入本服务 MySQL 聚合表,并通过内部 HTTP 查询 API 支撑后台数据大屏;它不拥有注册、账务、房间或游戏业务事实。
  • 腾讯云 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/JSON outbox 先落 MySQL生产可由 RocketMQ fanout 到 activity、notice、IM bridge、statistics 和后续 push/inbox消费端按 event_id 幂等。
  • outbox 读取权只属于 owner service。room-service 读取 room_outboxwallet-service 读取 wallet_outboxuser-service 读取 user_outboxgame-service 读取 game_outbox 并发布 RocketMQ其他服务不能直连 owner 数据库扫描 outbox。
  • 下游消费 owner outbox 必须使用本服务独立 RocketMQ consumer group并在本服务库内用幂等表或唯一键记录已消费 event_id;消费位点不能写回或复用 owner service 的 outbox 状态。
  • 后台统计查询面:server/adminstatistics-service 内部 HTTP APIgateway-service 是 App 入口,除非有明确 App 产品需求,否则不暴露后台数据大屏统计。

App 系统/活动消息:

  • Flutter 消息页固定展示腾讯云 IM 用户消息、后端系统消息和后端活动消息三个入口;系统/活动页通过 gateway-service 读取 activity-service 的 inbox只按当前客户端能力展示标题、摘要/正文和时间。
  • activity-service/internal/service/message 是系统/活动消息 owner。单人消息使用 CreateSystemNotice / CreateActivityNotice,全服或范围通知使用 CreateNoticeFanout 创建 message_fanout_jobs,由 cron 触发 fanout worker 分批写入 user_inbox_messages
  • 活动业务发通知时传 producer_event_idcommand_id 作为幂等键,message_type 只允许 systemactivity;跳转参数使用已有 action_type 白名单,例如 activity_detailroom_detailuser_profileapp_h5
  • server/admin 的运营管理/全服通知只通过 MessageInboxService.CreateFanoutJob 创建后台 fanout 命令,不直接写 hyapp_activity 表,也不在 HTTP 请求里同步遍历用户。

Time Model

项目全局时间模型固定为 UTC + epoch milliseconds。除客户端展示外App 后端所有按天、按周、按月归档或结算的业务口径都按 UTC不按中国时区、服务器本地时区、用户设备时区或 IP 推断时区切分。

  • 跨服务、数据库和 HTTP/gRPC 响应里的业务时间统一使用 Unix epoch milliseconds字段名保持 *_ms,例如 created_at_msexpire_at_msstart_at_msend_at_msnext_refresh_at_ms
  • 后端业务自然日统一按 UTC 00:00:00 切日。task_dayreward_daycheckin_daystat_daybudget_day 都是 UTC YYYY-MM-DDnext_refresh_at_ms 是下一次 UTC 00:00:00 的毫秒时间戳。
  • App 每日任务和七日签到按 UTC 日刷新。任务事件归属到 occurred_at_ms 所在 UTC 日;七日签到连续性只比较今天和昨天的 UTC 日期。
  • 日榜、周榜、月榜按 UTC 周期计算。today/day/daily 是 UTC 当天,week/weekly 从 UTC 周一 00:00 开始,month/monthly 从 UTC 月初 00:00 开始;后台用户送礼榜按 [周期开始, 当前 UTC 时间) 查询,房间礼物榜 Redis key 也按 UTC 周期开始时间生成。
  • 统计和账务时间范围统一使用 [start_ms, end_ms),也就是包含开始、不包含结束。查询“昨天充值多少”时,调用方必须先把“昨天”转换成明确的 UTC 毫秒范围,再交给后端查询。
  • 定时任务由 cron-serviceinterval 轮询不使用本地时区表达“几点运行”。任务运行记录、lease 和传给 owner service 的 now_ms 都是 UTC epoch milliseconds。
  • 工资和结算周期按 UTC 月锚定。主播工资周期键 cycle_key 是 UTC YYYY-MM;主播日结、半月结和月底清算由 cron 触发后在 wallet-service 内按这个周期键幂等结算。团队 BD/Admin 自动结算在后台按 UTC 每月 5 日判断,结算上一个 UTC 自然月。
  • MySQL DSN 必须使用 loc=UTC。Docker 环境设置 TZ=UTCMySQL 使用 --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
game-service 13008 gRPC
notice-service 13009 gRPC
statistics-service 13010 internal query HTTP
statistics-service 13110 health HTTP
MySQL 23306 -> 3306 local Docker only
Redis 13379 -> 6379 local Docker only

Statistics

统计系统的硬边界是“不查全表,不回查 owner 明细库”。数据大屏只能查询 statistics-service 的聚合表;任何新指标都必须先定义事件事实、聚合表字段和 UTC 时间范围,再接入查询接口。

事实来源:

  • user-service: UserRegistered,用于新增用户和留存 cohort。
  • wallet-service: WalletRechargeRecorded,用于总充值、首充、付费用户、币商/MifaPay/Google 渠道分桶、ARPU、ARPPU 和 UP 值。
  • room-service: RoomUserJoined 用于进房活跃,RoomGiftSent 用于礼物消耗、幸运礼物流水和幸运礼物付费用户;国家维度来自事件中的 visible_region_id
  • game-service: GameOrderSettleddebit/bet 计投注流水,credit/payout 计返奖,refund/reverse 计退款;游戏利润为 投注 - 返奖 - 退款

聚合读模型:

  • statistics_event_consumption: 本服务消费幂等表,按 app_code + source + event_id 去重。
  • stat_app_day_country: App/国家/UTC 日总览,保存新增、活跃、付费、充值渠道、礼物、幸运礼物、游戏总览。
  • stat_user_day_activity: 用户 UTC 日活跃事实,用于 DAU 和留存 join。
  • stat_user_registration: 注册 cohort用于次留、7 留和 30 留。
  • stat_recharge_day_payers: 充值付费用户去重用于付费人数、ARPPU 和 UP 值。
  • stat_lucky_gift_day_payers: 幸运礼物付费用户去重。
  • stat_game_day_country / stat_game_day_players: 游戏维度流水、返奖、退款、玩家数和排行。

查询契约:

GET /internal/v1/statistics/overview?app_code=lalu&start_ms=<UTC ms>&end_ms=<UTC ms>&country_id=<optional>

start_ms/end_ms 统一使用 [start_ms, end_ms)。返回值只来自聚合表;ARPU = recharge_usd_minor / active_usersARPPU = recharge_usd_minor / paid_users,当前 up_value_usd_minor 与 ARPPU 同口径。产品若要改变 UP 值定义,必须先改字段名、契约和边界测试。

Run Locally

Use Docker for local and test environments:

make up

Service aliases: gs/gateway, rs/room, ws/wallet, us/user, as/activity, cs/cron, game/games, ns/notice, mysql, redis, plus full service names.

Common commands:

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/<service>/configs/config.yaml; room-serviceuser-servicewallet-serviceactivity-servicecron-servicegame-servicenotice-service 都要求 MySQL 可用。

Tencent IM

客户端获取腾讯云 IM 登录票据:

GET /api/v1/im/usersig
Authorization: Bearer <access_token>

响应 data 包含:

{
  "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_idroom-service 由该 actor 收敛 owner外部 CreateRoom 请求体不接收 owner_user_id
  • X-Request-ID/request_id 由后端生成,只做链路追踪;写操作的 command_id 由前端按用户动作生成,用于服务端幂等。
  • room-serviceCreateRoom 成功后写 room_outbox,由 outbox worker 异步创建或补偿同名腾讯云 IM 群组。
  • room-service 在上下麦、禁言、踢人、送礼、火箭进度和发射等命令提交后只写房间状态、command log 和 outbox腾讯云 IM 房间系统自定义消息由 outbox worker direct 投递,或由 IM bridge consumer 从 RocketMQ 消费后投递。
  • notice-service 消费钱包余额变更 outbox 和房间踢人 outbox向单个用户发送 wallet_noticeroom_notice 私有自定义消息owner service 不直接调用腾讯云 IM 单聊。
  • gateway-service 已提供 /api/v1/tencent-im/callback,支持腾讯云 IM 入群前和发言前回调,并分别回查 VerifyRoomPresenceCheckSpeakPermission

腾讯云 IM 配置分别在:

services/gateway-service/configs/config.yaml
services/room-service/configs/config.yaml
services/room-service/configs/config.tencent.example.yaml
services/notice-service/configs/config.yaml
services/notice-service/configs/config.tencent.example.yaml

本地联调腾讯云 IM 时,gateway-serviceroom-servicenotice-service 必须使用同一组 sdk_app_idsecret_key;所有服务端 REST 调用方还必须配置匹配的 admin_identifier 和区域 endpoint

基础闭环实现顺序见:

docs/语音房基础闭环实现.md
docs/notice-service架构.md
docs/flutter对接/Flutter App 通知与钱包余额对接.md

RocketMQ

本地默认不依赖 RocketMQroom-service 可使用 outbox_worker.publish_mode=direct 直连腾讯云 IM publisher 和 activity gRPC publisher。线上推荐启用腾讯云 RocketMQ并把 owner outbox 统一发布到消息总线,下游服务各自 consumer group 消费。

规则:

  • room_outbox 只由 room-service 读取和发布;wallet_outbox 只由 wallet-service 读取和发布;user_outbox 只由 user-service 读取和发布;game_outbox 只由 game-service 读取和发布。
  • statistics-serviceactivity-servicenotice-servicecron-service 等非 owner service 不能配置 owner 数据库 DSN 或扫描其他服务 outbox 表。
  • 每个业务消费者使用独立 consumer group。即使消费同一个 topic不同业务也不能共用 group否则会互相抢消息。
  • 每个消费者在自己的库内维护幂等事实,例如按 app_code + event_id 或业务唯一键建唯一约束RocketMQ 的消费位点只解决投递进度,不替代业务幂等。
  • owner service 的 MySQL outbox 仍是可靠事实源。RocketMQ 发布失败由 owner outbox worker 补偿重试;下游处理失败由各自 consumer group 重投和本地幂等表收敛。

当前 topic 和 consumer group

Topic Producer Consumer Group 用途
hyapp_room_outbox hyapp-room-outbox-producer hyapp-activity-room-outbox activity-service 消费 RoomGiftSentRoomRocketIgnited 等房间事实
hyapp_room_outbox hyapp-room-outbox-producer hyapp-notice-room-outbox notice-service 消费 RoomUserKicked 等私有通知事实
hyapp_room_outbox hyapp-room-outbox-producer hyapp-room-im-bridge room-service IM bridge 消费房间群系统消息和群成员控制事实
hyapp_room_outbox hyapp-room-outbox-producer hyapp-user-mictime-room-outbox user-service 消费 RoomMicChanged 并更新用户麦时读模型
hyapp_room_outbox hyapp-room-outbox-producer hyapp-statistics-room-outbox statistics-service 消费 RoomGiftSentRoomUserJoined 并更新聚合表
hyapp_user_outbox hyapp-user-outbox-producer hyapp-statistics-user-outbox statistics-service 消费 UserRegistered 并更新注册 cohort
hyapp_wallet_outbox hyapp-wallet-outbox-producer hyapp-user-invite-wallet-outbox user-service 消费 WalletRechargeRecorded 并更新邀请有效充值
hyapp_wallet_outbox hyapp-wallet-outbox-producer hyapp-activity-first-recharge-wallet-outbox activity-service 消费 WalletRechargeRecorded 并处理首充奖励
hyapp_wallet_outbox hyapp-wallet-outbox-producer hyapp-activity-red-packet-wallet-outbox activity-service 消费 WalletRedPacketCreated 并生成区域飘屏 outbox
hyapp_wallet_outbox hyapp-wallet-outbox-producer hyapp-notice-wallet-outbox notice-service 消费余额、账务等私有通知事实
hyapp_wallet_outbox hyapp-wallet-outbox-producer hyapp-statistics-wallet-outbox statistics-service 消费 WalletRechargeRecorded 并更新充值聚合
hyapp_game_outbox hyapp-game-outbox-producer hyapp-statistics-game-outbox statistics-service 消费 GameOrderSettled 并更新游戏聚合
hyapp_room_rocket_launch hyapp-room-rocket-launch-producer hyapp-room-rocket-launch 火箭点火后到点唤醒 room-service 发射命令

RocketMQ 不拥有业务状态。room_outboxwallet_outboxuser_outboxgame_outbox 仍是各 owner service MySQL 内的可靠事实源MQ 发布失败时 outbox worker 退避重试,到达重试上限后进入死信。火箭延迟消息只负责唤醒,到点后 room-service 会重新校验 rocket_id、等级、launch_at_ms、状态和 UTC 重置边界。

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 执行。
  • game-service: hyapp_game,保存游戏平台、房间游戏会话和游戏交易映射。
  • notice-service: hyapp_notice,保存外部通知投递位点、重试锁和死信状态;业务事实仍留在 owner service outbox。
  • statistics-service: hyapp_statistics,保存事件消费幂等和数据大屏聚合读模型;禁止把 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:

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_addradvertise_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

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
services/game-service/     game session and wallet integration
services/notice-service/   async user notice delivery workers
services/statistics-service/ data dashboard event consumers and aggregate query API

Current Limits

  • 腾讯云 IM 回调入口已经落在 gateway线上仍必须在腾讯云 IM 控制台启用 Group.CallbackBeforeApplyJoinGroupGroup.CallbackBeforeSendMsg,并配置公网 callback URL 与鉴权 token。
  • 前端对同一次用户写动作的 HTTP 重试必须复用同一个 command_idX-Request-ID 不需要也不应该由前端传入。
  • wallet-service App 运行路径使用 MySQL 扣费事务;本地需要先通过 compose initdb 建好 hyapp_wallet
  • notice-service 本地默认不启用腾讯云 IM worker线上启用钱包通知或房间通知 RocketMQ consumer 时,必须同时启用 tencent_im.enabled=true,且不能直连 wallet/room 数据库扫描 outbox。
  • activity-service 已承接每日任务、消息/活动基础能力和 room outbox consumer本地可走 direct gRPC线上推荐走 RocketMQ hyapp_room_outbox
  • room-service App 已启动 room_outbox 补偿 workerdirect 模式表示直连 publisher 完成,mq 模式表示事件已发布到 RocketMQ下游成功由各服务消费位点保证。
  • statistics-service 只保证从已发布 MQ 事件生成聚合表;如果历史库没有对应 outbox 事实,例如早期用户没有 UserRegistered、本地没有 game_outbox,统计结果会按当前事实为 0不允许临时扫业务表补数。
  • JWT、腾讯云 IM secret 等本地配置只能用开发值,线上必须走安全配置源。
Description
No description provided
Readme 28 MiB
Languages
Go 99%
Python 0.3%
HTML 0.3%
Shell 0.2%