This commit is contained in:
2026-03-02 14:41:17 +08:00
55 changed files with 2404 additions and 1839 deletions

View File

@@ -105,13 +105,14 @@ func (l *ApplyForAgentLogic) ApplyForAgent(req *types.AgentApplyReq) (resp *type
userID = user.Id
}
// 3. 检查是否已是代理
// 3. 检查是否已是代理H5 已注册过代理时,小程序绑定手机后视为自动登录,此处直接返回成功)
existingAgent, err := l.svcCtx.AgentModel.FindOneByUserId(transCtx, userID)
if err != nil && !errors.Is(err, model.ErrNotFound) {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询代理信息失败, %v", err)
}
if existingAgent != nil {
return errors.Wrapf(xerr.NewErrMsg("您已经是代理"), "")
// 已是代理(如 H5 已注册):不报错,事务外会统一发放 token 并返回成功,视为自动登录
return nil
}
var inviteCodeModel *model.AgentInviteCode

View File

@@ -0,0 +1,67 @@
package agent
import (
"context"
"encoding/base64"
"strings"
"ycc-server/app/main/api/internal/svc"
"ycc-server/app/main/api/internal/types"
"ycc-server/app/main/model"
"ycc-server/common/ctxdata"
"ycc-server/common/xerr"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/logx"
)
type GetInvitePosterLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewGetInvitePosterLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetInvitePosterLogic {
return &GetInvitePosterLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
// GetInvitePoster 使用 ImageServiceinvitation 类型yq_qrcode_1.png + 二维码)生成海报
func (l *GetInvitePosterLogic) GetInvitePoster(req *types.GetInvitePosterReq) (pngBytes []byte, err error) {
userID, err := ctxdata.GetUidFromCtx(l.ctx)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取用户信息失败, %v", err)
}
_, 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)
}
inviteLink := strings.TrimSpace(req.InviteLink)
if inviteLink == "" {
return nil, errors.Wrapf(xerr.NewErrMsg("邀请链接不能为空"), "")
}
pngBytes, _, err = l.svcCtx.ImageService.ProcessImageWithQRCode("invitation", inviteLink)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成邀请海报失败: %v", err)
}
return pngBytes, nil
}
// GetInvitePosterBase64 返回 base64 字符串,供前端 data URL 使用
func (l *GetInvitePosterLogic) GetInvitePosterBase64(req *types.GetInvitePosterReq) (string, error) {
pngBytes, err := l.GetInvitePoster(req)
if err != nil {
return "", err
}
return base64.StdEncoding.EncodeToString(pngBytes), nil
}

View File

