28 KiB
28 KiB
渠道管理与用户注册绑定调用链文档
概述
本文档描述 Bindbox Game 项目中渠道管理模块与用户注册绑定的调用链关系。
渠道绑定的三种方式:
- 用户登录时绑定 - 微信/抖音登录时传入
channel_code - 定时任务自动绑定 - 直播间奖品发放时,根据活动关联渠道自动绑定主播邀请人
- 抖音登录绑定 - 抖音小程序登录时传入
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):
// 查找渠道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. 发放奖品并更新状态
关键代码:自动绑定主播邀请人
// 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 邀请绑定规则
- 仅限一次: 用户绑定邀请人后不可更改
- 不能自邀: 用户不能邀请自己
- 奖励触发: 绑定成功后触发任务中心奖励逻辑
- 定时任务补绑: 直播间用户未绑定邀请人时,自动绑定主播
6.4 权限控制
渠道管理接口需要以下权限:
| 操作 | 权限标识 |
|---|---|
| 创建渠道 | channel:create |
| 修改渠道 | channel:modify |
| 删除渠道 | channel:delete |
| 查看渠道 | channel:view |
七、相关迁移文件
| 文件 | 说明 |
|---|---|
migrations/20260223_add_channel_fields_to_livestream_activities.sql |
直播间活动添加渠道字段 |
八、注意事项
- 渠道 Code 唯一性: 渠道的
code字段必须唯一,用于用户登录时匹配 - 统计性能: 渠道统计涉及多表 JOIN,大数据量时需注意性能优化
- 事务处理: 用户创建和渠道绑定在同一事务中,保证数据一致性
- 软删除: 渠道删除为软删除,不影响已绑定用户
文档生成时间: 2026-02-27 项目: bindbox_game