This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/go-playground/validator/v10"
|
||||
"github.com/perfect-panel/server/internal/logic/common"
|
||||
"github.com/perfect-panel/server/internal/svc"
|
||||
"github.com/perfect-panel/server/internal/types"
|
||||
"github.com/perfect-panel/server/pkg/result"
|
||||
"github.com/perfect-panel/server/pkg/xerr"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// GetAppVersionHandler 获取 App 版本
|
||||
func GetAppVersionHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) {
|
||||
return func(c *gin.Context) {
|
||||
var req types.GetAppVersionRequest
|
||||
if err := c.ShouldBind(&req); err != nil {
|
||||
result.HttpResult(c, nil, errors.Wrapf(xerr.NewErrCode(xerr.InvalidParams), "parse params failed: %v", err))
|
||||
return
|
||||
}
|
||||
validate := validator.New()
|
||||
if err := validate.Struct(&req); err != nil {
|
||||
result.HttpResult(c, nil, errors.Wrapf(xerr.NewErrCode(xerr.InvalidParams), "validate params failed: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
l := common.NewGetAppVersionLogic(c.Request.Context(), svcCtx)
|
||||
resp, err := l.GetAppVersion(&req)
|
||||
result.HttpResult(c, resp, err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/go-playground/validator/v10"
|
||||
"github.com/perfect-panel/server/internal/logic/common"
|
||||
"github.com/perfect-panel/server/internal/svc"
|
||||
"github.com/perfect-panel/server/internal/types"
|
||||
"github.com/perfect-panel/server/pkg/result"
|
||||
"github.com/perfect-panel/server/pkg/xerr"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// GetDownloadLinkHandler 获取下载链接
|
||||
func GetDownloadLinkHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) {
|
||||
return func(c *gin.Context) {
|
||||
var req types.GetDownloadLinkRequest
|
||||
if err := c.ShouldBind(&req); err != nil {
|
||||
result.HttpResult(c, nil, errors.Wrapf(xerr.NewErrCode(xerr.InvalidParams), "parse params failed: %v", err))
|
||||
return
|
||||
}
|
||||
validate := validator.New()
|
||||
if err := validate.Struct(&req); err != nil {
|
||||
result.HttpResult(c, nil, errors.Wrapf(xerr.NewErrCode(xerr.InvalidParams), "validate params failed: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
l := common.NewGetDownloadLinkLogic(c.Request.Context(), svcCtx)
|
||||
resp, err := l.GetDownloadLink(&req)
|
||||
result.HttpResult(c, resp, err)
|
||||
}
|
||||
}
|
||||
@@ -2,11 +2,16 @@ package user
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/perfect-panel/server/internal/config"
|
||||
"github.com/perfect-panel/server/internal/logic/public/user"
|
||||
"github.com/perfect-panel/server/internal/svc"
|
||||
"github.com/perfect-panel/server/pkg/constant"
|
||||
"github.com/perfect-panel/server/pkg/result"
|
||||
)
|
||||
|
||||
@@ -27,7 +32,7 @@ func DeleteAccountHandler(serverCtx *svc.ServiceContext) gin.HandlerFunc {
|
||||
}
|
||||
|
||||
// 校验邮箱验证码
|
||||
if err := verifyEmailCode(c.Request.Context(), serverCtx, req.Code); err != nil {
|
||||
if err := verifyEmailCode(c.Request.Context(), serverCtx, req.Email, req.Code); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
@@ -43,12 +48,43 @@ func DeleteAccountHandler(serverCtx *svc.ServiceContext) gin.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
// CacheKeyPayload 验证码缓存结构
|
||||
type CacheKeyPayload struct {
|
||||
Code string `json:"code"`
|
||||
LastAt int64 `json:"lastAt"`
|
||||
}
|
||||
|
||||
func verifyEmailCode(ctx context.Context, serverCtx *svc.ServiceContext, code string) error {
|
||||
// verifyEmailCode 校验邮箱验证码
|
||||
// 支持 DeleteAccount 和 Security 两种场景的验证码
|
||||
func verifyEmailCode(ctx context.Context, serverCtx *svc.ServiceContext, email string, code string) error {
|
||||
// 尝试多种场景的验证码
|
||||
scenes := []string{constant.DeleteAccount.String(), constant.Security.String()}
|
||||
var verified bool
|
||||
var cacheKeyUsed string
|
||||
var payload CacheKeyPayload
|
||||
|
||||
for _, scene := range scenes {
|
||||
cacheKey := fmt.Sprintf("%s:%s:%s", config.AuthCodeCacheKey, scene, email)
|
||||
value, err := serverCtx.Redis.Get(ctx, cacheKey).Result()
|
||||
if err != nil || value == "" {
|
||||
continue
|
||||
}
|
||||
if err := json.Unmarshal([]byte(value), &payload); err != nil {
|
||||
continue
|
||||
}
|
||||
// 检查验证码是否匹配且未过期
|
||||
if payload.Code == code && time.Now().Unix()-payload.LastAt <= serverCtx.Config.VerifyCode.ExpireTime {
|
||||
verified = true
|
||||
cacheKeyUsed = cacheKey
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !verified {
|
||||
return fmt.Errorf("verification code error or expired")
|
||||
}
|
||||
|
||||
// 验证成功后删除缓存
|
||||
serverCtx.Redis.Del(ctx, cacheKeyUsed)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/alicebob/miniredis/v2"
|
||||
"github.com/perfect-panel/server/internal/config"
|
||||
"github.com/perfect-panel/server/internal/svc"
|
||||
"github.com/perfect-panel/server/pkg/constant"
|
||||
"github.com/redis/go-redis/v9"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestVerifyEmailCode(t *testing.T) {
|
||||
// 1. Setup Miniredis
|
||||
mr, err := miniredis.Run()
|
||||
assert.NoError(t, err)
|
||||
defer mr.Close()
|
||||
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: mr.Addr(),
|
||||
})
|
||||
|
||||
// 2. Setup ServiceContext
|
||||
serverCtx := &svc.ServiceContext{
|
||||
Redis: rdb,
|
||||
Config: config.Config{
|
||||
VerifyCode: config.VerifyCode{
|
||||
ExpireTime: 300, // 5 minutes validity
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
email := "test@example.com"
|
||||
code := "123456"
|
||||
|
||||
// Helper to set code in redis
|
||||
setCode := func(scene string, c string, lastAt int64) {
|
||||
payload := CacheKeyPayload{
|
||||
Code: c,
|
||||
LastAt: lastAt,
|
||||
}
|
||||
val, _ := json.Marshal(payload)
|
||||
key := fmt.Sprintf("%s:%s:%s", config.AuthCodeCacheKey, scene, email)
|
||||
rdb.Set(ctx, key, string(val), time.Hour)
|
||||
}
|
||||
|
||||
t.Run("Success_DeleteAccountScene", func(t *testing.T) {
|
||||
mr.FlushAll()
|
||||
setCode(constant.DeleteAccount.String(), code, time.Now().Unix())
|
||||
|
||||
err := verifyEmailCode(ctx, serverCtx, email, code)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Verify key is deleted
|
||||
key := fmt.Sprintf("%s:%s:%s", config.AuthCodeCacheKey, constant.DeleteAccount.String(), email)
|
||||
exists := rdb.Exists(ctx, key).Val()
|
||||
assert.Equal(t, int64(0), exists)
|
||||
})
|
||||
|
||||
t.Run("Success_SecurityScene", func(t *testing.T) {
|
||||
mr.FlushAll()
|
||||
setCode(constant.Security.String(), code, time.Now().Unix())
|
||||
|
||||
err := verifyEmailCode(ctx, serverCtx, email, code)
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("Fail_WrongCode", func(t *testing.T) {
|
||||
mr.FlushAll()
|
||||
setCode(constant.DeleteAccount.String(), code, time.Now().Unix())
|
||||
|
||||
err := verifyEmailCode(ctx, serverCtx, email, "wrong")
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "verification code error")
|
||||
})
|
||||
|
||||
t.Run("Fail_Expired", func(t *testing.T) {
|
||||
mr.FlushAll()
|
||||
// Set time to 301 seconds ago (expired)
|
||||
expiredTime := time.Now().Add(-301 * time.Second).Unix()
|
||||
setCode(constant.DeleteAccount.String(), code, expiredTime)
|
||||
|
||||
err := verifyEmailCode(ctx, serverCtx, email, code)
|
||||
assert.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("Fail_NoCode", func(t *testing.T) {
|
||||
mr.FlushAll()
|
||||
err := verifyEmailCode(ctx, serverCtx, email, code)
|
||||
assert.Error(t, err)
|
||||
})
|
||||
}
|
||||
@@ -647,6 +647,12 @@ func RegisterHandlers(router *gin.Engine, serverCtx *svc.ServiceContext) {
|
||||
// Get Client
|
||||
commonGroupRouter.GET("/client", common.GetClientHandler(serverCtx))
|
||||
|
||||
// Get Download Link
|
||||
commonGroupRouter.GET("/client/download", common.GetDownloadLinkHandler(serverCtx))
|
||||
|
||||
// Get App Version
|
||||
commonGroupRouter.GET("/app/version", common.GetAppVersionHandler(serverCtx))
|
||||
|
||||
// Submit contact info
|
||||
commonGroupRouter.POST("/contact", common.SubmitContactHandler(serverCtx))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user