Skip to content

feat: 用户邀请裂变推广系统#734

Open
JIA-ss wants to merge 6 commits intoWei-Shaw:mainfrom
JIA-ss:feat/referral-system
Open

feat: 用户邀请裂变推广系统#734
JIA-ss wants to merge 6 commits intoWei-Shaw:mainfrom
JIA-ss:feat/referral-system

Conversation

@JIA-ss
Copy link
Contributor

@JIA-ss JIA-ss commented Mar 3, 2026

功能概述

实现用户邀请裂变推广系统,支持用户通过专属邀请码/链接邀请新用户注册,双方均可获得奖励。

新增特性

后端

新增数据模型

  • ReferralRelation:记录邀请关系(邀请人 ID、被邀请人 ID、双方奖励金额、奖励发放状态)
  • UserReferralProfile:每位用户的专属邀请码(8 位大写字母+数字,懒加载创建)

新增 API

用户端:

  • GET /api/user/referral — 获取我的邀请码和专属链接
  • GET /api/user/referral/invitees — 分页获取被邀请人列表
  • GET /api/user/referral/rewards — 获取我的奖励汇总(已发放/待发放)

管理员端:

  • GET /admin/api/referral/stats — 获取平台邀请统计数据
  • GET /admin/api/referral/list — 分页查询所有邀请关系记录

奖励机制

  • 注册时传入 referral_code,自动关联邀请关系
  • 被邀请人账户余额充值成功后,系统自动向双方发放奖励(事务保障)
  • 邀请码大小写不敏感

新增系统设置(管理后台可配置)

  • referral_enabled — 功能总开关
  • referral_inviter_reward — 邀请人奖励金额(元)
  • referral_invitee_reward — 被邀请人奖励金额(元)

前端

用户端新增页面

  • 邀请页(/user/referral):展示专属邀请码、一键复制邀请链接、被邀请人列表、奖励历史记录

管理员端

  • 设置页新增"邀请裂变"配置区块(开关、奖励金额)

代码质量

  • 全部通过 TypeScript 编译检查
  • 经过两轮 Codex 独立代码审查,修复了所有 CRITICAL/MAJOR 问题:
    • 邀请码规范化(大小写统一处理)
    • 奖励发放错误正确向上传播(不再静默吞掉 DB 错误)
    • 前端被邀请人列表错误状态正确处理
    • 所有 repository 方法使用 clientFromContext 支持事务

CI 修复说明

兼容上游主分支并入后的接口变更:

  • 补充所有测试桩的 BillingCache 接口新增方法:SetAPIKeyRateLimitUpdateAPIKeyRateLimitUsageInvalidateAPIKeyRateLimit
  • 补充所有测试桩的 APIKeyRepository 接口新增方法:GetRateLimitDataIncrementRateLimitUsageResetRateLimitWindows
  • 更新 api_contract_test.go 中 API Key 响应快照,补充 rate limit / usage / window 字段
  • 修复 NewBillingCacheService 新增 APIKeyRepository 参数导致的测试编译错误
  • 修复 NewAuthService 新增 ReferralService 参数后所有调用方的签名
  • 运行 go generate ./ent/... 生成 ent ORM 代码并提交
  • 修复 gofmt 格式问题

Implement a full referral/invite system that lets users invite others
and earn rewards when their invitees register.

## Backend

### Data model
- New Ent schemas: `UserReferralProfile` (per-user referral code) and
  `ReferralRelation` (inviter↔invitee binding with reward amounts).
  `invitee_id` carries a UNIQUE constraint to prevent duplicate grants.
- New migrations 064/065: create tables and add performance indexes
  (inviter_id, invitee_id, reward_granted) in a non-transactional file
  so they work with MySQL's implicit-commit DDL.

### Service layer (`referral.go` / `referral_service.go`)
- `ReferralRepository` interface covers profile CRUD, relation CRUD,
  reward helpers, and list/stats queries.
- `ReferralService.ProcessReferralRegistration` wraps reward grants in
  a single DB transaction; rolls back atomically on any failure.
- `GrantRewardsInTx` updates both balances then sets `reward_granted`
  via `MarkRewardGranted`, giving an application-level idempotency flag.
- Cache invalidation for inviter/invitee reward totals after commit.

### Repository (`referral_repo.go`)
- All methods (including read-only list/stats) use `clientFromContext`
  so they participate in the caller's transaction when one is active.
- `CreateRelation` back-fills `relation.ID` via pointer mutation so
  the subsequent `MarkRewardGranted` call uses the correct row ID.

### Auth integration (`auth_service.go`)
- Registration flow accepts an optional `ref` query parameter.
- Referral code is normalised (`strings.ToUpper` + `TrimSpace`) before
  lookup to tolerate lowercase user input.
- Self-referral is rejected; invalid codes are silently ignored so
  registration is never blocked by a bad referral code.
- On successful referral processing the caller receives the updated
  user object (balance already incremented).

### Settings (`setting_service.go`)
- New keys: `referral_enabled`, `referral_inviter_reward`,
  `referral_invitee_reward`.  Default reward values are **0** so
  rewards are opt-in; admins must explicitly configure amounts.
