From 8bc8e81e95c1a1c1536493741e161929db9fb974 Mon Sep 17 00:00:00 2001 From: shanshanzhong Date: Wed, 6 May 2026 05:42:48 -0700 Subject: [PATCH] =?UTF-8?q?=E8=A1=A5=E5=8D=95=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../public/recovery/recoverOrderLogic.go | 64 ++++++++++++------- internal/recovery/config.go | 6 ++ public/order-recovery.html | 35 ++++++++-- 3 files changed, 76 insertions(+), 29 deletions(-) diff --git a/internal/logic/public/recovery/recoverOrderLogic.go b/internal/logic/public/recovery/recoverOrderLogic.go index 1e6c13c..7c1db26 100644 --- a/internal/logic/public/recovery/recoverOrderLogic.go +++ b/internal/logic/public/recovery/recoverOrderLogic.go @@ -20,13 +20,17 @@ import ( "github.com/perfect-panel/server/pkg/logger" "github.com/perfect-panel/server/pkg/tool" "github.com/perfect-panel/server/pkg/uuidx" - "github.com/perfect-panel/server/pkg/xerr" - pkgerrors "github.com/pkg/errors" "gorm.io/gorm" "gorm.io/gorm/clause" ) -const genericRecoveryError = "order recovery information is invalid" +const ( + recoverySuccessMessage = "恢复完成,请使用该邮箱验证码登录查看订阅。" + recoveryAlreadyDoneMessage = "该订单已恢复,请使用恢复时绑定的邮箱登录查看订阅。" + recoveryInvalidOrderMessage = "订单信息无效,请确认订单号是否正确。" + recoveryInvalidCodeMessage = "邮箱验证码错误或已过期,请重新获取验证码。" + recoverySystemErrorMessage = "恢复暂时失败,请稍后再试或联系客服处理。" +) type RecoverOrderLogic struct { logger.Logger @@ -48,45 +52,51 @@ func (l *RecoverOrderLogic) RecoverOrder(req *types.RecoverOrderRequest) (*types code := strings.TrimSpace(req.Code) if orderNo == "" || email == "" || code == "" { - return nil, pkgerrors.Wrapf(xerr.NewErrCode(xerr.InvalidParams), genericRecoveryError) + return recoveryFailed(recoveryInvalidOrderMessage), nil } if err := l.verifyEmailCode(email, code); err != nil { - return nil, err + return recoveryFailed(recoveryInvalidCodeMessage), nil } item, ok, err := recoveryconfig.FindOrder(orderNo) if err != nil { l.Errorw("[RecoverOrder] load recovery config failed", logger.Field("error", err.Error())) - return nil, pkgerrors.Wrapf(xerr.NewErrCode(xerr.ERROR), genericRecoveryError) + return recoveryFailed(recoverySystemErrorMessage), nil } if !ok { - return nil, pkgerrors.Wrapf(xerr.NewErrCode(xerr.InvalidParams), genericRecoveryError) + return recoveryFailed(recoveryInvalidOrderMessage), nil } if item.OrderStatus != "" && item.OrderStatus != "success" { - return nil, pkgerrors.Wrapf(xerr.NewErrCode(xerr.InvalidParams), genericRecoveryError) + return recoveryFailed(recoveryInvalidOrderMessage), nil } if item.Quantity <= 0 || item.OrderMoneyCents <= 0 { - return nil, pkgerrors.Wrapf(xerr.NewErrCode(xerr.InvalidParams), genericRecoveryError) + return recoveryFailed(recoveryInvalidOrderMessage), nil + } + claimOrderNo := item.OutOrderNo + if claimOrderNo == "" { + claimOrderNo = item.OrderNo } sub, err := l.svcCtx.SubscribeModel.FindOne(l.ctx, item.SubscribeId) if err != nil { l.Errorw("[RecoverOrder] subscribe not found", logger.Field("subscribe_id", item.SubscribeId), logger.Field("error", err.Error())) - return nil, pkgerrors.Wrapf(xerr.NewErrCode(xerr.InvalidParams), genericRecoveryError) + return recoveryFailed(recoverySystemErrorMessage), nil } var ( - claim recoverymodel.OrderRecoveryClaim - order ordermodel.Order - userSub usermodel.Subscribe + claim recoverymodel.OrderRecoveryClaim + order ordermodel.Order + userSub usermodel.Subscribe + alreadyRecovered bool ) err = l.svcCtx.DB.WithContext(l.ctx).Transaction(func(tx *gorm.DB) error { if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}). Model(&recoverymodel.OrderRecoveryClaim{}). - Where("order_no = ?", orderNo). + Where("order_no = ?", claimOrderNo). First(&claim).Error; err == nil { + alreadyRecovered = true return nil } else if !errors.Is(err, gorm.ErrRecordNotFound) { return err @@ -98,7 +108,7 @@ func (l *RecoverOrderLogic) RecoverOrder(req *types.RecoverOrderRequest) (*types } claim = recoverymodel.OrderRecoveryClaim{ - OrderNo: orderNo, + OrderNo: claimOrderNo, Email: email, UserId: u.Id, SubscribeId: item.SubscribeId, @@ -128,7 +138,14 @@ func (l *RecoverOrderLogic) RecoverOrder(req *types.RecoverOrderRequest) (*types }) if err != nil { l.Errorw("[RecoverOrder] recover failed", logger.Field("order_no", orderNo), logger.Field("error", err.Error())) - return nil, pkgerrors.Wrapf(xerr.NewErrCode(xerr.DatabaseInsertError), genericRecoveryError) + return recoveryFailed(recoverySystemErrorMessage), nil + } + + if alreadyRecovered { + return &types.RecoverOrderResponse{ + Success: true, + Message: recoveryAlreadyDoneMessage, + }, nil } l.clearCaches(&userSub, item.SubscribeId, email) @@ -137,10 +154,17 @@ func (l *RecoverOrderLogic) RecoverOrder(req *types.RecoverOrderRequest) (*types return &types.RecoverOrderResponse{ Success: true, - Message: "recovery completed, please login with your email verification code", + Message: recoverySuccessMessage, }, nil } +func recoveryFailed(message string) *types.RecoverOrderResponse { + return &types.RecoverOrderResponse{ + Success: false, + Message: message, + } +} + func (l *RecoverOrderLogic) createRecoveredOrder(tx *gorm.DB, userId int64, item recoveryconfig.Order, sub *subscribemodel.Subscribe) (ordermodel.Order, error) { var existing ordermodel.Order orderNo := item.OutOrderNo @@ -219,10 +243,6 @@ func (l *RecoverOrderLogic) createOrExtendSubscribe(tx *gorm.DB, userId int64, o existing.ExpireTime = base.Add(time.Duration(item.Quantity) * 24 * time.Hour) existing.Status = 1 existing.FinishedAt = nil - existing.NodeGroupId = sub.NodeGroupId - existing.Traffic = sub.Traffic - existing.Download = 0 - existing.Upload = 0 if strings.TrimSpace(item.Note) != "" { existing.Note = strings.TrimSpace(item.Note) } @@ -275,7 +295,7 @@ func (l *RecoverOrderLogic) verifyEmailCode(email string, code string) error { return err } if resp == nil || !resp.Status { - return pkgerrors.Wrapf(xerr.NewErrCode(xerr.InvalidParams), genericRecoveryError) + return errors.New("invalid verification code") } return nil } diff --git a/internal/recovery/config.go b/internal/recovery/config.go index f4f65b7..33a0b63 100644 --- a/internal/recovery/config.go +++ b/internal/recovery/config.go @@ -99,6 +99,12 @@ func loadOrders() (map[string]Order, error) { return nil, fmt.Errorf("duplicate recovery out_order_no %s", item.OutOrderNo) } result[item.OutOrderNo] = item + if item.OrderNo != "" && item.OrderNo != item.OutOrderNo { + if _, exists := result[item.OrderNo]; exists { + return nil, fmt.Errorf("duplicate recovery order_no %s", item.OrderNo) + } + result[item.OrderNo] = item + } } return result, nil } diff --git a/public/order-recovery.html b/public/order-recovery.html index e44fd31..aec22f4 100644 --- a/public/order-recovery.html +++ b/public/order-recovery.html @@ -277,9 +277,22 @@ const emailInput = document.getElementById("email"); const orderInput = document.getElementById("order-no"); const codeInput = document.getElementById("code"); + const API_BASE = "https://tapi.hifast.biz"; let countdownTimer = null; + const ERROR_MESSAGE_MAP = { + "Param Error": "请检查订单号、邮箱和验证码是否填写正确。", + "Verify code error": "邮箱验证码错误或已过期,请重新获取验证码。", + "Too Many Requests": "验证码发送太频繁,请稍后再试。", + "This account has reached the limit of sending times today": + "该邮箱今天获取验证码次数已达上限,请明天再试。" + }; + + function friendlyMessage(message, fallback) { + return ERROR_MESSAGE_MAP[message] || message || fallback; + } + function showStatus(type, message) { statusBox.className = `status show ${type}`; statusBox.textContent = message; @@ -296,7 +309,7 @@ } async function postJson(path, body) { - const response = await fetch(path, { + const response = await fetch(`${API_BASE}${path}`, { method: "POST", headers: { "Content-Type": "application/json" @@ -305,7 +318,9 @@ }); const payload = await response.json().catch(() => ({})); if (!response.ok || payload.code !== 200) { - throw new Error(payload.msg || "请求失败,请稍后再试"); + throw new Error( + friendlyMessage(payload.msg, "请求失败,请稍后再试。") + ); } return payload.data || {}; } @@ -361,13 +376,19 @@ email, code }); - showStatus( - "success", - result.message || "恢复完成,请使用该邮箱验证码登录查看订阅。" - ); + const message = + result.message || "恢复完成,请使用该邮箱验证码登录查看订阅。"; + if (result.success === false) { + showStatus("error", message); + return; + } + showStatus("success", message); form.reset(); } catch (error) { - showStatus("error", "订单或邮箱验证码无效,请确认后重试。"); + showStatus( + "error", + error.message || "恢复失败,请检查信息后重试。" + ); } finally { submitButton.disabled = false; submitButton.textContent = "恢复订阅";