From 61ca0587c65a97b1be0cc1d7d97215f0f67111ba Mon Sep 17 00:00:00 2001 From: luorijun Date: Fri, 21 Nov 2025 12:59:05 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E9=87=8D=E6=9E=84=E5=90=8E?= =?UTF-8?q?=E9=80=BB=E8=BE=91=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/sql/init.sql | 2 +- web/auth/authenticate.go | 5 +- web/domains/trade/types.go | 5 +- web/globals/asynq.go | 6 ++- web/globals/init.go | 1 + web/globals/shangfutong.go | 2 +- web/handlers/resource.go | 4 +- web/handlers/trade.go | 7 +-- web/services/resource.go | 67 ----------------------- web/services/trade.go | 108 +++++++++++++------------------------ web/services/user.go | 5 +- web/tasks/task.go | 5 +- 12 files changed, 60 insertions(+), 157 deletions(-) diff --git a/scripts/sql/init.sql b/scripts/sql/init.sql index d94994e..bc2e6b8 100644 --- a/scripts/sql/init.sql +++ b/scripts/sql/init.sql @@ -1083,4 +1083,4 @@ insert into client ( ) values ('web', '$2a$10$Ss12mXQgpYyo1CKIZ3URouDm.Lc2KcYJzsvEK2PTIXlv6fHQht45a', '', 3, 'web', 1) --- endregion \ No newline at end of file +-- endregion diff --git a/web/auth/authenticate.go b/web/auth/authenticate.go index 27257d0..8095c1a 100644 --- a/web/auth/authenticate.go +++ b/web/auth/authenticate.go @@ -155,10 +155,7 @@ func authUserBySms(tx *q.Query, username, code string) (*m.User, error) { // 验证验证码 err := s.Verifier.VerifySms(context.Background(), username, code) if err != nil { - if errors.Is(err, s.ErrVerifierServiceInvalid) { - return nil, ErrAuthorizeInvalidRequest - } - return nil, err + return nil, core.NewBizErr("短信认证失败:%w", err) } // 查找用户 diff --git a/web/domains/trade/types.go b/web/domains/trade/types.go index 08729b6..4863775 100644 --- a/web/domains/trade/types.go +++ b/web/domains/trade/types.go @@ -1,8 +1,9 @@ package trade import ( - "github.com/shopspring/decimal" m "platform/web/models" + + "github.com/shopspring/decimal" ) type Type int32 @@ -43,7 +44,7 @@ const ( StatusPending Status = iota // 待支付 StatusSuccess // 已支付 StatusCanceled // 已取消 -) // 已退款 +) type ProductInfo interface { GetType() Type diff --git a/web/globals/asynq.go b/web/globals/asynq.go index d1c7679..825fd01 100644 --- a/web/globals/asynq.go +++ b/web/globals/asynq.go @@ -2,11 +2,13 @@ package globals import ( "github.com/hibiken/asynq" + "github.com/redis/go-redis/v9" ) var Asynq *asynq.Client -func initAsynq() { - var client = asynq.NewClientFromRedisClient(Redis) +func initAsynq(redis *redis.Client) error { + var client = asynq.NewClientFromRedisClient(redis) Asynq = client + return nil } diff --git a/web/globals/init.go b/web/globals/init.go index 692f436..e53a016 100644 --- a/web/globals/init.go +++ b/web/globals/init.go @@ -15,6 +15,7 @@ func Init(ctx context.Context) error { errs = append(errs, initValidator()) errs = append(errs, initRedis()) errs = append(errs, initOrm()) + errs = append(errs, initAsynq(Redis)) errs = append(errs, initProxy()) errs = append(errs, initSft()) diff --git a/web/globals/shangfutong.go b/web/globals/shangfutong.go index 1ecbb75..88b3b4e 100644 --- a/web/globals/shangfutong.go +++ b/web/globals/shangfutong.go @@ -358,7 +358,7 @@ func (s *SftClient) verify(str []byte) (string, error) { } if resp.Code != "000000" { - return "", fmt.Errorf("请求业务响应失败: %s", u.Z(resp.Msg)) + return "", fmt.Errorf("接口的响应为失败: %s", u.Z(resp.Msg)) } if resp.Sign == nil { diff --git a/web/handlers/resource.go b/web/handlers/resource.go index 387da43..97110a8 100644 --- a/web/handlers/resource.go +++ b/web/handlers/resource.go @@ -261,7 +261,7 @@ func StatisticResourceFree(c *fiber.Ctx) error { } // 统计套餐剩余数量 - resources, err := q.Resource.Debug(). + resources, err := q.Resource. Preload( q.Resource.Short, q.Resource.Long, @@ -383,7 +383,7 @@ func StatisticResourceUsage(c *fiber.Ctx) error { } var data = new(StatisticResourceUsageResp) - err = q.LogsUserUsage.Debug(). + err = q.LogsUserUsage. Select( q.LogsUserUsage.Count_.Sum().As("count"), field.NewUnsafeFieldRaw("date_trunc('day', time)").As("date"), diff --git a/web/handlers/trade.go b/web/handlers/trade.go index be87b7c..466a01e 100644 --- a/web/handlers/trade.go +++ b/web/handlers/trade.go @@ -81,10 +81,7 @@ func TradeComplete(c *fiber.Ctx) error { } // 检查订单状态 - err = s.Trade.CompleteTrade(&s.ModifyTradeData{ - TradeNo: req.TradeNo, - Method: req.Method, - }) + err = s.Trade.CompleteTrade(&req.ModifyTradeData) if err != nil { return err } @@ -110,7 +107,7 @@ func TradeCancel(c *fiber.Ctx) error { } // 取消交易 - err = s.Trade.CancelTrade(req.TradeNo, req.Method, time.Now()) + err = s.Trade.CancelTrade(&req.ModifyTradeData, time.Now()) if err != nil { slog.Error("取消交易失败", "trade_no", req.TradeNo, "error", err) return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "取消交易失败"}) diff --git a/web/services/resource.go b/web/services/resource.go index 89309ad..3064621 100644 --- a/web/services/resource.go +++ b/web/services/resource.go @@ -294,73 +294,6 @@ func (r ResourceOnTradeComplete) OnTradeComplete(info trade2.ProductInfo, trade return Resource.CreateResourceByTrade(trade.UserID, time.Time(*trade.CompletedAt), info.(*CreateResourceData), trade) } -// type CreateResourceData struct { -// Type resource2.Type `json:"type" validate:"required"` -// Short *CreateShortResourceData `json:"short,omitempty"` -// Long *CreateLongResourceData `json:"long,omitempty"` -// } -// -// func (data *CreateResourceData) GetSubject() string { -// switch data.Type { -// case resource2.TypeShort: -// return data.Short.GetSubject() -// case resource2.TypeLong: -// return data.Long.GetSubject() -// default: -// panic("未处理的 resource type 枚举值") -// } -// } -// -// func (data *CreateResourceData) GetAmount() decimal.Decimal { -// switch data.Type { -// case resource2.TypeShort: -// return data.Short.GetAmount() -// case resource2.TypeLong: -// return data.Long.GetAmount() -// default: -// panic("未处理的 resource type 枚举值") -// } -// } -// -// func (data *CreateResourceData) ToData() (CreateResourceData, error) { -// switch data.Type { -// case resource2.TypeShort: -// return data.Short, nil -// case resource2.TypeLong: -// return data.Long, nil -// } -// -// return nil, fmt.Errorf("不支持的套餐类型") -// } -// -// type PrepareResourceData struct { -// CreateResourceData -// PaymentMethod trade2.Method `json:"payment_method" validate:"required"` -// PaymentPlatform trade2.Platform `json:"payment_platform" validate:"required"` -// } -// -// type PrepareResourceCache struct { -// Uid int32 `json:"uid"` -// TradeId int32 `json:"trade_id"` -// BillId int32 `json:"bill_id"` -// *PrepareResourceData -// } -// -// func (c PrepareResourceCache) MarshalBinary() (data []byte, err error) { -// data, err = json.Marshal(c) -// if err != nil { -// return nil, err -// } -// return data, nil -// } -// -// func (c PrepareResourceCache) UnmarshalBinary(data []byte) error { -// if err := json.Unmarshal(data, &c); err != nil { -// return err -// } -// return nil -// } - type ResourceServiceErr string func (e ResourceServiceErr) Error() string { diff --git a/web/services/trade.go b/web/services/trade.go index 48b47ea..09c6ecf 100644 --- a/web/services/trade.go +++ b/web/services/trade.go @@ -12,13 +12,14 @@ import ( "platform/web/core" coupon2 "platform/web/domains/coupon" trade2 "platform/web/domains/trade" - "platform/web/events" + e "platform/web/events" g "platform/web/globals" "platform/web/globals/orm" m "platform/web/models" q "platform/web/queries" "time" + "github.com/hibiken/asynq" "github.com/shopspring/decimal" wecahtpaycore "github.com/wechatpay-apiv3/wechatpay-go/core" @@ -32,6 +33,7 @@ var Trade = &tradeService{} type tradeService struct { } +// 创建交易 func (s *tradeService) CreateTrade(uid int32, now time.Time, data *CreateTradeData) (*CreateTradeResult, error) { platform := data.Platform method := data.Method @@ -42,7 +44,7 @@ func (s *tradeService) CreateTrade(uid int32, now time.Time, data *CreateTradeDa // 实际支付金额,只在创建真实订单时使用 var amountReal = data.Product.GetAmount() - if env.RunMode == "debug" { + if env.RunMode == env.RunModeDev { amountReal = decimal.NewFromFloat(0.01) } @@ -240,10 +242,11 @@ func (s *tradeService) CreateTrade(uid int32, now time.Time, data *CreateTradeDa } // 提交异步关闭事件 - _, err = g.Asynq.Enqueue(events.NewCancelTrade(events.CancelTradeData{ + closeAt := now.Add(time.Duration(env.TradeExpire) * time.Second) + _, err = g.Asynq.Enqueue(e.NewCancelTrade(e.CancelTradeData{ TradeNo: tradeNo, Method: method, - })) + }), asynq.ProcessAt(closeAt)) if err != nil { return nil, core.NewServErr("提交异步关闭事件失败", err) } @@ -254,17 +257,30 @@ func (s *tradeService) CreateTrade(uid int32, now time.Time, data *CreateTradeDa }, nil } +// 完成交易 func (s *tradeService) CompleteTrade(data *ModifyTradeData) error { return g.Redsync.WithLock(tradeLockKey(data.TradeNo), func() error { // 检查订单状态 - result, err := s.ConfirmTradeCompleted(data) + result, err := s.CheckTrade(data) if err != nil { - return core.NewServErr("确认交易状态失败", err) + return core.NewServErr("检查订单状态失败", err) + } + if result.Status != trade2.StatusSuccess { + switch result.Status { + case trade2.StatusPending: + return core.NewBizErr("订单未支付") + case trade2.StatusCanceled: + return core.NewBizErr("订单已过期") + } } // 更新交易状态 - trade, err := completeTrade(&OnTradeCompletedData{data.TradeNo, *result}) + trade, err := completeTrade(&OnTradeCompletedData{ + data.TradeNo, + result.TransId, + result.Success, + }) if err != nil { return core.NewServErr("处理交易失败", err) } @@ -329,9 +345,12 @@ func completeTrade(data *OnTradeCompletedData) (*m.Trade, error) { trade.Payment = payment trade.Acquirer = u.P(int32(acquirer)) trade.CompletedAt = u.P(orm.LocalDateTime(paidAt)) - _, err = q.Trade. - Where(q.Trade.InnerNo.Eq(tradeNo)). + rs, err := q.Trade. + Where(q.Trade.InnerNo.Eq(tradeNo), q.Trade.Status.Eq(int32(trade2.StatusPending))). Updates(trade) + if rs.RowsAffected == 0 { + return core.NewBizErr("交易状态已发生变化") + } if err != nil { return core.NewServErr("更新交易信息失败", err) } @@ -378,7 +397,11 @@ func afterTradeComplete(trade *m.Trade) error { return nil } -func (s *tradeService) CancelTrade(tradeNo string, method trade2.Method, now time.Time) error { +// 取消交易 +func (s *tradeService) CancelTrade(data *ModifyTradeData, now time.Time) error { + tradeNo := data.TradeNo + method := data.Method + return g.Redsync.WithLock(tradeLockKey(tradeNo), func() error { switch method { @@ -477,13 +500,15 @@ func cancelTrade(tradeNo string, now time.Time) error { }) } -func (s *tradeService) RefundTrade(tradeNo string, method trade2.Method) error { +// 交易退款 +func (s *tradeService) RefundTrade(data *ModifyTradeData) error { panic("todo") } func (s *tradeService) OnTradeRefunded(q *q.Query, tradeNo string, now time.Time) error { panic("todo") } +// 检查交易状态 func (s *tradeService) CheckTrade(data *ModifyTradeData) (*CheckTradeResult, error) { var tradeNo = data.TradeNo var method = data.Method @@ -624,63 +649,6 @@ func (s *tradeService) CheckTrade(data *ModifyTradeData) (*CheckTradeResult, err return result, nil } -func (s *tradeService) ConfirmTradeCompleted(data *ModifyTradeData) (*TradeSuccessResult, error) { - rs, err := Trade.CheckTrade(&ModifyTradeData{ - TradeNo: data.TradeNo, - Method: data.Method, - }) - if err != nil { - return nil, err - } - - switch rs.Status { - case trade2.StatusPending: - return nil, core.NewBizErr("订单未支付") - case trade2.StatusCanceled: - return nil, core.NewBizErr("订单已关闭") - case trade2.StatusSuccess: - } - - return rs.Success, nil -} -func (s *tradeService) ConfirmTradeCanceled(data *ModifyTradeData) error { - rs, err := Trade.CheckTrade(&ModifyTradeData{ - TradeNo: data.TradeNo, - Method: data.Method, - }) - if err != nil { - return err - } - - switch rs.Status { - case trade2.StatusPending: - return core.NewBizErr("订单未支付") - case trade2.StatusSuccess: - return core.NewBizErr("订单已关闭") - case trade2.StatusCanceled: - } - - return nil -} -func (s *tradeService) ConfirmTradeRefunded(data *ModifyTradeData) error { - rs, err := Trade.CheckTrade(&ModifyTradeData{ - TradeNo: data.TradeNo, - Method: data.Method, - }) - if err != nil { - return err - } - - switch rs.Status { - case trade2.StatusPending: - return core.NewBizErr("订单未支付") - case trade2.StatusCanceled: - return core.NewBizErr("订单已关闭") - case trade2.StatusSuccess: - } - - return core.NewBizErr("订单状态异常") -} func tradeProductKey(no string) string { return fmt.Sprintf("trade:%s:product", no) @@ -714,7 +682,6 @@ type CheckTradeResult struct { } type TradeSuccessResult struct { - TransId string Acquirer trade2.Acquirer Payment decimal.Decimal Time time.Time @@ -722,7 +689,8 @@ type TradeSuccessResult struct { type OnTradeCompletedData struct { TradeNo string - TradeSuccessResult + TransId string + *TradeSuccessResult } type TradeErr string diff --git a/web/services/user.go b/web/services/user.go index 8e25b99..3e0af39 100644 --- a/web/services/user.go +++ b/web/services/user.go @@ -3,13 +3,14 @@ package services import ( "encoding/json" "fmt" - "github.com/shopspring/decimal" "platform/web/core" bill2 "platform/web/domains/bill" trade2 "platform/web/domains/trade" g "platform/web/globals" m "platform/web/models" q "platform/web/queries" + + "github.com/shopspring/decimal" ) var User = &userService{} @@ -77,7 +78,7 @@ func (r *RechargeProductInfo) GetType() trade2.Type { } func (r *RechargeProductInfo) GetSubject() string { - return fmt.Sprintf("账户充值 - " + r.GetAmount().StringFixed(2) + "元") + return fmt.Sprintf("账户充值 - %s元", r.GetAmount().StringFixed(2)) } func (r *RechargeProductInfo) GetAmount() decimal.Decimal { diff --git a/web/tasks/task.go b/web/tasks/task.go index 1009dfa..2af90f6 100644 --- a/web/tasks/task.go +++ b/web/tasks/task.go @@ -18,7 +18,10 @@ func HandleCancelTrade(_ context.Context, task *asynq.Task) (err error) { return fmt.Errorf("解析任务参数失败: %w", err) } - err = s.Trade.CancelTrade(data.TradeNo, data.Method, time.Now()) + err = s.Trade.CancelTrade(&s.ModifyTradeData{ + TradeNo: data.TradeNo, + Method: data.Method, + }, time.Now()) if err != nil { return fmt.Errorf("取消交易失败: %w", err) }