hi-server/internal/logic/common/sendEmailCodeLogic.go
shanshanzhong a98fcbfe73
Some checks failed
Build docker and publish / build (20.15.1) (push) Has been cancelled
下载
2026-01-23 03:48:30 -08:00

151 lines
5.2 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package common
import (
"context"
"encoding/json"
"fmt"
"strings"
"time"
"github.com/hibiken/asynq"
"github.com/perfect-panel/server/internal/config"
"github.com/perfect-panel/server/pkg/constant"
"github.com/perfect-panel/server/pkg/limit"
"github.com/perfect-panel/server/pkg/random"
"github.com/pkg/errors"
"gorm.io/gorm"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/logger"
"github.com/perfect-panel/server/pkg/xerr"
queue "github.com/perfect-panel/server/queue/types"
)
type SendEmailCodeLogic struct {
logger.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
const (
IntervalTime = 60
)
type VerifyTemplate struct {
Type uint8
SiteLogo string
SiteName string
Expire uint8
Code string
}
type CacheKeyPayload struct {
Code string `json:"code"`
LastAt int64 `json:"lastAt"`
}
// NewSendEmailCodeLogic Get verification code
func NewSendEmailCodeLogic(ctx context.Context, svcCtx *svc.ServiceContext) *SendEmailCodeLogic {
return &SendEmailCodeLogic{
Logger: logger.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *SendEmailCodeLogic) SendEmailCode(req *types.SendCodeRequest) (resp *types.SendCodeResponse, err error) {
req.Email = strings.ToLower(strings.TrimSpace(req.Email))
// Check if there is Redis in the code
cacheKey := fmt.Sprintf("%s:%s:%s", config.AuthCodeCacheKey, constant.ParseVerifyType(req.Type), req.Email)
// Check if the limit is exceeded of current request
limiter := limit.NewPeriodLimit(60, 1, l.svcCtx.Redis, fmt.Sprintf("%s:%s:%s", config.SendIntervalKeyPrefix, "email", constant.ParseVerifyType(req.Type)))
permit, err := limiter.Take(req.Email)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "Failed to take limit")
}
if !limiter.ParsePermitState(permit) {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.TooManyRequests), "send email too many requests")
}
// Check if the limit is exceeded of today
permit, err = l.svcCtx.AuthLimiter.Take(fmt.Sprintf("%s:%s:%s", "email", constant.ParseVerifyType(req.Type), req.Email))
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "Failed to take limit")
}
if !l.svcCtx.AuthLimiter.ParsePermitState(permit) {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.TodaySendCountExceedsLimit), "send email too many requests")
}
m, err := l.svcCtx.UserModel.FindUserAuthMethodByOpenID(l.ctx, "email", req.Email)
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "FindUserAuthMethodByOpenID error")
}
if constant.ParseVerifyType(req.Type) == constant.Register && m.Id > 0 {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.UserExist), "mobile already bind")
}
var payload CacheKeyPayload
var taskPayload queue.SendEmailPayload
// Generate verification code
code := random.Key(6, 0)
taskPayload.Type = queue.EmailTypeVerify
taskPayload.Email = req.Email
taskPayload.Subject = "Verification code"
expireTime := l.svcCtx.Config.VerifyCode.ExpireTime
if expireTime == 0 {
expireTime = 900
}
fmt.Printf("expireTime: %v\n", expireTime)
expireMinutes := expireTime / 60
taskPayload.Content = map[string]interface{}{
"Type": req.Type,
"SiteLogo": l.svcCtx.Config.Site.SiteLogo,
"SiteName": l.svcCtx.Config.Site.SiteName,
"Expire": expireMinutes,
"Code": code,
}
// Override for account deletion
if constant.ParseVerifyType(req.Type) == constant.DeleteAccount {
taskPayload.Subject = "注销账号验证"
taskPayload.Content["Content"] = fmt.Sprintf("您正在申请注销账号,验证码为:%s有效期 %d 分钟。如非本人操作,请忽略。", code, expireMinutes)
}
// Save to Redis
payload = CacheKeyPayload{
Code: code,
LastAt: time.Now().Unix(),
}
// Marshal the payload
val, _ := json.Marshal(payload)
if err = l.svcCtx.Redis.Set(l.ctx, cacheKey, string(val), time.Second*time.Duration(l.svcCtx.Config.VerifyCode.ExpireTime)).Err(); err != nil {
l.Errorw("[SendEmailCode]: Redis Error", logger.Field("error", err.Error()), logger.Field("cacheKey", cacheKey))
return nil, errors.Wrap(xerr.NewErrCode(xerr.ERROR), "Failed to set verification code")
}
// Marshal the task payload
payloadBuy, err := json.Marshal(taskPayload)
if err != nil {
l.Errorw("[SendEmailCode]: Marshal Error", logger.Field("error", err.Error()))
return nil, errors.Wrap(xerr.NewErrCode(xerr.ERROR), "Failed to marshal task payload")
}
// Create a queue task
task := asynq.NewTask(queue.ForthwithSendEmail, payloadBuy, asynq.MaxRetry(3))
// Enqueue the task
taskInfo, err := l.svcCtx.Queue.Enqueue(task)
if err != nil {
l.Errorw("[SendEmailCode]: Enqueue Error", logger.Field("error", err.Error()), logger.Field("payload", string(payloadBuy)))
return nil, errors.Wrap(xerr.NewErrCode(xerr.ERROR), "Failed to enqueue task")
}
l.Infow("[SendEmailCode]: Enqueue Success", logger.Field("taskID", taskInfo.ID), logger.Field("payload", string(payloadBuy)))
if l.svcCtx.Config.Model == constant.DevMode {
return &types.SendCodeResponse{
Code: payload.Code,
Status: true,
}, nil
} else {
return &types.SendCodeResponse{
Status: true,
}, nil
}
}