183 lines
6.2 KiB
Go
183 lines
6.2 KiB
Go
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
|
||
}
|