# 渠道管理与用户注册绑定调用链文档 ## 概述 本文档描述 Bindbox Game 项目中渠道管理模块与用户注册绑定的调用链关系。 **渠道绑定的三种方式:** 1. **用户登录时绑定** - 微信/抖音登录时传入 `channel_code` 2. **定时任务自动绑定** - 直播间奖品发放时,根据活动关联渠道自动绑定主播邀请人 3. **抖音登录绑定** - 抖音小程序登录时传入 `channel_code` --- ## 一、数据模型 ### 1.1 渠道表 (channels) | 字段 | 类型 | 说明 | |------|------|------| | id | int64 | 主键ID | | name | string | 渠道名称 | | code | string | 渠道唯一标识(用于登录时绑定) | | type | string | 渠道类型 | | remarks | string | 备注 | | created_at | time | 创建时间 | | updated_at | time | 更新时间 | | deleted_at | time | 删除时间(软删) | **文件位置**: `internal/repository/mysql/model/channels.gen.go:16-25` ### 1.2 用户表 (users) - 渠道相关字段 | 字段 | 类型 | 说明 | |------|------|------| | id | int64 | 主键ID | | channel_id | int64 | 渠道ID(关联 channels.id) | | invite_code | string | 用户唯一邀请码 | | inviter_id | int64 | 邀请人用户ID | | openid | string | 微信openid | | unionid | string | 微信unionid | **文件位置**: `internal/repository/mysql/model/users.gen.go:16-33` --- ## 二、调用链架构图 ``` ┌─────────────────────────────────────────────────────────────────────┐ │ 前端/客户端 │ └─────────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ API 路由层 │ │ internal/router/router.go │ │ │ │ 渠道管理路由: │ │ - POST /api/admin/channels → CreateChannel() │ │ - PUT /api/admin/channels/:id → ModifyChannel() │ │ - DELETE /api/admin/channels/:id → DeleteChannel() │ │ - GET /api/admin/channels → ListChannels() │ │ - GET /api/admin/channels/:id/stats → ChannelStats() │ │ │ │ 用户登录路由: │ │ - POST /api/app/users/weixin/login → WeixinLogin() │ │ (携带 channel_code 参数) │ │ - POST /api/app/users/douyin/login → DouyinLogin() │ │ (携带 channel_code 参数) │ └─────────────────────────────────────────────────────────────────────┘ │ ┌─────────────────────┼─────────────────────┐ ▼ ▼ ▼ ┌─────────────────────────┐ ┌───────────────────┐ ┌───────────────────────┐ │ 渠道管理 API 层 │ │ 用户登录 API 层 │ │ 定时任务调度器 │ │ internal/api/admin/ │ │ internal/api/user/│ │ internal/service/ │ │ channels.go │ │ login_app.go │ │ douyin/scheduler.go │ │ │ │ login_douyin_app │ │ │ │ - CreateChannel() │ │ - WeixinLogin() │ │ - GrantLivestreamPrizes() │ - ModifyChannel() │ │ - DouyinLogin() │ │ - bindAnchorInviter │ │ - DeleteChannel() │ │ 接收channel_code│ │ IfNeeded() │ │ - ListChannels() │ │ │ │ │ │ - ChannelStats() │ │ │ │ 每5分钟自动执行 │ └─────────────────────────┘ └───────────────────┘ └───────────────────────┘ │ │ │ ▼ ▼ ▼ ┌─────────────────────────┐ ┌───────────────────┐ ┌───────────────────────┐ │ 渠道 Service 层 │ │ 用户 Service 层 │ │ 用户 Service 层 │ │ internal/service/ │ │ internal/service/ │ │ internal/service/user │ │ channel/channel.go │ │ user/ │ │ │ │ │ │ login_weixin.go │ │ - BindInviter() │ │ - Create() │ │ login_douyin.go │ │ (定时任务调用) │ │ - Modify() │ │ │ │ │ │ - Delete() │ │ - LoginWeixin() │ │ │ │ - List() │ │ - LoginDouyin() │ │ │ │ - GetStats() │ │ 查渠道并绑定用户 │ │ │ │ - GetByID() │ │ │ │ │ └─────────────────────────┘ └───────────────────┘ └───────────────────────┘ │ │ │ └─────────────────────┼─────────────────────┘ ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ 数据访问层 (DAO) │ │ internal/repository/mysql/dao/ │ │ │ │ - channels.gen.go 渠道表操作 │ │ - users.gen.go 用户表操作 │ │ - user_invites.gen.go 邀请关系表操作 │ │ - livestream_activities.gen.go 直播间活动表(含渠道字段) │ └─────────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ MySQL 数据库 │ │ - channels 表 │ │ - users 表 (channel_id 字段) │ │ - user_invites 表 │ │ - livestream_activities 表 (channel_id, channel_code 字段) │ └─────────────────────────────────────────────────────────────────────┘ ``` --- ## 三、详细调用链 ### 3.1 渠道管理(管理端) #### 创建渠道 ``` HTTP POST /api/admin/channels │ ├── 参数: { name, code, type, remarks } │ ▼ internal/api/admin/channels.go:26 CreateChannel() │ ├── 验证参数 │ ▼ internal/service/channel/channel.go:79 Create() │ ├── 创建 Channels 模型 │ ▼ dao.Channels.Create(m) │ ▼ MySQL INSERT INTO channels ``` #### 查询渠道列表(含用户数统计) ``` HTTP GET /api/admin/channels │ ▼ internal/api/admin/channels.go:135 ListChannels() │ ▼ internal/service/channel/channel.go:111 List() │ ├── 1. 查询渠道列表 │ SELECT * FROM channels WHERE name LIKE ? ORDER BY id DESC │ ├── 2. 统计每个渠道的用户数 │ SELECT channel_id, count(*) as count │ FROM users │ WHERE channel_id IN (?) │ GROUP BY channel_id │ ▼ 返回渠道列表(含 user_count 字段) ``` #### 渠道数据分析 ``` HTTP GET /api/admin/channels/:channel_id/stats │ ▼ internal/api/admin/channels.go:53 ChannelStats() │ ▼ internal/service/channel/channel.go:169 GetStats() │ ├── 1. 统计渠道用户总数 │ SELECT count(*) FROM users WHERE channel_id = ? │ ├── 2. 统计渠道订单数和GMV │ SELECT count(*) as count, sum(actual_amount) as gmv │ FROM orders o │ JOIN users u ON u.id = o.user_id │ WHERE u.channel_id = ? AND o.status = 2 │ ├── 3. 月度用户增长统计 │ SELECT DATE_FORMAT(created_at, '%Y-%m') as date, count(*) as count │ FROM users │ WHERE channel_id = ? AND created_at >= ? │ GROUP BY date │ ├── 4. 月度订单统计 │ SELECT DATE_FORMAT(created_at, '%Y-%m') as date, count(*), sum(actual_amount) │ FROM orders o │ JOIN users u ON u.id = o.user_id │ WHERE u.channel_id = ? AND o.status = 2 AND o.created_at >= ? │ GROUP BY date │ ▼ 返回渠道统计数据 ``` --- ### 3.2 用户注册绑定渠道 #### 微信登录(绑定渠道) ``` HTTP POST /api/app/users/weixin/login │ ├── 参数: { code, invite_code, douyin_id, channel_code } │ ▼ internal/api/user/login_app.go:47 WeixinLogin() │ ├── 1. 微信 code2session 获取 openid/unionid │ ▼ internal/service/user/login_weixin.go:42 LoginWeixin() │ ├── 2. 查询渠道(如果传入 channel_code) │ ch, _ := s.readDB.Channels.Where(Channels.Code.Eq(in.ChannelCode)).First() │ channelID = ch.ID │ 【文件位置: login_weixin.go:86-92】 │ ├── 3. 查找或创建用户 │ ├── 查找: WHERE openid = ? OR unionid = ? │ │ │ └── 创建新用户: │ u = &model.Users{ │ Nickname: nickname, │ Openid: in.OpenID, │ ChannelID: channelID, // 绑定渠道 │ ... │ } │ 【文件位置: login_weixin.go:113-124】 │ ├── 4. 更新已有用户(如果传入 channel_code) │ if channelID > 0 { │ UPDATE users SET channel_id = ? WHERE id = ? │ } │ 【文件位置: login_weixin.go:141-143】 │ ├── 5. 处理邀请关系(如果传入 invite_code 且是新用户) │ 【详见 3.3 节】 │ ▼ 返回用户信息和 Token ``` **关键代码片段**(`login_weixin.go:86-92`): ```go // 查找渠道ID var channelID int64 if in.ChannelCode != "" { ch, _ := s.readDB.Channels.WithContext(ctx).Where(s.readDB.Channels.Code.Eq(in.ChannelCode)).First() if ch != nil { channelID = ch.ID } } ``` --- ### 3.3 抖音登录(绑定渠道) ``` HTTP POST /api/app/users/douyin/login │ ├── 参数: { code, anonymous_code, invite_code, channel_code } │ ▼ internal/api/user/login_douyin_app.go:44 DouyinLogin() │ ├── 参数校验 │ ▼ internal/service/user/login_douyin.go:39 LoginDouyin() │ ├── 1. 抖音 code2session 获取 openid │ ├── 2. 查询渠道(如果传入 channel_code) │ ch, _ := s.readDB.Channels.Where(Channels.Code.Eq(in.ChannelCode)).First() │ channelID = ch.ID │ 【文件位置: login_douyin.go:91-97】 │ ├── 3. 查找或创建用户 │ ├── 查找: WHERE douyin_id = ? OR unionid = ? │ │ │ └── 创建新用户: │ u = &model.Users{ │ Nickname: nickname, │ DouyinID: openID, │ ChannelID: channelID, // 绑定渠道 │ ... │ } │ 【文件位置: login_douyin.go:119-127】 │ ├── 4. 更新已有用户(如果传入 channel_code 且未绑定) │ if channelID > 0 && u.ChannelID == 0 { │ UPDATE users SET channel_id = ? │ } │ 【文件位置: login_douyin.go:143-144】 │ └── 5. 处理邀请关系(如果传入 invite_code 且是新用户) ``` --- ### 3.4 邀请关系绑定 用户绑定邀请人有两种方式: #### 方式一:登录时自动绑定(推荐) ``` 用户登录时传入 invite_code 参数 │ ▼ LoginWeixin() / LoginDouyin() 内部处理 │ ├── 检查是否新用户 │ ├── 查找邀请人(通过 invite_code) │ ├── 创建 user_invites 记录 │ ├── 更新 users.inviter_id │ ▼ 触发任务中心奖励: task.OnInviteSuccess() ``` #### 方式二:用户主动绑定 ``` HTTP POST /api/app/users/inviter/bind │ ├── 参数: { invite_code } │ ▼ internal/api/user/bind_inviter_app.go:34 BindInviter() │ ▼ internal/service/user/bind_inviter.go:33 BindInviter() │ ├── 1. 加锁获取当前用户 │ ├── 2. 检查是否已绑定(inviter_id != 0 则拒绝) │ ├── 3. 查找邀请人 │ ├── 4. 创建 user_invites 记录 │ ├── 5. 更新 users.inviter_id │ ▼ 触发任务中心奖励: task.OnInviteSuccess() ``` --- ### 3.5 定时任务自动绑定渠道(直播间奖品发放) **重要场景:直播间用户通过渠道绑定主播邀请人** ``` 定时任务 (每5分钟) │ ▼ internal/service/douyin/scheduler.go:24 StartDouyinOrderSync() │ ├── ticker5min.C 触发 │ ▼ internal/service/douyin/scheduler.go:155 GrantLivestreamPrizes() │ ├── 1. 查找未发放的直播抽奖记录 │ SELECT * FROM livestream_draw_logs WHERE is_granted = 0 │ ├── 2. 解析活动关联的渠道/主播邀请码 │ resolveActivityAnchorCodes() │ 【文件位置: scheduler.go:418-489】 │ │ ├── 查询直播间活动的渠道信息 │ │ SELECT id, channel_id, channel_code │ │ FROM livestream_activities │ │ WHERE id IN (?) │ │ 【文件位置: scheduler.go:451-458】 │ │ │ └── 补充缺失的渠道 code │ fetchChannelCodes() │ SELECT id, code FROM channels WHERE id IN (?) │ 【文件位置: scheduler.go:491-513】 │ ├── 3. 自动绑定主播邀请人(如果用户未绑定) │ bindAnchorInviterIfNeeded(ctx, userID, anchorCode) │ 【文件位置: scheduler.go:515-546】 │ │ ├── 查询用户是否已有邀请人 │ │ SELECT inviter_id FROM users WHERE id = ? │ │ │ └── 如果 inviter_id == 0,调用绑定服务 │ s.userSvc.BindInviter(ctx, userID, BindInviterInput{InviteCode: anchorCode}) │ 【文件位置: scheduler.go:534】 │ └── 4. 发放奖品并更新状态 ``` **关键代码:自动绑定主播邀请人** ```go // scheduler.go:515-546 func (s *service) bindAnchorInviterIfNeeded(ctx context.Context, userID int64, anchorCode string) { // 1. 检查用户是否已有邀请人 userRecord, err := s.readDB.Users.WithContext(ctx). Select(s.readDB.Users.InviterID). Where(s.readDB.Users.ID.Eq(userID)). First() if userRecord.InviterID != 0 { return // 已绑定,跳过 } // 2. 自动绑定主播邀请人 s.userSvc.BindInviter(ctx, userID, user.BindInviterInput{InviteCode: anchorCode}) } ``` **数据流:** ``` ┌───────────────────────────────────────────────────────────────────────┐ │ 直播间活动配置 │ │ livestream_activities │ │ ├── channel_id (关联渠道ID) │ │ └── channel_code (主播邀请码) │ └───────────────────────────────────────────────────────────────────────┘ │ ▼ ┌───────────────────────────────────────────────────────────────────────┐ │ 直播抽奖记录 │ │ livestream_draw_logs │ │ ├── activity_id (关联活动) │ │ ├── local_user_id (本地用户ID) │ │ └── is_granted (发放状态) │ └───────────────────────────────────────────────────────────────────────┘ │ ▼ ┌───────────────────────────────────────────────────────────────────────┐ │ 定时任务处理 │ │ GrantLivestreamPrizes() │ │ │ │ 1. 查 activity → 获取 channel_code │ │ 2. 查 channels → 补充缺失的 code │ │ 3. 查 users.inviter_id → 检查是否已绑定 │ │ 4. 未绑定 → 调用 BindInviter() 绑定主播 │ └───────────────────────────────────────────────────────────────────────┘ │ ▼ ┌───────────────────────────────────────────────────────────────────────┐ │ 用户邀请关系 │ │ users.inviter_id = 主播用户ID │ │ user_invites 表新增记录 │ └───────────────────────────────────────────────────────────────────────┘ ``` --- ## 四、核心文件索引 | 文件路径 | 说明 | 关键函数 | |----------|------|----------| | `internal/api/admin/channels.go` | 渠道管理 API | CreateChannel, ListChannels, ChannelStats | | `internal/service/channel/channel.go` | 渠道业务逻辑 | Create, List, GetStats | | `internal/api/user/login_app.go` | 用户登录 API (微信) | WeixinLogin | | `internal/service/user/login_weixin.go` | 微信登录逻辑 | LoginWeixin(渠道绑定核心) | | `internal/api/user/login_douyin_app.go` | 用户登录 API (抖音) | DouyinLogin | | `internal/service/user/login_douyin.go` | 抖音登录逻辑 | LoginDouyin(渠道绑定核心) | | `internal/api/user/bind_inviter_app.go` | 绑定邀请人 API | BindInviter | | `internal/service/user/bind_inviter.go` | 绑定邀请人逻辑 | BindInviter | | **`internal/service/douyin/scheduler.go`** | **抖音定时任务** | **GrantLivestreamPrizes, bindAnchorInviterIfNeeded** | | `internal/repository/mysql/model/channels.gen.go` | 渠道模型 | Channels struct | | `internal/repository/mysql/model/users.gen.go` | 用户模型 | Users struct(含 channel_id) | | `internal/repository/mysql/model/user_invites.gen.go` | 邀请关系模型 | UserInvites struct | | `internal/repository/mysql/model/livestream_activities.gen.go` | 直播间活动模型 | LivestreamActivities(含 channel_id, channel_code) | | `internal/router/router.go` | 路由配置 | 渠道路由: 215-219 行 | --- ## 五、数据流图 ### 5.1 用户注册绑定渠道流程 ``` ┌──────────┐ ┌──────────────┐ ┌────────────────┐ │ 前端 │────▶│ 微信登录 API │────▶│ 用户 Service │ │ │ │ │ │ │ │ channel_ │ │ code │ │ 1. code2session│ │ code │ │ invite_code │ │ 2. 查渠道 │ └──────────┘ └──────────────┘ │ 3. 创建/更新用户│ │ 4. 绑定邀请人 │ └───────┬────────┘ │ ┌─────────────────────────┼─────────────────────────┐ │ │ │ ▼ ▼ ▼ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ channels 表 │ │ users 表 │ │user_invites表│ │ │ │ │ │ │ │ code → ID │ │ channel_id │ │ inviter_id │ │ │ │ inviter_id │ │ invitee_id │ └──────────────┘ └──────────────┘ └──────────────┘ ``` ### 5.2 渠道统计查询流程 ``` ┌──────────┐ ┌──────────────┐ ┌────────────────┐ │ 管理后台 │────▶│ 渠道统计 API │────▶│ 渠道 Service │ │ │ │ │ │ │ │ 选择渠道 │ │ channel_id │ │ 1. 统计用户数 │ │ 时间范围 │ │ days │ │ 2. 统计订单数 │ └──────────┘ │ start_date │ │ 3. 统计GMV │ │ end_date │ │ 4. 月度趋势 │ └──────────────┘ └───────┬────────┘ │ ┌─────────────────────────┴────────────────┐ │ │ ▼ ▼ ┌──────────────┐ ┌──────────────┐ │ users 表 │ │ orders 表 │ │ │ │ │ │ WHERE │ │ JOIN users │ │ channel_id=? │ │ WHERE │ │ │ │ channel_id=? │ └──────────────┘ └──────────────┘ ``` --- ## 六、业务规则 ### 6.1 渠道绑定规则 | 场景 | 触发条件 | 说明 | |------|----------|------| | 微信登录绑定 | 传入 `channel_code` | 查询 channels 表,绑定 channel_id 到用户 | | 抖音登录绑定 | 传入 `channel_code` | 查询 channels 表,仅当用户未绑定时才更新 | | 定时任务绑定 | 直播间活动配置了 `channel_code` | 自动绑定主播邀请人(邀请关系,非渠道) | ### 6.2 渠道与邀请人的区别 | 概念 | 字段 | 说明 | |------|------|------| | **渠道 (Channel)** | `users.channel_id` | 用户来源渠道,用于统计分析 | | **邀请人 (Inviter)** | `users.inviter_id` | 邀请该用户注册的人,用于奖励计算 | **定时任务场景:** - 直播间活动的 `channel_code` 用作**主播邀请码** - 定时任务调用 `BindInviter()` 绑定的是**邀请关系**,而非渠道 - 主播邀请码 = 某个用户的 `invite_code`(通常是主播账号) ### 6.3 邀请绑定规则 1. **仅限一次**: 用户绑定邀请人后不可更改 2. **不能自邀**: 用户不能邀请自己 3. **奖励触发**: 绑定成功后触发任务中心奖励逻辑 4. **定时任务补绑**: 直播间用户未绑定邀请人时,自动绑定主播 ### 6.4 权限控制 渠道管理接口需要以下权限: | 操作 | 权限标识 | |------|----------| | 创建渠道 | `channel:create` | | 修改渠道 | `channel:modify` | | 删除渠道 | `channel:delete` | | 查看渠道 | `channel:view` | --- ## 七、相关迁移文件 | 文件 | 说明 | |------|------| | `migrations/20260223_add_channel_fields_to_livestream_activities.sql` | 直播间活动添加渠道字段 | --- ## 八、注意事项 1. **渠道 Code 唯一性**: 渠道的 `code` 字段必须唯一,用于用户登录时匹配 2. **统计性能**: 渠道统计涉及多表 JOIN,大数据量时需注意性能优化 3. **事务处理**: 用户创建和渠道绑定在同一事务中,保证数据一致性 4. **软删除**: 渠道删除为软删除,不影响已绑定用户 --- *文档生成时间: 2026-02-27* *项目: bindbox_game*