package agent import ( "context" "database/sql" "fmt" "strconv" "strings" "time" "ycc-server/app/main/model" "ycc-server/common/ctxdata" "ycc-server/common/globalkey" "ycc-server/common/tool" "ycc-server/common/xerr" "ycc-server/pkg/lzkit/crypto" "github.com/pkg/errors" "ycc-server/app/main/api/internal/svc" "ycc-server/app/main/api/internal/types" "github.com/google/uuid" "github.com/zeromicro/go-zero/core/logx" ) type GetInviteLinkLogic struct { logx.Logger ctx context.Context svcCtx *svc.ServiceContext } func NewGetInviteLinkLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetInviteLinkLogic { return &GetInviteLinkLogic{ Logger: logx.WithContext(ctx), ctx: ctx, svcCtx: svcCtx, } } func (l *GetInviteLinkLogic) GetInviteLink(req *types.GetInviteLinkReq) (resp *types.GetInviteLinkResp, err error) { // 1. 获取当前代理信息 userID, err := ctxdata.GetUidFromCtx(l.ctx) if err != nil { return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取用户信息失败, %v", err) } agent, err := l.svcCtx.AgentModel.FindOneByUserId(l.ctx, userID) if err != nil { if errors.Is(err, model.ErrNotFound) { return nil, errors.Wrapf(xerr.NewErrMsg("您不是代理"), "") } return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询代理信息失败, %v", err) } // 2. 验证邀请码参数 if req.InviteCode == "" { return nil, errors.Wrapf(xerr.NewErrMsg("邀请码不能为空"), "") } ref := strings.TrimSpace(req.InviteCode) var inviteCodeRecord *model.AgentInviteCode inviteCodeRecord, err = l.svcCtx.AgentInviteCodeModel.FindOneByCode(l.ctx, ref) if err != nil && !errors.Is(err, model.ErrNotFound) { return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询邀请码失败, %v", err) } if inviteCodeRecord != nil { if !inviteCodeRecord.AgentId.Valid || inviteCodeRecord.AgentId.String != agent.Id { return nil, errors.Wrapf(xerr.NewErrMsg("无权使用此邀请码"), "") } if inviteCodeRecord.Status != 0 { if inviteCodeRecord.Status == 1 { return nil, errors.Wrapf(xerr.NewErrMsg("邀请码已使用"), "") } return nil, errors.Wrapf(xerr.NewErrMsg("邀请码已失效"), "") } if inviteCodeRecord.ExpireTime.Valid && inviteCodeRecord.ExpireTime.Time.Before(time.Now()) { return nil, errors.Wrapf(xerr.NewErrMsg("邀请码已过期"), "") } } else { if codeVal, parseErr := strconv.ParseInt(ref, 10, 64); parseErr == nil && codeVal > 0 { if agent.AgentCode != codeVal { return nil, errors.Wrapf(xerr.NewErrMsg("无权使用此邀请码"), "") } } else { encMobile, _ := crypto.EncryptMobile(ref, l.svcCtx.Config.Encrypt.SecretKey) if encMobile != agent.Mobile { return nil, errors.Wrapf(xerr.NewErrMsg("邀请信息无效"), "") } } } // 7. 使用默认target_path(如果未提供) targetPath := req.TargetPath if targetPath == "" { targetPath = fmt.Sprintf("/register?invite_code=%s", ref) } shortLink, err := l.createInviteShortLink(func() string { if inviteCodeRecord != nil { return inviteCodeRecord.Id } return "" }(), ref, targetPath) if err != nil { return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "生成短链失败, %v", err) } return &types.GetInviteLinkResp{ InviteLink: shortLink, }, nil } // createInviteShortLink 创建邀请好友短链 func (l *GetInviteLinkLogic) createInviteShortLink(inviteCodeId string, inviteCode, targetPath string) (string, error) { promotionConfig := l.svcCtx.Config.Promotion // 如果没有配置推广域名,返回空字符串(保持向后兼容) if promotionConfig.PromotionDomain == "" { l.Errorf("推广域名未配置,返回空链接") return "", nil } // 先查询是否已存在短链 existingShortLink, err := l.svcCtx.AgentShortLinkModel.FindOneByInviteCodeIdTypeDelState(l.ctx, sql.NullString{String: inviteCodeId, Valid: inviteCodeId != ""}, 2, globalkey.DelStateNo) if err == nil && existingShortLink != nil { // 已存在短链,直接返回 return fmt.Sprintf("%s/s/%s", promotionConfig.PromotionDomain, existingShortLink.ShortCode), nil } if err != nil && !errors.Is(err, model.ErrNotFound) { return "", errors.Wrapf(err, "查询短链失败") } // 如果没有邀请码ID(例如使用手机号或代理码),按邀请码字符串尝试查找以避免重复创建 if inviteCodeId == "" && inviteCode != "" { existingByCode, err2 := l.svcCtx.AgentShortLinkModel.FindOneByInviteCodeTypeDelState(l.ctx, sql.NullString{String: inviteCode, Valid: true}, 2, globalkey.DelStateNo) if err2 == nil && existingByCode != nil { return fmt.Sprintf("%s/s/%s", promotionConfig.PromotionDomain, existingByCode.ShortCode), nil } if err2 != nil && !errors.Is(err2, model.ErrNotFound) { return "", errors.Wrapf(err2, "查询短链失败") } } // 生成短链标识(6位随机字符串,大小写字母+数字) var shortCode string maxRetries := 10 // 最大重试次数 for retry := 0; retry < maxRetries; retry++ { shortCode = tool.Krand(6, tool.KC_RAND_KIND_ALL) // 检查短链标识是否已存在 _, err := l.svcCtx.AgentShortLinkModel.FindOneByShortCodeDelState(l.ctx, shortCode, globalkey.DelStateNo) if err != nil { if errors.Is(err, model.ErrNotFound) { // 短链标识不存在,可以使用 break } return "", errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "检查短链标识失败, %v", err) } // 短链标识已存在,继续生成 if retry == maxRetries-1 { return "", errors.Wrapf(xerr.NewErrMsg("生成短链失败,请重试"), "") } } // 创建短链记录(类型:2=邀请好友) shortLink := &model.AgentShortLink{ Id: uuid.NewString(), Type: 2, // 邀请好友 InviteCodeId: sql.NullString{String: inviteCodeId, Valid: inviteCodeId != ""}, InviteCode: sql.NullString{String: inviteCode, Valid: inviteCode != ""}, ShortCode: shortCode, TargetPath: targetPath, PromotionDomain: promotionConfig.PromotionDomain, } _, err = l.svcCtx.AgentShortLinkModel.Insert(l.ctx, nil, shortLink) if err != nil { return "", errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "保存短链失败, %v", err) } return fmt.Sprintf("%s/s/%s", promotionConfig.PromotionDomain, shortCode), nil }