feat(invitation): 实现邀请返利系统、专属注册落地页与分成管理

- 数据库与数据模型:新增 migration 000081,支持用户专属邀请码、被邀请人绑定与实名时间戳;新增邀请返利配置表与结算明细表
- 业务与结算逻辑:实现首月 60%、次月 30%、往后 15% 阶梯返利算法;买卖双向独立寻源并根据订单平台利润 50% 单边基准核算;与终态订单结算和钱包可用余额强幂等联动
- 分销站点支持:支持主站与加盟分站独立配置返利比例;跨站交易按照收号端与售卖端独立出资,资金互不侵蚀
- 前端交互与入口:
  - 新增专属短链注册落地页 /i/:code,自动带入并锁定邀请码,引导实名认证
  - 个人中心与移动端新增邀请返利看板(专属码、推广链接、统计面板、好友记录与返利流水明细)
  - 管理后台新增邀请返利管理(比例配置、邀请关系与返利流水审计)
- 验证方式:
  - 通过 repository_test.go 单元测试(阶梯区间、实名激活、跨站多方分流验证)
  - 通过 npm run format、npm run check 与 scripts/check.sh 全量门禁校验
This commit is contained in:
yml2213
2026-10-10 12:48:59 +08:00
parent e92668689b
commit 984433f866
26 changed files with 4292 additions and 16 deletions
+26 -8
View File
@@ -1,6 +1,7 @@
package auth
import (
"context"
"errors"
"net/http"
"strings"
@@ -11,8 +12,17 @@ import (
"github.com/gin-gonic/gin"
)
type InvitationBinder interface {
BindInvitation(ctx context.Context, inviteeID uint64, code string, siteID uint64, siteCode ...string) error
}
type Handler struct {
service *Service
service *Service
invitationBinder InvitationBinder
}
func (h *Handler) SetInvitationBinder(binder InvitationBinder) {
h.invitationBinder = binder
}
type SendSMSRequest struct {
@@ -23,9 +33,10 @@ type SendSMSRequest struct {
}
type LoginRequest struct {
Phone string `json:"phone" binding:"required"`
Code string `json:"code" binding:"required,len=6"`
TrackCode string `json:"track_code"`
Phone string `json:"phone" binding:"required"`
Code string `json:"code" binding:"required,len=6"`
TrackCode string `json:"track_code"`
InviteCode string `json:"invite_code"`
}
type RefreshRequest struct {
@@ -43,10 +54,11 @@ type SetPasswordRequest struct {
}
type RegisterRequest struct {
Phone string `json:"phone" binding:"required"`
Code string `json:"code" binding:"required,len=6"`
Password string `json:"password" binding:"required,min=8,max=20"`
TrackCode string `json:"track_code"`
Phone string `json:"phone" binding:"required"`
Code string `json:"code" binding:"required,len=6"`
Password string `json:"password" binding:"required,min=8,max=20"`
TrackCode string `json:"track_code"`
InviteCode string `json:"invite_code"`
}
type ResetPasswordRequest struct {
@@ -141,6 +153,9 @@ func (h *Handler) Login(c *gin.Context) {
writeAuthError(c, err)
return
}
if h.invitationBinder != nil && strings.TrimSpace(req.InviteCode) != "" {
_ = h.invitationBinder.BindInvitation(c.Request.Context(), result.User.ID, strings.TrimSpace(req.InviteCode), 0, sitecode.FromRequest(c))
}
setAccessCookie(c, result.Tokens.AccessToken)
response.OK(c, result)
}
@@ -224,6 +239,9 @@ func (h *Handler) Register(c *gin.Context) {
writeAuthError(c, err)
return
}
if h.invitationBinder != nil && strings.TrimSpace(req.InviteCode) != "" {
_ = h.invitationBinder.BindInvitation(c.Request.Context(), result.User.ID, strings.TrimSpace(req.InviteCode), 0, sitecode.FromRequest(c))
}
setAccessCookie(c, result.Tokens.AccessToken)
response.OK(c, result)
}