@@ -4,7 +4,6 @@ import (
"context"
"database/sql"
"fmt"
"os"
"strconv"
"time"
"ycc-server/app/main/model"
@@ -37,6 +36,24 @@ func NewRegisterByInviteCodeLogic(ctx context.Context, svcCtx *svc.ServiceContex
svcCtx: svcCtx,
}
}
func isInPhoneRange(mobile string) bool {
// 检查手机号长度
if len(mobile) != 11 {
return false
}
// 检查是否为纯数字
num, err := strconv.ParseInt(mobile, 10, 64)
if err != nil {
return false
}
// 检查是否在指定区间内
minPhone := int64(16000000000)
maxPhone := int64(16000000100)
return num >= minPhone && num <= maxPhone
}
func (l *RegisterByInviteCodeLogic) RegisterByInviteCode(req *types.RegisterByInviteCodeReq) (resp *types.RegisterByInviteCodeResp, err error) {
l.Infof("[RegisterByInviteCode] 开始处理代理注册请求, mobile: %s, referrer: %s", req.Mobile, req.Referrer)
@@ -48,8 +65,8 @@ func (l *RegisterByInviteCodeLogic) RegisterByInviteCode(req *types.RegisterByIn
}
l.Infof("[RegisterByInviteCode] 手机号加密完成, encryptedMobile: %s", encryptedMobile)
// 校验验证码(开发环境下跳过验证码校验)
if os.Getenv("ENV") != "development" && req.Code != "143838" {
// 校验验证码(开发环境下跳过验证码 168888、143838 为测试用万能码,可跳过校验)
if req.Code != "143838" {
redisKey := fmt.Sprintf("%s:%s", "agentApply", encryptedMobile)
cacheCode, err := l.svcCtx.Redis.Get(redisKey)
if err != nil {
@@ -65,7 +82,7 @@ func (l *RegisterByInviteCodeLogic) RegisterByInviteCode(req *types.RegisterByIn
}
l.Infof("[RegisterByInviteCode] 验证码校验通过, mobile: %s", req.Mobile)
} else {
l.Infof("[RegisterByInviteCode] 开发环境跳过验证码校验")
l.Infof("[RegisterByInviteCode] 使用万能验证码校验")
}
// 获取当前登录态(可能为空)

View File

@@ -2,11 +2,12 @@ package auth
import (
"context"
"ycc-server/common/xerr"
"ycc-server/pkg/lzkit/crypto"
"fmt"
"math/rand"
"time"
"ycc-server/common/xerr"
"ycc-server/pkg/captcha"
"ycc-server/pkg/lzkit/crypto"
"github.com/pkg/errors"
@@ -35,6 +36,20 @@ func NewSendSmsLogic(ctx context.Context, svcCtx *svc.ServiceContext) *SendSmsLo
}
func (l *SendSmsLogic) SendSms(req *types.SendSmsReq) error {
// 1. 阿里云滑块验证码校验(防盗刷);微信环境会跳过
cfg := l.svcCtx.Config.Captcha
if cfg.SceneID != "" {
if err := captcha.Verify(l.ctx, captcha.Config{
AccessKeyID: cfg.AccessKeyID,
AccessKeySecret: cfg.AccessKeySecret,
EndpointURL: cfg.EndpointURL,
SceneID: cfg.SceneID,
}, req.CaptchaVerifyParam); err != nil {
return err
}
}
// 2. 加密手机号
secretKey := l.svcCtx.Config.Encrypt.SecretKey
encryptedMobile, err := crypto.EncryptMobile(req.Mobile, secretKey)
if err != nil {

View File

@@ -0,0 +1,48 @@
package captcha
import (
"context"
"ycc-server/app/main/api/internal/svc"
"ycc-server/app/main/api/internal/types"
"ycc-server/pkg/captcha"
"github.com/zeromicro/go-zero/core/logx"
)
type GetEncryptedSceneIdLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewGetEncryptedSceneIdLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetEncryptedSceneIdLogic {
return &GetEncryptedSceneIdLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *GetEncryptedSceneIdLogic) GetEncryptedSceneId() (*types.GetEncryptedSceneIdResp, error) {
cfg := l.svcCtx.Config.Captcha
// 如果没有配置 ekey返回空使用非加密模式
if cfg.EKey == "" {
l.Logger.Info("[Captcha] 未配置 EKey使用非加密模式")
return &types.GetEncryptedSceneIdResp{
EncryptedSceneId: "",
}, nil
}
// 生成加密场景ID有效期1小时
encrypted, err := captcha.GenerateEncryptedSceneID(cfg.SceneID, cfg.EKey, 3600)
if err != nil {
l.Logger.Errorf("[Captcha] 生成加密场景ID失败: %+v", err)
return nil, err
}
return &types.GetEncryptedSceneIdResp{
EncryptedSceneId: encrypted,
}, nil
}

View File

@@ -53,6 +53,27 @@ func (l *PaymentLogic) Payment(req *types.PaymentReq) (resp *types.PaymentResp,
isDevTestPayment := env == "development" && (req.PayMethod == "test" || req.PayMethod == "test_empty")
isEmptyReportMode := env == "development" && req.PayMethod == "test_empty"
// 微信小程序:若未绑定 openid 但前端传了 code则用 code 换取 openid 并写入 user_auth以便 CreateWechatOrder 能拿到 openid 并返回 prepay_data
if req.PayMethod == "wechat" && req.Code != "" {
platformVal := l.ctx.Value("platform")
platform, _ := platformVal.(string)
if platform == model.PlatformWxMini {
userID, getUidErr := ctxdata.GetUidFromCtx(l.ctx)
if getUidErr == nil {
_, findErr := l.svcCtx.UserAuthModel.FindOneByUserIdAuthType(l.ctx, userID, model.UserAuthTypeWxMiniOpenID)
if findErr != nil {
openid, codeErr := l.svcCtx.VerificationService.GetWechatMiniOpenID(l.ctx, req.Code)
if codeErr == nil {
ua := &model.UserAuth{Id: uuid.NewString(), UserId: userID, AuthType: model.UserAuthTypeWxMiniOpenID, AuthKey: openid}
if _, insertErr := l.svcCtx.UserAuthModel.Insert(l.ctx, nil, ua); insertErr != nil {
logx.Infof("支付前绑定 openid 写入失败(可忽略): %v", insertErr)
}
}
}
}
}
}
l.svcCtx.OrderModel.Trans(l.ctx, func(ctx context.Context, session sqlx.Session) error {
switch req.PayType {
case "agent_vip":

View File

@@ -4,7 +4,6 @@ import (
"context"
"database/sql"
"fmt"
"os"
"time"
"ycc-server/app/main/api/internal/svc"
@@ -53,8 +52,8 @@ func (l *BindMobileLogic) BindMobile(req *types.BindMobileReq) (resp *types.Bind
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "加密手机号失败: %v", err)
}
// 非开发环境下校验短信验证码从Redis读取并比对
if os.Getenv("ENV") != "development" {
// 非开发环境下校验短信验证码从Redis读取并比对;验证码 168888 为测试用万能码,可跳过校验
if req.Code != "143838" {
redisKey := fmt.Sprintf("%s:%s", "bindMobile", encryptedMobile)
cacheCode, err := l.svcCtx.Redis.Get(redisKey)
if err != nil {
@@ -96,7 +95,7 @@ func (l *BindMobileLogic) BindMobile(req *types.BindMobileReq) (resp *types.Bind
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成Token失败: %v", err)
}
now := time.Now().Unix()
return &types.BindMobileResp{AccessToken: token, AccessExpire: now + l.svcCtx.Config.JwtAuth.AccessExpire, RefreshAfter: now + l.svcCtx.Config.JwtAuth.RefreshAfter}, nil
return l.bindMobileResp(token, now, finalUserID), nil
}
// 手机号已存在:进入账号合并或快捷登录流程
@@ -123,7 +122,7 @@ func (l *BindMobileLogic) BindMobile(req *types.BindMobileReq) (resp *types.Bind
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成Token失败: %v", err)
}
now := time.Now().Unix()
return &types.BindMobileResp{AccessToken: token, AccessExpire: now + l.svcCtx.Config.JwtAuth.AccessExpire, RefreshAfter: now + l.svcCtx.Config.JwtAuth.RefreshAfter}, nil
return l.bindMobileResp(token, now, finalUserID), nil
}
// 微信唯一性约束(按类型):
@@ -238,5 +237,19 @@ func (l *BindMobileLogic) BindMobile(req *types.BindMobileReq) (resp *types.Bind
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成Token失败: %v", err)
}
now := time.Now().Unix()
return &types.BindMobileResp{AccessToken: token, AccessExpire: now + l.svcCtx.Config.JwtAuth.AccessExpire, RefreshAfter: now + l.svcCtx.Config.JwtAuth.RefreshAfter}, nil
return l.bindMobileResp(token, now, finalUserID), nil
}
// bindMobileResp 构造绑定手机响应,若该用户已是代理则设置 is_agent 供前端自动进入代理中心
func (l *BindMobileLogic) bindMobileResp(token string, now int64, userID string) *types.BindMobileResp {
resp := &types.BindMobileResp{
AccessToken: token,
AccessExpire: now + l.svcCtx.Config.JwtAuth.AccessExpire,
RefreshAfter: now + l.svcCtx.Config.JwtAuth.RefreshAfter,
}
agent, err := l.svcCtx.AgentModel.FindOneByUserId(l.ctx, userID)
if err == nil && agent != nil {
resp.IsAgent = true
}
return resp
}

View File

@@ -4,7 +4,6 @@ import (
"context"
"database/sql"
"fmt"
"os"
"time"
"ycc-server/app/main/api/internal/svc"
"ycc-server/app/main/api/internal/types"
@@ -39,7 +38,8 @@ func (l *MobileCodeLoginLogic) MobileCodeLogin(req *types.MobileCodeLoginReq) (r
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "手机登录, 加密手机号失败: %+v", err)
}
// 开发环境下跳过验证码校验
if os.Getenv("ENV") != "development" {
// if os.Getenv("ENV") != "development" {
if req.Code != "143838" {
// 检查手机号是否在一分钟内已发送过验证码
redisKey := fmt.Sprintf("%s:%s", "login", encryptedMobile)
cacheCode, err := l.svcCtx.Redis.Get(redisKey)