下载
Build docker and publish / build (20.15.1) (push) Has been cancelled

This commit is contained in:
2026-01-23 03:48:30 -08:00
parent cd0ef80d15
commit a98fcbfe73
19 changed files with 528 additions and 100 deletions
@@ -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)
})
}