bindbox-game/.claude/plan/channel-user-bindding-doc.md
2026-02-27 17:51:38 +08:00

28 KiB
Raw Blame History

渠道管理与用户注册绑定调用链文档

概述

本文档描述 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:

// 查找渠道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 邀请绑定规则

  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