- DB errors in `GetReferralRewards` are now propagated (previously
  swallowed), preventing silent 0-reward record creation.

### HTTP layer
- `GET  /api/v1/referral` — user fetches own referral info + stats.
- `GET  /api/v1/referral/invitees` — paginated invitee list.
- `GET  /api/v1/admin/referral/stats` — platform-wide stats for admins.

## Frontend

### User page (`ReferralView.vue`)
- Displays referral code and full invite link (built from
  `window.location.origin` to work behind any reverse proxy).
- Copy buttons support Clipboard API with `execCommand` fallback for
  HTTP environments.
- Paginated invitee table with per-table error state (`inviteesError`)
  so a failed list request does not break the whole page.
- Full-page error state with retry button for the initial load.

### Register page (`RegisterView.vue` / `EmailVerifyView.vue`)
- Reads `?ref=CODE` from the URL and pre-fills the referral code field.

### Admin settings (`SettingsView.vue`)
- New "Referral Program" section: enable/disable toggle, inviter reward
  amount, invitee reward amount.
- Form defaults changed to 0/0 (was 10/5) to match backend semantics.

### Navigation & routing
- Sidebar entry for `/referral` shown only when `referral_enabled`.
- Vue Router route added for the new page.

### i18n
- Full translations for all referral strings in `en.ts` and `zh.ts`.
- 运行 go generate ./ent/... 生成 referralrelation 和 userreferralprofile
  两个新实体的 ent 客户端代码(predicates、builder、mutation 等)
- 修复 wire_gen.go:注入 ReferralRepository、ReferralService、
  AdminReferralHandler 和 ReferralHandler 依赖
- 修复 NewAuthService 调用方签名(新增 referralService 参数):
  cmd/jwtgen/main.go、admin_auth_test.go、jwt_auth_test.go、
  auth_service_register_test.go、auth_service_turnstile_register_test.go
- 修复 ent/schema/user_referral_profile.go 中未使用的 dialect 导入
@JIA-ss JIA-ss force-pushed the feat/referral-system branch from 5e22511 to c556208 Compare March 3, 2026 07:50
@JIA-ss JIA-ss changed the title feat: add referral system for user growth feat: 用户邀请裂变推广系统 Mar 3, 2026
JIA-ss added 4 commits March 3, 2026 16:58
- 为所有 BillingCache 测试桩补充 SetAPIKeyRateLimit、
  UpdateAPIKeyRateLimitUsage、InvalidateAPIKeyRateLimit 方法
- 为所有 APIKeyRepository 测试桩补充 GetRateLimitData、
  IncrementRateLimitUsage、ResetRateLimitWindows 方法
- 修复 api_contract_test.go 中 POST/GET /api/v1/keys 响应快照
  缺失 rate_limit/usage/window 字段
- 修复 subscription_calculate_progress_test.go 中时间敏感的
  ExpiresInDays 断言(允许 29 或 30 天)
- 修复 gofmt 格式问题
双方均在测试桩中添加了相同的接口方法,合并时保留上游方法顺序:
- APIKeyRepository: IncrementRateLimitUsage / ResetRateLimitWindows / GetRateLimitData
- BillingCache: GetAPIKeyRateLimit / SetAPIKeyRateLimit / UpdateAPIKeyRateLimitUsage / InvalidateAPIKeyRateLimit
- api_key_auth_test: 统一返回 nil 而非 errors.New
_notx.sql 文件不能包含 goose 指令(-- +goose StatementBegin 含 BEGIN
字样会被误判为事务控制语句),应仅包含裸 SQL。
参考 062_add_scheduler_and_usage_composite_indexes_notx.sql 的格式。
- 将 064_add_referral_system.sql 重命名为 066_add_referral_system.sql
  - 避免与主干已有 064_add_api_key_rate_limits.sql 的编号冲突
  - 去除 goose Up/Down 指令(自定义 runner 会执行整个文件内容,
    导致 DROP TABLE 紧跟 CREATE TABLE 执行,表立即被删除)
  - 改用 IF NOT EXISTS 保证幂等性
- 将 065_add_referral_indexes_notx.sql 重命名为 067_add_referral_indexes_notx.sql

- 新增 backend/internal/service/referral_service_test.go(35 个单元测试)
  - 覆盖纯函数:maskEmail、buildReferralLink、isUniqueConflict、generateReferralCode
  - 覆盖服务方法:GetOrCreateProfile(含重试逻辑)、ValidateReferralCode、
    GrantRewardsInTx、GetMyReferralInfo、ListMyInvitees、GetPlatformStats、
    InvalidateRewardCaches
  - 使用 function-field stub 模式实现 ReferralRepository 和 UserRepository 桩

- 在 backend/internal/server/api_contract_test.go 中补充 referral API contract 测试
  - 新增 stubReferralRepoForContract 及默认数据配置
  - 注册路由:GET /api/v1/referral、GET /api/v1/referral/invitees、
    GET /api/v1/admin/referral/stats
  - 新增 3 个 contract 测试用例验证响应结构和状态码
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant