This commit is contained in:
2026-05-29 12:37:37 +08:00
parent f8bf88f635
commit 0d3a116820
5 changed files with 46 additions and 14 deletions

View File

@@ -4,7 +4,7 @@ import "time"
// CreateInvitationResponse 创建邀请
type CreateInvitationResponse struct {
InviteToken string `json:"invite_token" description:"仅返回一次,请转达被邀请人"`
InviteToken string `json:"invite_token" description:"主账号固定邀请码,可重复使用"`
InviteURL string `json:"invite_url" description:"子站注册完整链接"`
ExpiresAt time.Time `json:"expires_at"`
InvitationID string `json:"invitation_id"`

View File

@@ -81,8 +81,15 @@ func (s *SubordinateApplicationServiceImpl) RegisterSubPortal(ctx context.Contex
if inv == nil {
return fmt.Errorf("邀请码无效")
}
if inv.Status != subentities.InvitationStatusPending {
return fmt.Errorf("邀请码已使用或已失效")
switch inv.Status {
case subentities.InvitationStatusRevoked:
return fmt.Errorf("邀请码已失效")
case subentities.InvitationStatusConsumed:
return fmt.Errorf("邀请码已失效,请联系主账号获取新邀请码")
case subentities.InvitationStatusPending:
// 固定邀请码可重复使用,注册后不核销
default:
return fmt.Errorf("邀请码无效")
}
now := time.Now()
if now.After(inv.ExpiresAt) {
@@ -105,15 +112,6 @@ func (s *SubordinateApplicationServiceImpl) RegisterSubPortal(ctx context.Contex
return fmt.Errorf("注册失败,请重试或联系主账号")
}
consumed, consumeErr := s.subRepo.ConsumeInvitation(txCtx, inv.ID, u.ID, now)
if consumeErr != nil {
s.logger.Error("核销邀请失败", zap.Error(consumeErr), zap.String("user_id", u.ID))
return fmt.Errorf("注册失败,请重试或联系主账号")
}
if !consumed {
return fmt.Errorf("邀请码已使用或已失效")
}
resp = &responses.SubPortalRegisterResponse{ID: u.ID, Phone: u.Phone}
return nil
})
@@ -123,8 +121,19 @@ func (s *SubordinateApplicationServiceImpl) RegisterSubPortal(ctx context.Contex
return resp, nil
}
// CreateInvitation 主账号发邀请
// CreateInvitation 获取或创建主账号固定邀请码(可重复使用)
func (s *SubordinateApplicationServiceImpl) CreateInvitation(ctx context.Context, cmd *commands.CreateInvitationCommand) (*responses.CreateInvitationResponse, error) {
existing, err := s.subRepo.FindActiveInvitationByParent(ctx, cmd.ParentUserID)
if err != nil {
return nil, err
}
if existing != nil {
if raw := strings.TrimSpace(existing.Token); raw != "" {
return s.buildInvitationResponse(existing, raw)
}
// 历史 pending 记录未存明文,无法找回,继续创建新的固定邀请码
}
hours := cmd.ExpiresInHours
if hours <= 0 {
// 永久有效设置100年后过期
@@ -136,6 +145,7 @@ func (s *SubordinateApplicationServiceImpl) CreateInvitation(ctx context.Context
}
inv := &subentities.SubordinateInvitation{
ParentUserID: cmd.ParentUserID,
Token: raw,
TokenHash: hash,
ExpiresAt: time.Now().Add(time.Duration(hours) * time.Hour),
Status: subentities.InvitationStatusPending,
@@ -143,6 +153,10 @@ func (s *SubordinateApplicationServiceImpl) CreateInvitation(ctx context.Context
if err := s.subRepo.CreateInvitation(ctx, inv); err != nil {
return nil, err
}
return s.buildInvitationResponse(inv, raw)
}
func (s *SubordinateApplicationServiceImpl) buildInvitationResponse(inv *subentities.SubordinateInvitation, raw string) (*responses.CreateInvitationResponse, error) {
base := strings.TrimSpace(os.Getenv("SUB_PORTAL_BASE_URL"))
if base == "" {
base = s.cfg.App.SubPortalBaseURL