193 lines
5.9 KiB
Go
193 lines
5.9 KiB
Go
package member
|
|
|
|
import (
|
|
"biz-member-gateway/internal/domain"
|
|
"biz-member-gateway/internal/svc"
|
|
"biz-member-gateway/internal/types"
|
|
"bytes"
|
|
|
|
"code.30cm.net/digimon/app-cloudep-member-server/pkg/domain/member"
|
|
"code.30cm.net/digimon/library-go/errs/code"
|
|
|
|
"context"
|
|
"fmt"
|
|
"html/template"
|
|
|
|
"code.30cm.net/digimon/library-go/errs"
|
|
|
|
ntpl "code.30cm.net/digimon/app-cloudep-notification-service/pkg/domain/template"
|
|
memberProto "code.30cm.net/digimon/proto-all/pkg/member"
|
|
notificationProto "code.30cm.net/digimon/proto-all/pkg/notification"
|
|
"github.com/zeromicro/go-zero/core/logx"
|
|
)
|
|
|
|
type ForgetPasswordCodeLogic struct {
|
|
logx.Logger
|
|
ctx context.Context
|
|
svcCtx *svc.ServiceContext
|
|
}
|
|
|
|
// NewForgetPasswordCodeLogic 發送忘記密碼驗證
|
|
func NewForgetPasswordCodeLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ForgetPasswordCodeLogic {
|
|
return &ForgetPasswordCodeLogic{
|
|
Logger: logx.WithContext(ctx),
|
|
ctx: ctx,
|
|
svcCtx: svcCtx,
|
|
}
|
|
}
|
|
|
|
func (l *ForgetPasswordCodeLogic) ForgetPasswordCode(req *types.ForgetPasswordCodeReq) (resp *types.RespOK, err error) {
|
|
// 1. 驗證並標準化帳號
|
|
acc, err := normalizeAccount(req.AccountType, req.Account)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// 2. 統一檢查冷卻時間與平台正確性
|
|
if err = l.checkAccountValidity(acc); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// 3. 生成驗證碼
|
|
vcode, err := l.svcCtx.MemberRPC.GenerateRefreshCode(l.ctx, &memberProto.GenerateRefreshCodeReq{
|
|
Account: acc,
|
|
CodeType: int32(member.GenerateCodeTypeForgetPassword),
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// 4. 取得用戶資訊
|
|
uidInfo, err := l.svcCtx.MemberRPC.GetUIDByAccount(l.ctx, &memberProto.GetUIDByAccountReq{Account: acc})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
userInfo, err := l.svcCtx.MemberRPC.GetUserInfo(l.ctx, &memberProto.GetUserInfoReq{Uid: uidInfo.GetUid()})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// 5. 發送驗證碼
|
|
nickname := getEmailShowName(userInfo)
|
|
if err = l.sendVerificationCode(req.AccountType, acc, userInfo.Data, vcode.Data.VerifyCode, nickname); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// 6. 設置 Redis 驗證碼鍵值
|
|
rk := domain.GetSendCodeRedisKey(fmt.Sprintf("%s:%d", acc, member.GenerateCodeTypeForgetPassword))
|
|
l.setRedisKeyWithExpiry(rk, vcode.Data.VerifyCode, int(l.svcCtx.Config.Member.ForgetTimeOutInSec))
|
|
|
|
return &types.RespOK{}, nil
|
|
}
|
|
|
|
// normalizeAccount 驗證並標準化帳號
|
|
func normalizeAccount(accountType, account string) (string, error) {
|
|
at := member.GetAccountTypeByCode(accountType)
|
|
switch at {
|
|
case member.AccountTypePhone:
|
|
phone, ok := normalizeTaiwanMobile(account)
|
|
if !ok {
|
|
return "", errs.InvalidFormat("phone number is invalid")
|
|
}
|
|
return phone, nil
|
|
case member.AccountTypeMail:
|
|
if !isValidEmail(account) {
|
|
return "", errs.InvalidFormat("email is invalid")
|
|
}
|
|
return account, nil
|
|
default:
|
|
return "", errs.InvalidFormat("unsupported account type")
|
|
}
|
|
}
|
|
|
|
// checkAccountValidity 同時檢查發送冷卻與平台正確性
|
|
func (l *ForgetPasswordCodeLogic) checkAccountValidity(acc string) error {
|
|
// 檢查發送冷卻
|
|
rk := domain.GetSendCodeRedisKey(fmt.Sprintf("%s:%d", acc, member.GenerateCodeTypeForgetPassword))
|
|
if cached, err := l.svcCtx.Redis.GetCtx(l.ctx, rk); err != nil || cached != "" {
|
|
return errs.InvalidRange("verification code already sent, please wait for system to send again")
|
|
}
|
|
|
|
// 檢查平台是否正確(只允許平台帳號進行忘記密碼操作)
|
|
accountInfo, err := l.svcCtx.MemberRPC.GetUserAccountInfo(l.ctx, &memberProto.GetUIDByAccountReq{Account: acc})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if accountInfo.Data.Platform != member.Digimon.ToInt64() {
|
|
return errs.InvalidResourceState("failed to send verify code since platform not correct")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// sendVerificationCode 根據帳號類型發送驗證碼
|
|
func (l *ForgetPasswordCodeLogic) sendVerificationCode(accountType, acc string, info *memberProto.UserInfo, verifyCode, nickname string) error {
|
|
switch member.GetAccountTypeByCode(accountType) {
|
|
case member.AccountTypePhone:
|
|
// TODO: 傳送簡訊
|
|
fmt.Printf("SMS Template: %s\n", verifyCode)
|
|
case member.AccountTypeMail:
|
|
return l.sendVerificationEmail(acc, info, verifyCode, nickname)
|
|
default:
|
|
return errs.InvalidResourceState("unsupported account type")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type EmailTmpInfo struct {
|
|
Username string
|
|
VerifyCode string
|
|
}
|
|
|
|
// sendVerificationEmail 發送驗證郵件
|
|
func (l *ForgetPasswordCodeLogic) sendVerificationEmail(recipientEmail string, info *memberProto.UserInfo, verifyCode, nickname string) error {
|
|
tpl, err := l.svcCtx.NotificationRPC.GetStaticTemplate(l.ctx, ¬ificationProto.TemplateReq{
|
|
Language: info.Language,
|
|
TemplateId: string(ntpl.ForgetPasswordVerify),
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
tmpl, err := template.New("ForgetPasswordEmail").Parse(tpl.GetBody())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
emailParams := EmailTmpInfo{Username: nickname, VerifyCode: verifyCode}
|
|
var buf bytes.Buffer
|
|
if err := tmpl.Execute(&buf, emailParams); err != nil {
|
|
return err
|
|
}
|
|
|
|
_, err = l.svcCtx.NotificationRPC.SendMail(l.ctx, ¬ificationProto.SendMailReq{
|
|
To: recipientEmail,
|
|
Subject: tpl.Title,
|
|
Body: buf.String(),
|
|
From: l.svcCtx.Config.MailSender,
|
|
})
|
|
return err
|
|
}
|
|
|
|
// getEmailShowName 取得寄信用的顯示名稱
|
|
func getEmailShowName(info *memberProto.GetUserInfoResp) string {
|
|
if info.Data.FullName != nil {
|
|
return *info.Data.FullName
|
|
}
|
|
if info.Data.NickName != nil {
|
|
return *info.Data.NickName
|
|
}
|
|
return info.Data.Uid
|
|
}
|
|
|
|
// setRedisKeyWithExpiry 設置 Redis 鍵與過期時間
|
|
func (l *ForgetPasswordCodeLogic) setRedisKeyWithExpiry(rk, verifyCode string, expiry int) {
|
|
if status, err := l.svcCtx.Redis.SetnxExCtx(l.ctx, rk, verifyCode, expiry); err != nil || !status {
|
|
_ = errs.DatabaseErrorWithScopeL(code.CloudEPMember, domain.FailedToSetVerifyCodeErrorCode,
|
|
logx.WithContext(l.ctx),
|
|
[]logx.LogField{
|
|
{Key: "redisKey", Value: rk},
|
|
{Key: "error", Value: err.Error()},
|
|
}, "failed to set redis expire").Wrap(err)
|
|
}
|
|
}
|