From 3588bf9af66a33b66367d85746f6e0f8a4a3cbd1 Mon Sep 17 00:00:00 2001 From: danial Date: Sun, 23 Nov 2025 00:08:35 +0800 Subject: [PATCH] =?UTF-8?q?feat(camel=5Foil):=20=E6=94=AF=E6=8C=81Token?= =?UTF-8?q?=E7=AE=A1=E7=90=86=E4=B8=8E=E5=8D=A1=E5=AF=86=E7=BB=91=E5=AE=9A?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增CamelOilToken和CamelOilCardBinding数据库表,实现Token及卡密绑定记录管理 - 在service层增加Token的创建、查询、更新、删除及分页功能 - 实现卡密与Token绑定的业务逻辑,支持基于Token的卡密管理 - 在API层新增Token和卡密绑定相关接口:创建Token、获取Token详情、删除Token、列出Token及根据Token查询绑定卡密 - camel_oil_api新增绑卡接口,支持绑卡状态分类及错误处理 - 在定时任务中增加卡密绑定任务,实现自动处理已支付订单的卡密绑定 - 优化订单提交及支付流程,包含日志调整和请求参数随机扰动 - 统一调整camel_oil模块多控制器实现,完成账号状态查询及订单相关接口实现 - 注册更多camel_oil定时任务,包括订单支付检查、账号日重置和待回调订单处理任务 --- api/camel_oil/camel_oil.go | 5 + api/camel_oil/v1/token.go | 106 ++++++ api/card_info_apple/card_info_apple.go | 2 +- internal/consts/camel_oil.go | 16 + .../camel_oil/camel_oil_v1_account_history.go | 8 +- .../camel_oil_v1_account_order_list.go | 8 +- .../camel_oil_v1_account_statistics.go | 8 +- .../camel_oil/camel_oil_v1_check_account.go | 39 +- .../camel_oil/camel_oil_v1_create_token.go | 19 + .../camel_oil/camel_oil_v1_delete_token.go | 18 + .../camel_oil/camel_oil_v1_get_token.go | 31 ++ ...amel_oil_v1_list_card_bindings_by_token.go | 36 ++ .../camel_oil/camel_oil_v1_list_order.go | 2 +- .../camel_oil/camel_oil_v1_list_tokens.go | 41 ++ .../camel_oil/camel_oil_v1_order_callback.go | 8 +- .../camel_oil/camel_oil_v1_order_detail.go | 2 +- .../camel_oil/camel_oil_v1_order_history.go | 8 +- .../camel_oil/camel_oil_v1_submit_order.go | 2 +- .../internal/v_1_camel_oil_card_binding.go | 97 +++++ internal/dao/internal/v_1_camel_oil_token.go | 103 +++++ internal/dao/v_1_camel_oil_card_binding.go | 22 ++ internal/dao/v_1_camel_oil_token.go | 22 ++ internal/logic/camel_oil/cron_tasks.go | 68 +++- internal/logic/camel_oil/order_callback.go | 10 +- internal/logic/camel_oil/token.go | 359 ++++++++++++++++++ internal/middleware/auth.go | 1 + .../model/do/v_1_camel_oil_card_binding.go | 25 ++ internal/model/do/v_1_camel_oil_token.go | 28 ++ .../entity/v_1_camel_oil_card_binding.go | 24 ++ internal/model/entity/v_1_camel_oil_token.go | 27 ++ internal/service/camel_oil.go | 38 +- sql/camel_oil_tables.sql | 52 +++ utility/cron/cron.go | 31 +- utility/integration/camel_oil_api/api.go | 92 ++++- utility/integration/camel_oil_api/api_test.go | 3 + .../integration/camel_oil_api/encrypt_test.go | 9 +- 36 files changed, 1301 insertions(+), 69 deletions(-) create mode 100644 api/camel_oil/v1/token.go create mode 100644 internal/controller/camel_oil/camel_oil_v1_create_token.go create mode 100644 internal/controller/camel_oil/camel_oil_v1_delete_token.go create mode 100644 internal/controller/camel_oil/camel_oil_v1_get_token.go create mode 100644 internal/controller/camel_oil/camel_oil_v1_list_card_bindings_by_token.go create mode 100644 internal/controller/camel_oil/camel_oil_v1_list_tokens.go create mode 100644 internal/dao/internal/v_1_camel_oil_card_binding.go create mode 100644 internal/dao/internal/v_1_camel_oil_token.go create mode 100644 internal/dao/v_1_camel_oil_card_binding.go create mode 100644 internal/dao/v_1_camel_oil_token.go create mode 100644 internal/logic/camel_oil/token.go create mode 100644 internal/model/do/v_1_camel_oil_card_binding.go create mode 100644 internal/model/do/v_1_camel_oil_token.go create mode 100644 internal/model/entity/v_1_camel_oil_card_binding.go create mode 100644 internal/model/entity/v_1_camel_oil_token.go diff --git a/api/camel_oil/camel_oil.go b/api/camel_oil/camel_oil.go index eda53da6..3bf88b3d 100644 --- a/api/camel_oil/camel_oil.go +++ b/api/camel_oil/camel_oil.go @@ -21,4 +21,9 @@ type ICamelOilV1 interface { OrderHistory(ctx context.Context, req *v1.OrderHistoryReq) (res *v1.OrderHistoryRes, err error) AccountOrderList(ctx context.Context, req *v1.AccountOrderListReq) (res *v1.AccountOrderListRes, err error) OrderCallback(ctx context.Context, req *v1.OrderCallbackReq) (res *v1.OrderCallbackRes, err error) + CreateToken(ctx context.Context, req *v1.CreateTokenReq) (res *v1.CreateTokenRes, err error) + GetToken(ctx context.Context, req *v1.GetTokenReq) (res *v1.GetTokenRes, err error) + ListTokens(ctx context.Context, req *v1.ListTokensReq) (res *v1.ListTokensRes, err error) + DeleteToken(ctx context.Context, req *v1.DeleteTokenReq) (res *v1.DeleteTokenRes, err error) + ListCardBindingsByToken(ctx context.Context, req *v1.ListCardBindingsByTokenReq) (res *v1.ListCardBindingsByTokenRes, err error) } diff --git a/api/camel_oil/v1/token.go b/api/camel_oil/v1/token.go new file mode 100644 index 00000000..4eb93b30 --- /dev/null +++ b/api/camel_oil/v1/token.go @@ -0,0 +1,106 @@ +package v1 + +import ( + "kami/api/commonApi" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gtime" + "github.com/shopspring/decimal" +) + +// ==================================================================================== +// Token 管理 API 请求/响应结构 +// ==================================================================================== + +// CreateTokenReq 创建 Token 请求 +type CreateTokenReq struct { + g.Meta `path:"/token/create" tags:"JD V2 Token Management" method:"post" summary:"创建 Token"` + TokenName string `json:"tokenName" v:"required" description:"Token名称"` + TokenValue string `json:"tokenValue" v:"required" description:"Token值"` + Phone string `json:"phone" description:"绑定的手机号"` + Remark string `json:"remark" description:"备注"` +} + +// CreateTokenRes 创建 Token 响应 +type CreateTokenRes struct { + TokenId int64 `json:"tokenId" description:"Token ID"` +} + +// GetTokenReq 获取 Token 信息请求 +type GetTokenReq struct { + g.Meta `path:"/token/get" tags:"JD V2 Token Management" method:"get" summary:"获取 Token 信息"` + TokenId int64 `json:"tokenId" v:"required" description:"Token ID"` +} + +// TokenInfo Token 信息 +type TokenInfo struct { + Id int64 `json:"id" description:"Token ID"` + TokenName string `json:"tokenName" description:"Token名称"` + TokenValue string `json:"tokenValue" description:"Token值"` + Phone string `json:"phone" description:"绑定的手机号"` + Status int `json:"status" description:"状态"` + BindCount int `json:"bindCount" description:"已绑定卡密数量"` + TotalBindAmount decimal.Decimal `json:"totalBindAmount" description:"累计绑定金额"` + LastBindAt *gtime.Time `json:"lastBindAt" description:"最后绑定时间"` + LastUsedAt *gtime.Time `json:"lastUsedAt" description:"最后使用时间"` + Remark string `json:"remark" description:"备注"` + CreatedAt *gtime.Time `json:"createdAt" description:"创建时间"` + UpdatedAt *gtime.Time `json:"updatedAt" description:"更新时间"` +} + +// GetTokenRes 获取 Token 信息响应 +type GetTokenRes struct { + Token TokenInfo `json:"token" description:"Token信息"` +} + +// ListTokensRes 列出 Token 响应 +type ListTokensRes struct { + commonApi.CommonPageRes[TokenInfo] +} + +// ListTokensReq 列出 Token 请求 +type ListTokensReq struct { + g.Meta `path:"/token/list" tags:"JD V2 Token Management" method:"get" summary:"列出 Token"` + commonApi.CommonPageReq + TokenName string `json:"tokenName" description:"Token名称"` + Status int `json:"status" description:"状态"` +} + +// DeleteTokenReq 删除 Token 请求 +type DeleteTokenReq struct { + g.Meta `path:"/token/delete" tags:"JD V2 Token Management" method:"post" summary:"删除 Token"` + TokenId int64 `json:"tokenId" v:"required" description:"Token ID"` +} + +// DeleteTokenRes 删除 Token 响应 +type DeleteTokenRes struct { + Message string `json:"message" description:"信息"` +} + +// ==================================================================================== +// 卡密绑定 API 请求/响应结构 +// ==================================================================================== + +// CardBindingInfo 卡密绑定信息 +type CardBindingInfo struct { + Id int64 `json:"id" description:"绑定记录ID"` + TokenId int64 `json:"tokenId" description:"Token ID"` + TokenName string `json:"tokenName" description:"Token名称"` + OrderId int64 `json:"orderId" description:"订单ID"` + CardNumber string `json:"cardNumber" description:"卡号"` + CardPassword string `json:"cardPassword" description:"卡密"` + Amount decimal.Decimal `json:"amount" description:"绑定金额"` + CreatedAt *gtime.Time `json:"createdAt" description:"创建时间"` +} + +// ListCardBindingsByTokenReq 根据 tokenId 查询绑卡记录请求 +type ListCardBindingsByTokenReq struct { + g.Meta `path:"/card-binding/list-by-token" tags:"JD V2 Token Management" method:"get" summary:"查询 Token 绑卡记录"` + commonApi.CommonPageReq + TokenId int64 `json:"tokenId" v:"required" description:"Token ID"` +} + +// ListCardBindingsByTokenRes 根据 tokenId 查询绑卡记录响应 +type ListCardBindingsByTokenRes struct { + commonApi.CommonPageRes[CardBindingInfo] +} diff --git a/api/card_info_apple/card_info_apple.go b/api/card_info_apple/card_info_apple.go index 74dc7ff3..90e7b9a1 100644 --- a/api/card_info_apple/card_info_apple.go +++ b/api/card_info_apple/card_info_apple.go @@ -7,7 +7,7 @@ package card_info_apple import ( "context" - v1 "kami/api/card_info_apple/v1" + "kami/api/card_info_apple/v1" ) type ICardInfoAppleV1 interface { diff --git a/internal/consts/camel_oil.go b/internal/consts/camel_oil.go index 990b3e02..399e4e3a 100644 --- a/internal/consts/camel_oil.go +++ b/internal/consts/camel_oil.go @@ -76,6 +76,22 @@ var CamelOilCallbackStatusText = map[CamelOilNotifyStatus]string{ CamelOilCallbackStatusFailed: "回调失败", } +// CamelOilTokenStatus Token管理状态枚举 +type CamelOilTokenStatus int + +const ( + CamelOilTokenStatusAvailable CamelOilTokenStatus = 1 // 可用 + CamelOilTokenStatusDisabled CamelOilTokenStatus = 2 // 已禁用 + CamelOilTokenStatusExpired CamelOilTokenStatus = 3 // 已过期 +) + +// CamelOilTokenStatusText Token状态文本映射 +var CamelOilTokenStatusText = map[CamelOilTokenStatus]string{ + CamelOilTokenStatusAvailable: "可用", + CamelOilTokenStatusDisabled: "已禁用", + CamelOilTokenStatusExpired: "已过期", +} + // ==================================================================================== // 变更类型常量定义 // ==================================================================================== diff --git a/internal/controller/camel_oil/camel_oil_v1_account_history.go b/internal/controller/camel_oil/camel_oil_v1_account_history.go index 6994e8ff..ddd6d893 100644 --- a/internal/controller/camel_oil/camel_oil_v1_account_history.go +++ b/internal/controller/camel_oil/camel_oil_v1_account_history.go @@ -3,12 +3,10 @@ package camel_oil import ( "context" - "github.com/gogf/gf/v2/errors/gcode" - "github.com/gogf/gf/v2/errors/gerror" - - "kami/api/camel_oil/v1" + v1 "kami/api/camel_oil/v1" + "kami/internal/service" ) func (c *ControllerV1) AccountHistory(ctx context.Context, req *v1.AccountHistoryReq) (res *v1.AccountHistoryRes, err error) { - return nil, gerror.NewCode(gcode.CodeNotImplemented) + return service.CamelOil().GetAccountHistory(ctx, req) } diff --git a/internal/controller/camel_oil/camel_oil_v1_account_order_list.go b/internal/controller/camel_oil/camel_oil_v1_account_order_list.go index c98a1da5..0d03c774 100644 --- a/internal/controller/camel_oil/camel_oil_v1_account_order_list.go +++ b/internal/controller/camel_oil/camel_oil_v1_account_order_list.go @@ -3,12 +3,10 @@ package camel_oil import ( "context" - "github.com/gogf/gf/v2/errors/gcode" - "github.com/gogf/gf/v2/errors/gerror" - - "kami/api/camel_oil/v1" + v1 "kami/api/camel_oil/v1" + "kami/internal/service" ) func (c *ControllerV1) AccountOrderList(ctx context.Context, req *v1.AccountOrderListReq) (res *v1.AccountOrderListRes, err error) { - return nil, gerror.NewCode(gcode.CodeNotImplemented) + return service.CamelOil().GetAccountOrders(ctx, req) } diff --git a/internal/controller/camel_oil/camel_oil_v1_account_statistics.go b/internal/controller/camel_oil/camel_oil_v1_account_statistics.go index 99f1c197..dbb8d918 100644 --- a/internal/controller/camel_oil/camel_oil_v1_account_statistics.go +++ b/internal/controller/camel_oil/camel_oil_v1_account_statistics.go @@ -3,12 +3,10 @@ package camel_oil import ( "context" - "github.com/gogf/gf/v2/errors/gcode" - "github.com/gogf/gf/v2/errors/gerror" - - "kami/api/camel_oil/v1" + v1 "kami/api/camel_oil/v1" + "kami/internal/service" ) func (c *ControllerV1) AccountStatistics(ctx context.Context, req *v1.AccountStatisticsReq) (res *v1.AccountStatisticsRes, err error) { - return nil, gerror.NewCode(gcode.CodeNotImplemented) + return service.CamelOil().GetAccountStatistics(ctx, req) } diff --git a/internal/controller/camel_oil/camel_oil_v1_check_account.go b/internal/controller/camel_oil/camel_oil_v1_check_account.go index 6f37a754..6fb19f80 100644 --- a/internal/controller/camel_oil/camel_oil_v1_check_account.go +++ b/internal/controller/camel_oil/camel_oil_v1_check_account.go @@ -3,12 +3,41 @@ package camel_oil import ( "context" - "github.com/gogf/gf/v2/errors/gcode" - "github.com/gogf/gf/v2/errors/gerror" - - "kami/api/camel_oil/v1" + v1 "kami/api/camel_oil/v1" + "kami/internal/consts" + "kami/internal/service" ) func (c *ControllerV1) CheckAccount(ctx context.Context, req *v1.CheckAccountReq) (res *v1.CheckAccountRes, err error) { - return nil, gerror.NewCode(gcode.CodeNotImplemented) + account, err := service.CamelOil().GetAccountInfo(ctx, req.AccountId) + if err != nil { + return nil, err + } + + res = &v1.CheckAccountRes{ + IsOnline: account.Status == int(consts.CamelOilAccountStatusOnline), + Status: consts.CamelOilAccountStatus(account.Status), + StatusText: getAccountStatusTextForStatus(account.Status), + FailureReason: account.FailureReason, + } + + return res, nil +} + +// getAccountStatusTextForStatus 获取账号状态文本 +func getAccountStatusTextForStatus(status int) string { + switch status { + case 1: + return "待登录" + case 2: + return "在线" + case 3: + return "暂停" + case 4: + return "已失效" + case 5: + return "登录失败" + default: + return "未知" + } } diff --git a/internal/controller/camel_oil/camel_oil_v1_create_token.go b/internal/controller/camel_oil/camel_oil_v1_create_token.go new file mode 100644 index 00000000..3047f952 --- /dev/null +++ b/internal/controller/camel_oil/camel_oil_v1_create_token.go @@ -0,0 +1,19 @@ +package camel_oil + +import ( + "context" + + v1 "kami/api/camel_oil/v1" + "kami/internal/service" +) + +func (c *ControllerV1) CreateToken(ctx context.Context, req *v1.CreateTokenReq) (res *v1.CreateTokenRes, err error) { + tokenId, err := service.CamelOil().CreateToken(ctx, req.TokenName, req.TokenValue, req.Phone, req.Remark) + if err != nil { + return nil, err + } + + return &v1.CreateTokenRes{ + TokenId: tokenId, + }, nil +} diff --git a/internal/controller/camel_oil/camel_oil_v1_delete_token.go b/internal/controller/camel_oil/camel_oil_v1_delete_token.go new file mode 100644 index 00000000..3d97745c --- /dev/null +++ b/internal/controller/camel_oil/camel_oil_v1_delete_token.go @@ -0,0 +1,18 @@ +package camel_oil + +import ( + "context" + + v1 "kami/api/camel_oil/v1" + "kami/internal/service" +) + +func (c *ControllerV1) DeleteToken(ctx context.Context, req *v1.DeleteTokenReq) (res *v1.DeleteTokenRes, err error) { + err = service.CamelOil().DeleteToken(ctx, req.TokenId) + if err != nil { + return nil, err + } + return &v1.DeleteTokenRes{ + Message: "Token deleted successfully", + }, nil +} diff --git a/internal/controller/camel_oil/camel_oil_v1_get_token.go b/internal/controller/camel_oil/camel_oil_v1_get_token.go new file mode 100644 index 00000000..f822391b --- /dev/null +++ b/internal/controller/camel_oil/camel_oil_v1_get_token.go @@ -0,0 +1,31 @@ +package camel_oil + +import ( + "context" + + v1 "kami/api/camel_oil/v1" + "kami/internal/service" +) + +func (c *ControllerV1) GetToken(ctx context.Context, req *v1.GetTokenReq) (res *v1.GetTokenRes, err error) { + token, err := service.CamelOil().GetTokenInfo(ctx, req.TokenId) + if err != nil { + return nil, err + } + return &v1.GetTokenRes{ + Token: v1.TokenInfo{ + Id: token.Id, + TokenName: token.TokenName, + TokenValue: token.TokenValue, + Phone: token.Phone, + Status: token.Status, + BindCount: token.BindCount, + TotalBindAmount: token.TotalBindAmount, + LastBindAt: token.LastBindAt, + LastUsedAt: token.LastUsedAt, + Remark: token.Remark, + CreatedAt: token.CreatedAt, + UpdatedAt: token.UpdatedAt, + }, + }, nil +} diff --git a/internal/controller/camel_oil/camel_oil_v1_list_card_bindings_by_token.go b/internal/controller/camel_oil/camel_oil_v1_list_card_bindings_by_token.go new file mode 100644 index 00000000..9c155a40 --- /dev/null +++ b/internal/controller/camel_oil/camel_oil_v1_list_card_bindings_by_token.go @@ -0,0 +1,36 @@ +package camel_oil + +import ( + "context" + + v1 "kami/api/camel_oil/v1" + "kami/api/commonApi" + "kami/internal/service" +) + +func (c *ControllerV1) ListCardBindingsByToken(ctx context.Context, req *v1.ListCardBindingsByTokenReq) (res *v1.ListCardBindingsByTokenRes, err error) { + bindings, total, err := service.CamelOil().GetCardBindingsByToken(ctx, req.TokenId, req.Current, req.PageSize) + if err != nil { + return nil, err + } + bindingInfos := make([]v1.CardBindingInfo, 0, len(bindings)) + for _, binding := range bindings { + bindingInfos = append(bindingInfos, v1.CardBindingInfo{ + Id: binding.Id, + TokenId: binding.TokenId, + OrderId: binding.OrderId, + CardNumber: binding.CardNumber, + CardPassword: binding.CardPassword, + Amount: binding.Amount, + CreatedAt: binding.CreatedAt, + }) + } + return &v1.ListCardBindingsByTokenRes{ + CommonPageRes: commonApi.CommonPageRes[v1.CardBindingInfo]{ + Total: total, + CommonDataRes: commonApi.CommonDataRes[v1.CardBindingInfo]{ + List: bindingInfos, + }, + }, + }, nil +} diff --git a/internal/controller/camel_oil/camel_oil_v1_list_order.go b/internal/controller/camel_oil/camel_oil_v1_list_order.go index acf892d0..c2fd8172 100644 --- a/internal/controller/camel_oil/camel_oil_v1_list_order.go +++ b/internal/controller/camel_oil/camel_oil_v1_list_order.go @@ -3,7 +3,7 @@ package camel_oil import ( "context" - "kami/api/camel_oil/v1" + v1 "kami/api/camel_oil/v1" "kami/internal/service" ) diff --git a/internal/controller/camel_oil/camel_oil_v1_list_tokens.go b/internal/controller/camel_oil/camel_oil_v1_list_tokens.go new file mode 100644 index 00000000..227aba4a --- /dev/null +++ b/internal/controller/camel_oil/camel_oil_v1_list_tokens.go @@ -0,0 +1,41 @@ +package camel_oil + +import ( + "context" + + v1 "kami/api/camel_oil/v1" + "kami/api/commonApi" + "kami/internal/service" +) + +func (c *ControllerV1) ListTokens(ctx context.Context, req *v1.ListTokensReq) (res *v1.ListTokensRes, err error) { + tokens, total, err := service.CamelOil().ListTokensWithPagination(ctx, req.CommonPageReq, req.TokenName, req.Status) + if err != nil { + return nil, err + } + tokenInfos := make([]v1.TokenInfo, 0, len(tokens)) + for _, token := range tokens { + tokenInfos = append(tokenInfos, v1.TokenInfo{ + Id: token.Id, + TokenName: token.TokenName, + TokenValue: token.TokenValue, + Phone: token.Phone, + Status: token.Status, + BindCount: token.BindCount, + TotalBindAmount: token.TotalBindAmount, + LastBindAt: token.LastBindAt, + LastUsedAt: token.LastUsedAt, + Remark: token.Remark, + CreatedAt: token.CreatedAt, + UpdatedAt: token.UpdatedAt, + }) + } + return &v1.ListTokensRes{ + CommonPageRes: commonApi.CommonPageRes[v1.TokenInfo]{ + Total: total, + CommonDataRes: commonApi.CommonDataRes[v1.TokenInfo]{ + List: tokenInfos, + }, + }, + }, nil +} diff --git a/internal/controller/camel_oil/camel_oil_v1_order_callback.go b/internal/controller/camel_oil/camel_oil_v1_order_callback.go index 5d641161..0342dfcc 100644 --- a/internal/controller/camel_oil/camel_oil_v1_order_callback.go +++ b/internal/controller/camel_oil/camel_oil_v1_order_callback.go @@ -3,12 +3,10 @@ package camel_oil import ( "context" - "github.com/gogf/gf/v2/errors/gcode" - "github.com/gogf/gf/v2/errors/gerror" - - "kami/api/camel_oil/v1" + v1 "kami/api/camel_oil/v1" + "kami/internal/service" ) func (c *ControllerV1) OrderCallback(ctx context.Context, req *v1.OrderCallbackReq) (res *v1.OrderCallbackRes, err error) { - return nil, gerror.NewCode(gcode.CodeNotImplemented) + return service.CamelOil().TriggerOrderCallback(ctx, req) } diff --git a/internal/controller/camel_oil/camel_oil_v1_order_detail.go b/internal/controller/camel_oil/camel_oil_v1_order_detail.go index 92318928..2a978296 100644 --- a/internal/controller/camel_oil/camel_oil_v1_order_detail.go +++ b/internal/controller/camel_oil/camel_oil_v1_order_detail.go @@ -3,7 +3,7 @@ package camel_oil import ( "context" - "kami/api/camel_oil/v1" + v1 "kami/api/camel_oil/v1" "kami/internal/service" ) diff --git a/internal/controller/camel_oil/camel_oil_v1_order_history.go b/internal/controller/camel_oil/camel_oil_v1_order_history.go index a704e804..2c247773 100644 --- a/internal/controller/camel_oil/camel_oil_v1_order_history.go +++ b/internal/controller/camel_oil/camel_oil_v1_order_history.go @@ -3,12 +3,10 @@ package camel_oil import ( "context" - "github.com/gogf/gf/v2/errors/gcode" - "github.com/gogf/gf/v2/errors/gerror" - - "kami/api/camel_oil/v1" + v1 "kami/api/camel_oil/v1" + "kami/internal/service" ) func (c *ControllerV1) OrderHistory(ctx context.Context, req *v1.OrderHistoryReq) (res *v1.OrderHistoryRes, err error) { - return nil, gerror.NewCode(gcode.CodeNotImplemented) + return service.CamelOil().GetOrderHistory(ctx, req) } diff --git a/internal/controller/camel_oil/camel_oil_v1_submit_order.go b/internal/controller/camel_oil/camel_oil_v1_submit_order.go index 73a76a3b..8c6ad36f 100644 --- a/internal/controller/camel_oil/camel_oil_v1_submit_order.go +++ b/internal/controller/camel_oil/camel_oil_v1_submit_order.go @@ -3,7 +3,7 @@ package camel_oil import ( "context" - "kami/api/camel_oil/v1" + v1 "kami/api/camel_oil/v1" "kami/internal/service" ) diff --git a/internal/dao/internal/v_1_camel_oil_card_binding.go b/internal/dao/internal/v_1_camel_oil_card_binding.go new file mode 100644 index 00000000..b7bd72f2 --- /dev/null +++ b/internal/dao/internal/v_1_camel_oil_card_binding.go @@ -0,0 +1,97 @@ +// ========================================================================== +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// ========================================================================== + +package internal + +import ( + "context" + + "github.com/gogf/gf/v2/database/gdb" + "github.com/gogf/gf/v2/frame/g" +) + +// V1CamelOilCardBindingDao is the data access object for the table camel_oil_card_binding. +type V1CamelOilCardBindingDao struct { + table string // table is the underlying table name of the DAO. + group string // group is the database configuration group name of the current DAO. + columns V1CamelOilCardBindingColumns // columns contains all the column names of Table for convenient usage. + handlers []gdb.ModelHandler // handlers for customized model modification. +} + +// V1CamelOilCardBindingColumns defines and stores column names for the table camel_oil_card_binding. +type V1CamelOilCardBindingColumns struct { + Id string // 主键ID + TokenId string // Token ID + OrderId string // 订单ID + CardNumber string // 卡号 + CardPassword string // 卡密 + Amount string // 绑定金额 + Remark string // 备注 + CreatedAt string // 创建时间 + UpdatedAt string // 更新时间 + DeletedAt string // 删除时间(软删除) +} + +// v1CamelOilCardBindingColumns holds the columns for the table camel_oil_card_binding. +var v1CamelOilCardBindingColumns = V1CamelOilCardBindingColumns{ + Id: "id", + TokenId: "token_id", + OrderId: "order_id", + CardNumber: "card_number", + CardPassword: "card_password", + Amount: "amount", + Remark: "remark", + CreatedAt: "created_at", + UpdatedAt: "updated_at", + DeletedAt: "deleted_at", +} + +// NewV1CamelOilCardBindingDao creates and returns a new DAO object for table data access. +func NewV1CamelOilCardBindingDao(handlers ...gdb.ModelHandler) *V1CamelOilCardBindingDao { + return &V1CamelOilCardBindingDao{ + group: "default", + table: "camel_oil_card_binding", + columns: v1CamelOilCardBindingColumns, + handlers: handlers, + } +} + +// DB retrieves and returns the underlying raw database management object of the current DAO. +func (dao *V1CamelOilCardBindingDao) DB() gdb.DB { + return g.DB(dao.group) +} + +// Table returns the table name of the current DAO. +func (dao *V1CamelOilCardBindingDao) Table() string { + return dao.table +} + +// Columns returns all column names of the current DAO. +func (dao *V1CamelOilCardBindingDao) Columns() V1CamelOilCardBindingColumns { + return dao.columns +} + +// Group returns the database configuration group name of the current DAO. +func (dao *V1CamelOilCardBindingDao) Group() string { + return dao.group +} + +// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation. +func (dao *V1CamelOilCardBindingDao) Ctx(ctx context.Context) *gdb.Model { + model := dao.DB().Model(dao.table) + for _, handler := range dao.handlers { + model = handler(model) + } + return model.Safe().Ctx(ctx) +} + +// Transaction wraps the transaction logic using function f. +// It rolls back the transaction and returns the error if function f returns a non-nil error. +// It commits the transaction and returns nil if function f returns nil. +// +// Note: Do not commit or roll back the transaction in function f, +// as it is automatically handled by this function. +func (dao *V1CamelOilCardBindingDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) { + return dao.Ctx(ctx).Transaction(ctx, f) +} diff --git a/internal/dao/internal/v_1_camel_oil_token.go b/internal/dao/internal/v_1_camel_oil_token.go new file mode 100644 index 00000000..74de6900 --- /dev/null +++ b/internal/dao/internal/v_1_camel_oil_token.go @@ -0,0 +1,103 @@ +// ========================================================================== +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// ========================================================================== + +package internal + +import ( + "context" + + "github.com/gogf/gf/v2/database/gdb" + "github.com/gogf/gf/v2/frame/g" +) + +// V1CamelOilTokenDao is the data access object for the table camel_oil_token. +type V1CamelOilTokenDao struct { + table string // table is the underlying table name of the DAO. + group string // group is the database configuration group name of the current DAO. + columns V1CamelOilTokenColumns // columns contains all the column names of Table for convenient usage. + handlers []gdb.ModelHandler // handlers for customized model modification. +} + +// V1CamelOilTokenColumns defines and stores column names for the table camel_oil_token. +type V1CamelOilTokenColumns struct { + Id string // 主键ID + TokenName string // Token名称/标识 + TokenValue string // Token具体值 + Phone string // 绑定的手机号 + Status string // 状态:1可用 2已禁用 3已过期 + BindCount string // 已绑定卡密数量 + TotalBindAmount string // 累计绑定金额 + LastBindAt string // 最后绑定时间 + LastUsedAt string // 最后使用时间 + Remark string // 备注 + CreatedAt string // 创建时间 + UpdatedAt string // 更新时间 + DeletedAt string // 删除时间(软删除) +} + +// v1CamelOilTokenColumns holds the columns for the table camel_oil_token. +var v1CamelOilTokenColumns = V1CamelOilTokenColumns{ + Id: "id", + TokenName: "token_name", + TokenValue: "token_value", + Phone: "phone", + Status: "status", + BindCount: "bind_count", + TotalBindAmount: "total_bind_amount", + LastBindAt: "last_bind_at", + LastUsedAt: "last_used_at", + Remark: "remark", + CreatedAt: "created_at", + UpdatedAt: "updated_at", + DeletedAt: "deleted_at", +} + +// NewV1CamelOilTokenDao creates and returns a new DAO object for table data access. +func NewV1CamelOilTokenDao(handlers ...gdb.ModelHandler) *V1CamelOilTokenDao { + return &V1CamelOilTokenDao{ + group: "default", + table: "camel_oil_token", + columns: v1CamelOilTokenColumns, + handlers: handlers, + } +} + +// DB retrieves and returns the underlying raw database management object of the current DAO. +func (dao *V1CamelOilTokenDao) DB() gdb.DB { + return g.DB(dao.group) +} + +// Table returns the table name of the current DAO. +func (dao *V1CamelOilTokenDao) Table() string { + return dao.table +} + +// Columns returns all column names of the current DAO. +func (dao *V1CamelOilTokenDao) Columns() V1CamelOilTokenColumns { + return dao.columns +} + +// Group returns the database configuration group name of the current DAO. +func (dao *V1CamelOilTokenDao) Group() string { + return dao.group +} + +// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation. +func (dao *V1CamelOilTokenDao) Ctx(ctx context.Context) *gdb.Model { + model := dao.DB().Model(dao.table) + for _, handler := range dao.handlers { + model = handler(model) + } + return model.Safe().Ctx(ctx) +} + +// Transaction wraps the transaction logic using function f. +// It rolls back the transaction and returns the error if function f returns a non-nil error. +// It commits the transaction and returns nil if function f returns nil. +// +// Note: Do not commit or roll back the transaction in function f, +// as it is automatically handled by this function. +func (dao *V1CamelOilTokenDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) { + return dao.Ctx(ctx).Transaction(ctx, f) +} diff --git a/internal/dao/v_1_camel_oil_card_binding.go b/internal/dao/v_1_camel_oil_card_binding.go new file mode 100644 index 00000000..1a59cbb2 --- /dev/null +++ b/internal/dao/v_1_camel_oil_card_binding.go @@ -0,0 +1,22 @@ +// ================================================================================= +// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed. +// ================================================================================= + +package dao + +import ( + "kami/internal/dao/internal" +) + +// v1CamelOilCardBindingDao is the data access object for the table camel_oil_card_binding. +// You can define custom methods on it to extend its functionality as needed. +type v1CamelOilCardBindingDao struct { + *internal.V1CamelOilCardBindingDao +} + +var ( + // V1CamelOilCardBinding is a globally accessible object for table camel_oil_card_binding operations. + V1CamelOilCardBinding = v1CamelOilCardBindingDao{internal.NewV1CamelOilCardBindingDao()} +) + +// Add your custom methods and functionality below. diff --git a/internal/dao/v_1_camel_oil_token.go b/internal/dao/v_1_camel_oil_token.go new file mode 100644 index 00000000..3e797662 --- /dev/null +++ b/internal/dao/v_1_camel_oil_token.go @@ -0,0 +1,22 @@ +// ================================================================================= +// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed. +// ================================================================================= + +package dao + +import ( + "kami/internal/dao/internal" +) + +// v1CamelOilTokenDao is the data access object for the table camel_oil_token. +// You can define custom methods on it to extend its functionality as needed. +type v1CamelOilTokenDao struct { + *internal.V1CamelOilTokenDao +} + +var ( + // V1CamelOilToken is a globally accessible object for table camel_oil_token operations. + V1CamelOilToken = v1CamelOilTokenDao{internal.NewV1CamelOilTokenDao()} +) + +// Add your custom methods and functionality below. diff --git a/internal/logic/camel_oil/cron_tasks.go b/internal/logic/camel_oil/cron_tasks.go index 8c894ef3..d70be3c3 100644 --- a/internal/logic/camel_oil/cron_tasks.go +++ b/internal/logic/camel_oil/cron_tasks.go @@ -138,6 +138,7 @@ func (s *sCamelOil) CronOrderPaymentCheckTask(ctx context.Context) error { PayStatus: consts.CamelOilPaymentStatusPaid, }) _ = s.RecordOrderHistory(ctx, order.OrderNo, consts.CamelOilOrderChangeTypePaid, "", fmt.Sprintf("支付成功,金额: %.2f", queryResult.Balance)) + paidCount++ } continue @@ -162,7 +163,7 @@ func (s *sCamelOil) CronOrderPaymentCheckTask(ctx context.Context) error { return nil } -// CronAccountDailyResetTask 账号日重置任务 - 由cron调度器在每日00:05调用 +// CronAccountDailyResetTask 账号日重置任务 - 由cron调度器在每日00:00调用 func (s *sCamelOil) CronAccountDailyResetTask(ctx context.Context) error { glog.Info(ctx, "开始执行账号日重置任务") @@ -307,3 +308,68 @@ func (s *sCamelOil) CronVerifyCodeCheckTask(ctx context.Context) error { glog.Infof(ctx, "验证码检测任务完成: 成功=%d, 失败=%d", successCount, failCount) return nil } + +// CronCardBindingTask 卡密绑定定时任务 - 由cron调度器定期调用 +// 流程:处理已支付但未绑定 Token 的订单,进行卡密绑定 +func (s *sCamelOil) CronCardBindingTask(ctx context.Context) error { + glog.Info(ctx, "开始执行卡密绑定任务") + + // 查询已支付但未绑定 Token 的订单 + var orders []*entity.V1CamelOilOrder + err := dao.V1CamelOilOrder.Ctx(ctx).DB(config.GetDatabaseV1()). + Where(dao.V1CamelOilOrder.Columns().PayStatus, consts.CamelOilPaymentStatusPaid). + WhereNotIn(dao.V1CamelOilOrder.Columns().Id, + dao.V1CamelOilCardBinding.Ctx(ctx).DB(config.GetDatabaseV1()). + Fields(dao.V1CamelOilCardBinding.Columns().OrderId)). + Limit(50). + Scan(&orders) + + if err != nil { + glog.Error(ctx, "查询待绑定订单失败", err) + return err + } + + if len(orders) == 0 { + glog.Debug(ctx, "无待绑定订单") + return nil + } + + glog.Infof(ctx, "查询到 %d 个待绑定订单", len(orders)) + + successCount := 0 + failCount := 0 + + for _, order := range orders { + // 检查是否有可用的 Token + availableTokenCount, err := dao.V1CamelOilToken.Ctx(ctx).DB(config.GetDatabaseV1()). + Where(dao.V1CamelOilToken.Columns().Status, consts.CamelOilTokenStatusAvailable). + Count() + + if err != nil || availableTokenCount == 0 { + glog.Warningf(ctx, "无可用 Token,订单 ID: %d 无法绑定", order.Id) + failCount++ + continue + } + + // 检查卡号和卡密是否存在 + if order.CardNumber == "" || order.CardPassword == "" { + glog.Warningf(ctx, "订单 %d 卡号或卡密未填写,无法绑定", order.Id) + failCount++ + continue + } + + // 尝试绑定卡密到 Token + _, err = s.BindCardToToken(ctx, order.Id, order.CardNumber, order.CardPassword, order.Amount) + if err != nil { + glog.Errorf(ctx, "绑定卡密到 Token 失败,订单 ID: %d, 错误: %v", order.Id, err) + failCount++ + continue + } + + glog.Infof(ctx, "订单 %d 卡密绑定成功", order.Id) + successCount++ + } + + glog.Infof(ctx, "卡密绑定任务完成: 成功=%d, 失败=%d", successCount, failCount) + return nil +} diff --git a/internal/logic/camel_oil/order_callback.go b/internal/logic/camel_oil/order_callback.go index 1e5aa238..32f73e76 100644 --- a/internal/logic/camel_oil/order_callback.go +++ b/internal/logic/camel_oil/order_callback.go @@ -103,6 +103,7 @@ func (s *sCamelOil) TriggerOrderCallback(ctx context.Context, req *v1.OrderCallb // executeCallback 执行回调(内部方法) func (s *sCamelOil) executeCallback(ctx context.Context, order *entity.V1CamelOilOrder) (err error) { + // 查询订单信息 var orderInfo *entity.V1OrderInfo if err = dao.V1OrderInfo.Ctx(ctx).DB(config.GetDatabaseV1()).Where(dao.V1OrderInfo.Columns().BankOrderId, order.MerchantOrderId).Scan(&orderInfo); err != nil || orderInfo == nil || orderInfo.Id == 0 { glog.Error(ctx, "查询订单失败", g.Map{"userOrderId": order.MerchantOrderId, "err": err}) @@ -113,7 +114,7 @@ func (s *sCamelOil) executeCallback(ctx context.Context, order *entity.V1CamelOi glog.Error(ctx, "查询商户信息失败", g.Map{"merchantId": orderInfo.MerchantUid, "err": err}) return errors.New("商户不存在") } - // 发送HTTP回调请求 + // 发送 HTTP 回调请求 _, _ = gclient.New().Get(ctx, "http://kami_gateway:12309/myself/notify", g.Map{ "orderNo": order.OrderNo, "orderPrice": order.Amount, @@ -129,12 +130,17 @@ func (s *sCamelOil) executeCallback(ctx context.Context, order *entity.V1CamelOi // ProcessPendingCallbacks 处理待回调订单(定时任务使用) func (s *sCamelOil) ProcessPendingCallbacks(ctx context.Context) error { - // 查询需要回调的订单 + // 查询需要回调的订单(仅查询已绑定到 Token 的订单) var orders []*entity.V1CamelOilOrder err := dao.V1CamelOilOrder.Ctx(ctx).DB(config.GetDatabaseV1()). + InnerJoin(dao.V1CamelOilCardBinding.Table(), fmt.Sprintf("%s.%s = %s.%s", + dao.V1CamelOilOrder.Table(), dao.V1CamelOilOrder.Columns().Id, + dao.V1CamelOilCardBinding.Table(), dao.V1CamelOilCardBinding.Columns().OrderId)). Where(dao.V1CamelOilOrder.Columns().PayStatus, consts.CamelOilPaymentStatusPaid). Where(dao.V1CamelOilOrder.Columns().NotifyStatus, consts.CamelOilCallbackStatusPending). WhereLTE(dao.V1CamelOilOrder.Columns().NotifyCount, 3). + Fields(dao.V1CamelOilOrder.Table() + ".*"). + Distinct(). Limit(50). Scan(&orders) diff --git a/internal/logic/camel_oil/token.go b/internal/logic/camel_oil/token.go new file mode 100644 index 00000000..3a866501 --- /dev/null +++ b/internal/logic/camel_oil/token.go @@ -0,0 +1,359 @@ +package camel_oil + +import ( + "context" + + "github.com/gogf/gf/v2/database/gdb" + "github.com/gogf/gf/v2/util/gconv" + + "github.com/gogf/gf/v2/errors/gerror" + "github.com/gogf/gf/v2/os/glog" + "github.com/gogf/gf/v2/os/gtime" + "github.com/shopspring/decimal" + + "kami/api/commonApi" + "kami/internal/consts" + "kami/internal/dao" + "kami/internal/model/do" + "kami/internal/model/entity" + "kami/utility/config" + "kami/utility/integration/camel_oil_api" +) + +// ==================================================================================== +// Token 管理相关方法 +// ==================================================================================== + +// CreateToken 创建 Token +func (s *sCamelOil) CreateToken(ctx context.Context, tokenName string, tokenValue string, phone string, remark string) (tokenId int64, err error) { + m := dao.V1CamelOilToken.Ctx(ctx).DB(config.GetDatabaseV1()) + + result, err := m.Insert(&do.V1CamelOilToken{ + TokenName: tokenName, + TokenValue: tokenValue, + Phone: phone, + Status: int(consts.CamelOilTokenStatusAvailable), + BindCount: 0, + Remark: remark, + }) + + if err != nil { + return 0, gerror.Wrap(err, "创建 Token失败") + } + + tokenId, _ = result.LastInsertId() + glog.Infof(ctx, "Token创建成功: tokenId=%d, tokenName=%s, phone=%s", tokenId, tokenName, phone) + + return tokenId, nil +} + +// GetTokenInfo 获取 Token 信息 +func (s *sCamelOil) GetTokenInfo(ctx context.Context, tokenId int64) (token *entity.V1CamelOilToken, err error) { + m := dao.V1CamelOilToken.Ctx(ctx).DB(config.GetDatabaseV1()) + + err = m.Where(dao.V1CamelOilToken.Columns().Id, tokenId).Scan(&token) + if err != nil { + return nil, gerror.Wrap(err, "查询Token信息失败") + } + if token == nil { + return nil, gerror.New("Token不存在") + } + + return token, nil +} + +// ListTokens 列出所有可用的 Token +func (s *sCamelOil) ListTokens(ctx context.Context) (tokens []*entity.V1CamelOilToken, err error) { + m := dao.V1CamelOilToken.Ctx(ctx).DB(config.GetDatabaseV1()) + + err = m.Where(dao.V1CamelOilToken.Columns().Status, consts.CamelOilTokenStatusAvailable). + OrderAsc(dao.V1CamelOilToken.Columns().LastUsedAt). + OrderAsc(dao.V1CamelOilToken.Columns().LastBindAt). + Scan(&tokens) + if err != nil { + return nil, gerror.Wrap(err, "查询Token列表失败") + } + + return tokens, nil +} + +// ListTokensWithPagination 列出所有可用的 Token(支持分页和查询条件) +func (s *sCamelOil) ListTokensWithPagination(ctx context.Context, pageReq commonApi.CommonPageReq, tokenName string, status int) (tokens []*entity.V1CamelOilToken, total int, err error) { + m := dao.V1CamelOilToken.Ctx(ctx).DB(config.GetDatabaseV1()) + + query := m.Where(dao.V1CamelOilToken.Columns().DeletedAt, nil) + + // 添加查询条件 + if tokenName != "" { + query = query.WhereLike(dao.V1CamelOilToken.Columns().TokenName, "%"+tokenName+"%") + } + + if status > 0 { + query = query.Where(dao.V1CamelOilToken.Columns().Status, status) + } + + // 获取总数 + totalCount, err := query.Count() + if err != nil { + return nil, 0, gerror.Wrap(err, "查询Token总数失败") + } + + // 分页查询 + err = query. + Offset((pageReq.Current - 1) * pageReq.PageSize). + Limit(pageReq.PageSize). + OrderDesc(dao.V1CamelOilToken.Columns().CreatedAt). + Scan(&tokens) + + if err != nil { + return nil, 0, gerror.Wrap(err, "查询Token列表失败") + } + + return tokens, int(totalCount), nil +} + +// UpdateToken 更新 Token 信息 +func (s *sCamelOil) UpdateToken(ctx context.Context, tokenId int64, tokenName string, status int, remark string) error { + m := dao.V1CamelOilToken.Ctx(ctx).DB(config.GetDatabaseV1()) + + _, err := m.Where(dao.V1CamelOilToken.Columns().Id, tokenId).Update(&do.V1CamelOilToken{ + TokenName: tokenName, + Status: status, + Remark: remark, + UpdatedAt: gtime.Now(), + }) + + if err != nil { + return gerror.Wrap(err, "更新Token失败") + } + + glog.Infof(ctx, "Token更新成功: tokenId=%d", tokenId) + return nil +} + +// DeleteToken 删除 Token(软删除) +func (s *sCamelOil) DeleteToken(ctx context.Context, tokenId int64) error { + m := dao.V1CamelOilToken.Ctx(ctx).DB(config.GetDatabaseV1()) + + _, err := m.Where(dao.V1CamelOilToken.Columns().Id, tokenId). + Update(&do.V1CamelOilToken{ + DeletedAt: gtime.Now(), + }) + + if err != nil { + return gerror.Wrap(err, "删除Token失败") + } + + glog.Infof(ctx, "Token删除成功: tokenId=%d", tokenId) + return nil +} + +// UpdateTokenStatus 更新 Token 状态并记录日志 +func (s *sCamelOil) UpdateTokenStatus(ctx context.Context, tokenId int64, newStatus consts.CamelOilTokenStatus, remark string) error { + m := dao.V1CamelOilToken.Ctx(ctx).DB(config.GetDatabaseV1()) + + // 获取当前 Token 信息 + var token *entity.V1CamelOilToken + err := m.Where(dao.V1CamelOilToken.Columns().Id, tokenId).Scan(&token) + if err != nil { + return gerror.Wrap(err, "查询Token失败") + } + if token == nil { + return gerror.New("Token不存在") + } + + oldStatus := consts.CamelOilTokenStatus(token.Status) + + // 如果状态没有变化,则不更新 + if oldStatus == newStatus { + return nil + } + + // 更新 Token 状态 + _, err = m.Where(dao.V1CamelOilToken.Columns().Id, tokenId).Update(&do.V1CamelOilToken{ + Status: int(newStatus), + }) + + if err != nil { + return gerror.Wrap(err, "更新Token状态失败") + } + + glog.Infof(ctx, "Token状态更新成功: tokenId=%d, 原状态=%s, 新状态=%s, 备注=%s", tokenId, consts.CamelOilTokenStatusText[oldStatus], consts.CamelOilTokenStatusText[newStatus], remark) + return nil +} + +// ==================================================================================== +// 卡密绑定相关方法 +// ==================================================================================== + +// BindCardToToken 绑定卡密到 Token(使用轮询算法选择 Token) +func (s *sCamelOil) BindCardToToken(ctx context.Context, orderId int64, cardNumber string, cardPassword string, amount decimal.Decimal) (bindingId int64, err error) { + // 1. 获取订单信息 + var order *entity.V1CamelOilOrder + err = dao.V1CamelOilOrder.Ctx(ctx).DB(config.GetDatabaseV1()). + Where(dao.V1CamelOilOrder.Columns().Id, orderId). + Scan(&order) + if err != nil { + return 0, gerror.Wrap(err, "查询订单失败") + } + if order == nil { + return 0, gerror.New("订单不存在") + } + + // 2. 获取所有可用的 Token + tokens, err := s.ListTokens(ctx) + if err != nil { + return 0, gerror.Wrap(err, "查询Token列表失败") + } + + if len(tokens) == 0 { + return 0, gerror.New("没有可用的Token") + } + + // 3. 使用轮询算法选择 Token(选择绑定金额最少的 Token) + var selectedToken *entity.V1CamelOilToken + minAmount := tokens[0].TotalBindAmount + selectedToken = tokens[0] + + for _, token := range tokens { + if token.TotalBindAmount.Cmp(minAmount) < 0 { + minAmount = token.TotalBindAmount + selectedToken = token + } + } + + // 4.2 调用绑卡接口(使用选中 Token 的 TokenValue) + rechargeErrType, rechargeErr := camel_oil_api.NewClient().RechargeCard(ctx, selectedToken.TokenValue, selectedToken.Phone, cardPassword) + if rechargeErr != nil { + switch rechargeErrType { + case camel_oil_api.RechargeCardErrorCode: + // 卡密错误:标记订单为绑定失败 + glog.Warningf(ctx, "卡密错误: %v", rechargeErr) + // 调用已实现的方法更新订单状态 + _ = s.UpdateOrderStatus(ctx, orderId, consts.CamelOilOrderStatusFailed, consts.CamelOilOrderChangeTypeFail, "", "卡密样检失败") + return 0, gerror.Wrap(rechargeErr, "卡密样检失败") + case camel_oil_api.RechargeCardErrorToken: + // Token 过期/无效:标记 Token 为已过期 + glog.Warningf(ctx, "Token 过期: %v", rechargeErr) + // 调用已实现的方法更新 Token 状态 + _ = s.UpdateTokenStatus(ctx, selectedToken.Id, consts.CamelOilTokenStatusExpired, "Token已过期") + return 0, gerror.Wrap(rechargeErr, "Token过期,需要重新登录") + default: + // 网络或其他错误:不更新状态,由定时任务重试 + glog.Errorf(ctx, "绑卡失败(网络或其他错误): %v", rechargeErr) + return 0, gerror.Wrap(rechargeErr, "绑卡操作失败,稍后重试") + } + } + + // 5. 创建绑定记录 + bindingResult, err := dao.V1CamelOilCardBinding.Ctx(ctx).DB(config.GetDatabaseV1()). + Insert(&do.V1CamelOilCardBinding{ + TokenId: selectedToken.Id, + OrderId: orderId, + CardNumber: cardNumber, + CardPassword: cardPassword, + Amount: amount, + }) + + if err != nil { + return 0, gerror.Wrap(err, "创建绑定记录失败") + } + + bindingId, _ = bindingResult.LastInsertId() + + _, _ = dao.V1CamelOilToken.Ctx(ctx).DB(config.GetDatabaseV1()).Where(dao.V1CamelOilToken.Columns().Id, selectedToken.Id). + Data(dao.V1CamelOilToken.Columns().TotalBindAmount, &gdb.Counter{ + Field: dao.V1CamelOilToken.Columns().TotalBindAmount, + Value: gconv.Float64(amount), + }).Data(dao.V1CamelOilToken.Columns().BindCount, &gdb.Counter{ + Field: dao.V1CamelOilToken.Columns().BindCount, + Value: gconv.Float64(amount), + }).Data(dao.V1CamelOilToken.Columns().LastBindAt, gtime.Now()).Data(dao.V1CamelOilToken.Columns().LastUsedAt, gtime.Now()).Update() + + glog.Infof(ctx, "卡密绑定成功: bindingId=%d, tokenId=%d, 订单ID=%d, 绑定金额=%.2f", + bindingId, selectedToken.Id, orderId, amount.InexactFloat64()) + + return bindingId, nil +} + +// GetCardBindingInfo 获取卡密绑定信息 +func (s *sCamelOil) GetCardBindingInfo(ctx context.Context, bindingId int64) (binding *entity.V1CamelOilCardBinding, err error) { + m := dao.V1CamelOilCardBinding.Ctx(ctx).DB(config.GetDatabaseV1()) + + err = m.Where(dao.V1CamelOilCardBinding.Columns().Id, bindingId).Scan(&binding) + if err != nil { + return nil, gerror.Wrap(err, "查询绑定信息失败") + } + if binding == nil { + return nil, gerror.New("绑定记录不存在") + } + + return binding, nil +} + +// GetCardBindingByOrder 获取订单绑定的卡密信息 +func (s *sCamelOil) GetCardBindingByOrder(ctx context.Context, orderId int64) (binding *entity.V1CamelOilCardBinding, err error) { + m := dao.V1CamelOilCardBinding.Ctx(ctx).DB(config.GetDatabaseV1()) + + err = m.Where(dao.V1CamelOilCardBinding.Columns().OrderId, orderId).Scan(&binding) + if err != nil { + return nil, gerror.Wrap(err, "查询绑定信息失败") + } + + return binding, nil +} + +// GetCardBindingsByToken 根据 tokenId 查询绑定的卡密信息 +func (s *sCamelOil) GetCardBindingsByToken(ctx context.Context, tokenId int64, current int, pageSize int) (bindings []*entity.V1CamelOilCardBinding, total int, err error) { + m := dao.V1CamelOilCardBinding.Ctx(ctx).DB(config.GetDatabaseV1()) + + count, err := m.Where(dao.V1CamelOilCardBinding.Columns().TokenId, tokenId).Count() + if err != nil { + return nil, 0, gerror.Wrap(err, "查询绑定记录计数失败") + } + + err = m.Where(dao.V1CamelOilCardBinding.Columns().TokenId, tokenId). + Page(current, pageSize). + Scan(&bindings) + if err != nil { + return nil, 0, gerror.Wrap(err, "查询绑定记录失败") + } + + return bindings, int(count), nil +} + +// GetTokenBindingStats 获取 Token 的绑定统计 +func (s *sCamelOil) GetTokenBindingStats(ctx context.Context, tokenId int64) (bindCount int, totalAmount decimal.Decimal, err error) { + m := dao.V1CamelOilCardBinding.Ctx(ctx).DB(config.GetDatabaseV1()) + + var stats struct { + BindCount int + TotalAmount decimal.Decimal + } + + err = m.Where(dao.V1CamelOilCardBinding.Columns().TokenId, tokenId). + Fields("COUNT(*) as bind_count, SUM(amount) as total_amount"). + Scan(&stats) + + if err != nil { + return 0, decimal.Zero, gerror.Wrap(err, "查询Token绑定统计失败") + } + + return stats.BindCount, stats.TotalAmount, nil +} + +// CalculateTotalBindingAmount 计算所有 Token 的累计绑定金额 +func (s *sCamelOil) CalculateTotalBindingAmount(ctx context.Context) (totalAmount decimal.Decimal, err error) { + m := dao.V1CamelOilCardBinding.Ctx(ctx).DB(config.GetDatabaseV1()) + + var result struct { + Total decimal.Decimal + } + + err = m.Fields("SUM(amount) as total").Scan(&result) + if err != nil { + return decimal.Zero, gerror.Wrap(err, "计算累计绑定金额失败") + } + + return result.Total, nil +} diff --git a/internal/middleware/auth.go b/internal/middleware/auth.go index 33959599..8a15b383 100644 --- a/internal/middleware/auth.go +++ b/internal/middleware/auth.go @@ -46,6 +46,7 @@ func whiteListAuth(r *ghttp.Request) gcode.Code { "/api/restriction/collection/userInfo", "/api/cookieInfo/jd/order/placeOrder", "/api/jd-cookie/order/create", + "/api/jd-v2/order/submit", } internalWhiteListAuth := []string{ "/api/aes/encryption/params", diff --git a/internal/model/do/v_1_camel_oil_card_binding.go b/internal/model/do/v_1_camel_oil_card_binding.go new file mode 100644 index 00000000..2ad1ce2d --- /dev/null +++ b/internal/model/do/v_1_camel_oil_card_binding.go @@ -0,0 +1,25 @@ +// ================================================================================= +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// ================================================================================= + +package do + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gtime" +) + +// V1CamelOilCardBinding is the golang structure of table camel_oil_card_binding for DAO operations like Where/Data. +type V1CamelOilCardBinding struct { + g.Meta `orm:"table:camel_oil_card_binding, do:true"` + Id any // 主键ID + TokenId any // Token ID + OrderId any // 订单ID + CardNumber any // 卡号 + CardPassword any // 卡密 + Amount any // 绑定金额 + Remark any // 备注 + CreatedAt *gtime.Time // 创建时间 + UpdatedAt *gtime.Time // 更新时间 + DeletedAt *gtime.Time // 删除时间(软删除) +} diff --git a/internal/model/do/v_1_camel_oil_token.go b/internal/model/do/v_1_camel_oil_token.go new file mode 100644 index 00000000..a289b1b0 --- /dev/null +++ b/internal/model/do/v_1_camel_oil_token.go @@ -0,0 +1,28 @@ +// ================================================================================= +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// ================================================================================= + +package do + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gtime" +) + +// V1CamelOilToken is the golang structure of table camel_oil_token for DAO operations like Where/Data. +type V1CamelOilToken struct { + g.Meta `orm:"table:camel_oil_token, do:true"` + Id any // 主键ID + TokenName any // Token名称/标识 + TokenValue any // Token具体值 + Phone any // 绑定的手机号 + Status any // 状态:1可用 2已禁用 3已过期 + BindCount any // 已绑定卡密数量 + TotalBindAmount any // 累计绑定金额 + LastBindAt *gtime.Time // 最后绑定时间 + LastUsedAt *gtime.Time // 最后使用时间 + Remark any // 备注 + CreatedAt *gtime.Time // 创建时间 + UpdatedAt *gtime.Time // 更新时间 + DeletedAt *gtime.Time // 删除时间(软删除) +} diff --git a/internal/model/entity/v_1_camel_oil_card_binding.go b/internal/model/entity/v_1_camel_oil_card_binding.go new file mode 100644 index 00000000..d3e5a3a1 --- /dev/null +++ b/internal/model/entity/v_1_camel_oil_card_binding.go @@ -0,0 +1,24 @@ +// ================================================================================= +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// ================================================================================= + +package entity + +import ( + "github.com/gogf/gf/v2/os/gtime" + "github.com/shopspring/decimal" +) + +// V1CamelOilCardBinding is the golang structure for table v1camel_oil_card_binding. +type V1CamelOilCardBinding struct { + Id int64 `json:"id" orm:"id" description:"主键ID"` + TokenId int64 `json:"tokenId" orm:"token_id" description:"Token ID"` + OrderId int64 `json:"orderId" orm:"order_id" description:"订单ID"` + CardNumber string `json:"cardNumber" orm:"card_number" description:"卡号"` + CardPassword string `json:"cardPassword" orm:"card_password" description:"卡密"` + Amount decimal.Decimal `json:"amount" orm:"amount" description:"绑定金额"` + Remark string `json:"remark" orm:"remark" description:"备注"` + CreatedAt *gtime.Time `json:"createdAt" orm:"created_at" description:"创建时间"` + UpdatedAt *gtime.Time `json:"updatedAt" orm:"updated_at" description:"更新时间"` + DeletedAt *gtime.Time `json:"deletedAt" orm:"deleted_at" description:"删除时间(软删除)"` +} diff --git a/internal/model/entity/v_1_camel_oil_token.go b/internal/model/entity/v_1_camel_oil_token.go new file mode 100644 index 00000000..8046f4a8 --- /dev/null +++ b/internal/model/entity/v_1_camel_oil_token.go @@ -0,0 +1,27 @@ +// ================================================================================= +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// ================================================================================= + +package entity + +import ( + "github.com/gogf/gf/v2/os/gtime" + "github.com/shopspring/decimal" +) + +// V1CamelOilToken is the golang structure for table v1camel_oil_token. +type V1CamelOilToken struct { + Id int64 `json:"id" orm:"id" description:"主键ID"` + TokenName string `json:"tokenName" orm:"token_name" description:"Token名称/标识"` + TokenValue string `json:"tokenValue" orm:"token_value" description:"Token具体值"` + Phone string `json:"phone" orm:"phone" description:"绑定的手机号"` + Status int `json:"status" orm:"status" description:"状态:1可用 2已禁用 3已过期"` + BindCount int `json:"bindCount" orm:"bind_count" description:"已绑定卡密数量"` + TotalBindAmount decimal.Decimal `json:"totalBindAmount" orm:"total_bind_amount" description:"累计绑定金额"` + LastBindAt *gtime.Time `json:"lastBindAt" orm:"last_bind_at" description:"最后绑定时间"` + LastUsedAt *gtime.Time `json:"lastUsedAt" orm:"last_used_at" description:"最后使用时间"` + Remark string `json:"remark" orm:"remark" description:"备注"` + CreatedAt *gtime.Time `json:"createdAt" orm:"created_at" description:"创建时间"` + UpdatedAt *gtime.Time `json:"updatedAt" orm:"updated_at" description:"更新时间"` + DeletedAt *gtime.Time `json:"deletedAt" orm:"deleted_at" description:"删除时间(软删除)"` +} diff --git a/internal/service/camel_oil.go b/internal/service/camel_oil.go index e45cf095..c37299c0 100644 --- a/internal/service/camel_oil.go +++ b/internal/service/camel_oil.go @@ -7,10 +7,14 @@ package service import ( "context" + v1 "kami/api/camel_oil/v1" + commonApi "kami/api/commonApi" "kami/internal/consts" "kami/internal/model" "kami/internal/model/entity" + + "github.com/shopspring/decimal" ) type ( @@ -65,10 +69,10 @@ type ( // CronAccountDailyResetTask 账号日重置任务 - 由cron调度器在每日00:05调用 CronAccountDailyResetTask(ctx context.Context) error CronVerifyCodeCheckTask(ctx context.Context) error - // SubmitOrder 提交订单并返回支付宝支付链接 - SubmitOrder(ctx context.Context, req *v1.SubmitOrderReq) (res *v1.SubmitOrderRes, err error) // UpdateOrderStatus 更新订单状态并记录历史 UpdateOrderStatus(ctx context.Context, orderId int64, newStatus consts.CamelOilOrderStatus, operationType consts.CamelOilOrderChangeType, rawData string, description string) (err error) + // SubmitOrder 提交订单并返回支付宝支付链接 + SubmitOrder(ctx context.Context, req *v1.SubmitOrderReq) (res *v1.SubmitOrderRes, err error) // TriggerOrderCallback 触发订单回调 TriggerOrderCallback(ctx context.Context, req *v1.OrderCallbackReq) (res *v1.OrderCallbackRes, err error) // ProcessPendingCallbacks 处理待回调订单(定时任务使用) @@ -99,6 +103,36 @@ type ( RecordPrefetchOrderHistory(ctx context.Context, prefetchId int64, changeType consts.CamelOilPrefetchOrderChangeType, accountId int64, accountName string, remark string) error // CleanExpiredPrefetchOrders 清理过期的预拉取订单 CleanExpiredPrefetchOrders(ctx context.Context) (cleanedCount int, err error) + + // ==================================================================================== + // Token 管理相关方法 + // ==================================================================================== + + // CreateToken 创建 Token + CreateToken(ctx context.Context, tokenName string, tokenValue string, phone string, remark string) (tokenId int64, err error) + // GetTokenInfo 获取 Token 信息 + GetTokenInfo(ctx context.Context, tokenId int64) (token *entity.V1CamelOilToken, err error) + // ListTokens 列出所有可用的 Token + ListTokens(ctx context.Context) (tokens []*entity.V1CamelOilToken, err error) + // ListTokensWithPagination 列出所有可用的 Token(支持分页和查询条件) + ListTokensWithPagination(ctx context.Context, pageReq commonApi.CommonPageReq, tokenName string, status int) (tokens []*entity.V1CamelOilToken, total int, err error) + // UpdateToken 更新 Token 信息 + UpdateToken(ctx context.Context, tokenId int64, tokenName string, status int, remark string) error + // DeleteToken 删除 Token(软删除) + DeleteToken(ctx context.Context, tokenId int64) error + + // BindCardToToken 绑定卡密到 Token(使用轮询算法选择 Token) + BindCardToToken(ctx context.Context, orderId int64, cardNumber string, cardPassword string, amount decimal.Decimal) (bindingId int64, err error) + // GetCardBindingInfo 获取卡密绑定信息 + GetCardBindingInfo(ctx context.Context, bindingId int64) (binding *entity.V1CamelOilCardBinding, err error) + // GetCardBindingByOrder 获取订单绑定的卡密信息 + GetCardBindingByOrder(ctx context.Context, orderId int64) (binding *entity.V1CamelOilCardBinding, err error) + // GetCardBindingsByToken 根据 tokenId 查询绑定的卡密信息 + GetCardBindingsByToken(ctx context.Context, tokenId int64, current int, pageSize int) (bindings []*entity.V1CamelOilCardBinding, total int, err error) + // GetTokenBindingStats 获取 Token 的绑定统计 + GetTokenBindingStats(ctx context.Context, tokenId int64) (bindCount int, totalAmount decimal.Decimal, err error) + // CalculateTotalBindingAmount 计算所有 Token 的累计绑定金额 + CalculateTotalBindingAmount(ctx context.Context) (totalAmount decimal.Decimal, err error) } ) diff --git a/sql/camel_oil_tables.sql b/sql/camel_oil_tables.sql index 8ca36d0f..d424bbba 100644 --- a/sql/camel_oil_tables.sql +++ b/sql/camel_oil_tables.sql @@ -171,3 +171,55 @@ CREATE TABLE `camel_oil_prefetch_order_history` ( KEY `idx_created_at` (`created_at`), KEY `idx_deleted_at` (`deleted_at`) ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '骆驼加油预拉取订单历史表'; + +-- 7. 骆驼加油 Token 管理表 +DROP TABLE IF EXISTS `camel_oil_token`; + +CREATE TABLE `camel_oil_token` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `token_name` varchar(128) NOT NULL COMMENT 'Token名称/标识', + `token_value` text NOT NULL COMMENT 'Token具体值', + `phone` varchar(20) DEFAULT NULL COMMENT '绑定的手机号', + `status` tinyint NOT NULL DEFAULT 1 COMMENT '状态:1可用 2已禁用 3已过期', + `bind_count` int NOT NULL DEFAULT 0 COMMENT '已绑定卡密数量', + `total_bind_amount` decimal(15,2) NOT NULL DEFAULT 0 COMMENT '累计绑定金额', + `last_bind_at` datetime DEFAULT NULL COMMENT '最后绑定时间', + `last_used_at` datetime DEFAULT NULL COMMENT '最后使用时间', + `remark` text DEFAULT NULL COMMENT '备注', + `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `deleted_at` datetime DEFAULT NULL COMMENT '删除时间(软删除)', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_token_value` (`token_value`(255)), + KEY `idx_token_name` (`token_name`), + KEY `idx_phone` (`phone`), + KEY `idx_status` (`status`), + KEY `idx_bind_count` (`bind_count`), + KEY `idx_total_bind_amount` (`total_bind_amount`), + KEY `idx_last_bind_at` (`last_bind_at`), + KEY `idx_deleted_at` (`deleted_at`) +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '骆驼加油Token管理表'; + +-- 8. 骆驼加油卡密绑定记录表 +DROP TABLE IF EXISTS `camel_oil_card_binding`; + +CREATE TABLE `camel_oil_card_binding` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `token_id` bigint NOT NULL COMMENT 'Token ID', + `order_id` bigint NOT NULL COMMENT '订单ID', + `card_number` varchar(256) DEFAULT NULL COMMENT '卡号', + `card_password` varchar(256) DEFAULT NULL COMMENT '卡密', + `amount` decimal(10,2) NOT NULL COMMENT '绑定金额', + `remark` text DEFAULT NULL COMMENT '备注', + `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `deleted_at` datetime DEFAULT NULL COMMENT '删除时间(软删除)', + PRIMARY KEY (`id`), + KEY `idx_token_id` (`token_id`), + CONSTRAINT `fk_camel_oil_card_binding_token_id` FOREIGN KEY (`token_id`) REFERENCES `camel_oil_token` (`id`) ON DELETE RESTRICT, + KEY `idx_order_id` (`order_id`), + CONSTRAINT `fk_camel_oil_card_binding_order_id` FOREIGN KEY (`order_id`) REFERENCES `camel_oil_order` (`id`) ON DELETE CASCADE, + KEY `idx_amount` (`amount`), + KEY `idx_created_at` (`created_at`), + KEY `idx_deleted_at` (`deleted_at`) +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '骆驼加油卡密绑定记录表'; diff --git a/utility/cron/cron.go b/utility/cron/cron.go index cc651d51..918de273 100644 --- a/utility/cron/cron.go +++ b/utility/cron/cron.go @@ -76,20 +76,25 @@ func registerMainTasks(ctx context.Context) { // registerCamelOilTasks 注册骆驼加油模块的定时任务 func registerCamelOilTasks(ctx context.Context) { - // 账户预拉取任务 - 每30秒执行一次 - // 流程:并发拉取账户到指定数量 - //_, _ = gcron.AddSingleton(ctx, "@every 10s", func(ctx context.Context) { - // _ = service.CamelOil().CronAccountPrefetchTask(ctx) - //}, "CamelOilAccountPrefetch") - // - //_, _ = gcron.AddSingleton(ctx, "@every 10s", func(ctx context.Context) { - // _ = service.CamelOil().CronVerifyCodeCheckTask(ctx) - //}, "CamelOilAccountVerifyCodeCheck") + _, _ = gcron.AddSingleton(ctx, "@every 10s", func(ctx context.Context) { + _ = service.CamelOil().CronAccountPrefetchTask(ctx) + }, "CamelOilAccountPrefetch") - //// 流程:检查预拉取订单库存,不足时补充 - //_, _ = gcron.AddSingleton(ctx, "@every 10s", func(ctx context.Context) { - // _ = service.CamelOil().CronPrefetchOrderSupplementTask(ctx) - //}, "CamelOilPrefetchOrderSupplement") + _, _ = gcron.AddSingleton(ctx, "@every 10s", func(ctx context.Context) { + _ = service.CamelOil().CronVerifyCodeCheckTask(ctx) + }, "CamelOilAccountVerifyCodeCheck") + + _, _ = gcron.AddSingleton(ctx, "@every 10s", func(ctx context.Context) { + _ = service.CamelOil().CronOrderPaymentCheckTask(ctx) + }, "CamelOilOrderPaymentCheck") + + _, _ = gcron.AddSingleton(ctx, "0 1 0 * * ?", func(ctx context.Context) { + _ = service.CamelOil().CronAccountDailyResetTask(ctx) + }, "CamelOilAccountDailyReset") + + _, _ = gcron.AddSingleton(ctx, "@every 10s", func(ctx context.Context) { + _ = service.CamelOil().ProcessPendingCallbacks(ctx) + }, "CamelOilProcessPendingCallbacks") glog.Info(ctx, "骆驼加油模块定时任务注册完成") } diff --git a/utility/integration/camel_oil_api/api.go b/utility/integration/camel_oil_api/api.go index 97112aef..9852fb23 100644 --- a/utility/integration/camel_oil_api/api.go +++ b/utility/integration/camel_oil_api/api.go @@ -4,10 +4,20 @@ import ( "context" "encoding/json" "errors" + "math/rand/v2" + "strings" + "github.com/gogf/gf/v2/net/gclient" "github.com/gogf/gf/v2/os/glog" "github.com/google/uuid" - "strings" +) + +// 绑卡错误类型枚举 +const ( + RechargeCardSuccess = 0 // 绑卡成功 + RechargeCardErrorCode = 1 // 卡密错误 + RechargeCardErrorToken = 2 // Token 过期/无效 + RechargeCardErrorNetwork = 3 // 网络或其他错误 ) type Client struct { @@ -142,14 +152,14 @@ func (c *Client) CreateOrder(ctx context.Context, phone, token string, amount fl Brand string `json:"brand"` DeviceId string `json:"deviceId"` }{ - OpenId: "app2511181557205741495", + OpenId: "app2511221747117503039", Phone: phone, GoodId: goodId, GoodNum: 1, BindPhone: phone, PayType: "appAli", - ParamY: 26.996671, - ParamX: 77.450347, + ParamY: 31.2304 + (rand.Float64()-0.5)*2, + ParamX: 121.4737 + (rand.Float64()-0.5)*2, Yanqian: true, MobileOperatingPlatform: "iOS", SysVersion: "iOS 15.7", @@ -157,8 +167,9 @@ func (c *Client) CreateOrder(ctx context.Context, phone, token string, amount fl NetWork: "unknown", Platform: "ios", Brand: "apple", - DeviceId: strings.Replace(uuid.NewString(), "-", "", -1), + DeviceId: strings.ToUpper(strings.ReplaceAll(uuid.NewString(), "-", "")), } + pubkey := "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCkc6Xr/JhWEx/WPxG2q3VHLQ+FYk/oCmQ1y14B5j4xOJY+mAWoDOOti3sAXg0Kk662gWjWET1nLI6YED4wb9HWon1NAZn47lgc5ohIpEdU91Jao85X/kgkD3NvTTvhFicttepUOsrYUZN8rAQCE7AhzwGgKnCiIRY/kE8jOCCeZQIDAQAB" body, _ := json.Marshal(&bodyStr) bodyS, _ := EncryptWithPublicKey(pubkey, string(body)) @@ -176,7 +187,7 @@ func (c *Client) CreateOrder(ctx context.Context, phone, token string, amount fl return "", "", err } respStr := resp.ReadAllString() - glog.Info(ctx, "登录", req, respStr) + glog.Info(ctx, "登录", bodyStr, respStr) respStruct := struct { Code string `json:"code"` Message string `json:"message"` @@ -195,9 +206,14 @@ func (c *Client) CreateOrder(ctx context.Context, phone, token string, amount fl if respStruct.Code == "auth_error" { return "", "", errors.New("auth_error") } - base64PrivateKey := "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAKRzpev8mFYTH9Y/EbardUctD4ViT+gKZDXLXgHmPjE4lj6YBagM462LewBeDqTrraBaNYRPWcsjpgQPjBv0daifU0BmfjuWBzmiEikR1T3Ulqjzlf+SCQPc29NO+EWJy216l6ythRk3ysBAITsCHPAaAqcKIhFj+QTyM4IJ5lAgMBAAECgYEAg/vlMI7b3EkhBhw8JTVavLMnf8+1fe/JGXuMiU22oF5gBwCPmZ4upLwLDfJt2Q1J7WPTNetEMqgKEXUH1GwKJkFGm2tSEMHSHdTmUTQ3w6bS1C0peZghyhmlWRXUlpKk5tDOQ24sWO268YrwZyueXnVGKJ4s0hY0KOiZIU2trUECQQDxA+lzq/t/L09M/bUybjsiP6eb/HBeZeu+2+JnHekb8z9BMXTOKTHqAI0Cs9UvE6BDT3aU9IJbWHbRogIMypT9AkEArq0fccphwWtAIyS0+fns4Hqs4On7yTfXSXWiAbSVif1LxP60b5n5Xm8lo12oHkdwOvKaesvgDpnIGUM9xjFfiQJASTZrABxKNYRljnmzRTJ+/BRiEdxJNiO3zS52Q+SuHzNxD5i6ZrXU18R7EUsXg0lu8YN9/hmYT687yMpx3Pjc8QJAZBs1lSouQgIsPLfRvB1+otvLbg7KzPPivufak+2hcfanUNvEHt14a6V5RZnsOoYojK/y1oM3AkchxVCi+43aOQJAC0gI6qsZ3VaPu9QDddrHPJ1dCHTXyfcNJ0op3srCVF92HoBWX54pzeagj+9g/Z4oUT9IhaO0Q3YE07N03HuVrQ==" - respData, _ := DecryptWithPrivateKey(base64PrivateKey, respStruct.OrderRes.Body) - return respStruct.OrderId, respData, err + if respStruct.Code == "error" && strings.Contains(respStruct.Message, "系统繁忙") { + continue + } + if respStruct.Code == "success" { + base64PrivateKey := "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAKRzpev8mFYTH9Y/EbardUctD4ViT+gKZDXLXgHmPjE4lj6YBagM462LewBeDqTrraBaNYRPWcsjpgQPjBv0daifU0BmfjuWBzmiEikR1T3Ulqjzlf+SCQPc29NO+EWJy216l6ythRk3ysBAITsCHPAaAqcKIhFj+QTyM4IJ5lAgMBAAECgYEAg/vlMI7b3EkhBhw8JTVavLMnf8+1fe/JGXuMiU22oF5gBwCPmZ4upLwLDfJt2Q1J7WPTNetEMqgKEXUH1GwKJkFGm2tSEMHSHdTmUTQ3w6bS1C0peZghyhmlWRXUlpKk5tDOQ24sWO268YrwZyueXnVGKJ4s0hY0KOiZIU2trUECQQDxA+lzq/t/L09M/bUybjsiP6eb/HBeZeu+2+JnHekb8z9BMXTOKTHqAI0Cs9UvE6BDT3aU9IJbWHbRogIMypT9AkEArq0fccphwWtAIyS0+fns4Hqs4On7yTfXSXWiAbSVif1LxP60b5n5Xm8lo12oHkdwOvKaesvgDpnIGUM9xjFfiQJASTZrABxKNYRljnmzRTJ+/BRiEdxJNiO3zS52Q+SuHzNxD5i6ZrXU18R7EUsXg0lu8YN9/hmYT687yMpx3Pjc8QJAZBs1lSouQgIsPLfRvB1+otvLbg7KzPPivufak+2hcfanUNvEHt14a6V5RZnsOoYojK/y1oM3AkchxVCi+43aOQJAC0gI6qsZ3VaPu9QDddrHPJ1dCHTXyfcNJ0op3srCVF92HoBWX54pzeagj+9g/Z4oUT9IhaO0Q3YE07N03HuVrQ==" + respData, _ := DecryptWithPrivateKey(base64PrivateKey, respStruct.OrderRes.Body) + return respStruct.OrderId, respData, err + } } return "", "", errors.New("创建订单超时") } @@ -285,3 +301,61 @@ func (c *Client) QueryOrder(ctx context.Context, phone, token, orderId string) ( glog.Warningf(ctx, "查询订单%s超过最大页数%d,未找到", orderId, maxPages) return nil, nil } + +// RechargeCard 绑卡接口 +// 返回值说明: +// - errType == RechargeCardSuccess: 绑卡成功 +// - errType == RechargeCardErrorCode: 卡密错误 +// - errType == RechargeCardErrorToken: token 过期/无效 +// - errType == RechargeCardErrorNetwork: 网络或其他错误 +func (c *Client) RechargeCard(ctx context.Context, token, phone, eCardCode string) (errType int, err error) { + c.Client.SetHeader("Authorization", "Bearer "+c.getAuth(ctx, token)) + + req := struct { + ECardCode string `json:"eCardCode"` + OpenId string `json:"openId"` + Phone string `json:"phone"` + Channel string `json:"channel"` + }{ + ECardCode: eCardCode, + OpenId: "app2511181557205741495", + Phone: phone, + Channel: "app", + } + + resp, err := c.Client.ContentJson().Post(ctx, "https://recharge3.bac365.com/camel_wechat_mini_oil_server/eCardMall/eCardRecharge", req) + if err != nil { + glog.Errorf(ctx, "绑卡请求失败,错误: %v", err) + return RechargeCardErrorNetwork, err + } + + respStruct := struct { + Code string `json:"code"` + Message string `json:"message"` + }{} + + err = json.Unmarshal(resp.ReadAll(), &respStruct) + if err != nil { + glog.Errorf(ctx, "解析绑卡响应失败,错误: %v", err) + return RechargeCardErrorNetwork, err + } + + // 根据不同的错误码进行分类 + switch respStruct.Code { + case "success": + glog.Infof(ctx, "卡密绑卡成功,手机号: %s", phone) + return RechargeCardSuccess, nil + case "codeError": + err = errors.New(respStruct.Message) + glog.Warningf(ctx, "卡密错误: %v", err) + return RechargeCardErrorCode, err + case "auth_error", "unauthorized": + err = errors.New(respStruct.Message) + glog.Warningf(ctx, "Token 错误或已过期: %v", err) + return RechargeCardErrorToken, err + default: + err = errors.New(respStruct.Message) + glog.Errorf(ctx, "绑卡失败: %v", err) + return RechargeCardErrorNetwork, err + } +} diff --git a/utility/integration/camel_oil_api/api_test.go b/utility/integration/camel_oil_api/api_test.go index f9270b0c..70332cc2 100644 --- a/utility/integration/camel_oil_api/api_test.go +++ b/utility/integration/camel_oil_api/api_test.go @@ -5,4 +5,7 @@ import ( ) func TestClient_SendCaptcha(t *testing.T) { + for range 10 { + NewClient().CreateOrder(t.Context(), "18627350353", "X/c/tYh6aFvm7AeBeMlCkiPaTtOGrMpI1NPwL93RqUkb0JKYjK1Qe8OUIa85y621qHauOJBaEl977RmtJU8YHPAaLYhHi7BB1nhosYJOyIO6035w7uvWRc8Rg4lhA2TgUU4XnF+4jCvt8s+qWzEQLCNOd7VzpC2oYx60RqKvN60=", 100) + } } diff --git a/utility/integration/camel_oil_api/encrypt_test.go b/utility/integration/camel_oil_api/encrypt_test.go index 25f0fa76..36ee5ba5 100644 --- a/utility/integration/camel_oil_api/encrypt_test.go +++ b/utility/integration/camel_oil_api/encrypt_test.go @@ -38,8 +38,7 @@ func TestDec(t *testing.T) { prikey := "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAKRzpev8mFYTH9Y/EbardUctD4ViT+gKZDXLXgHmPjE4lj6YBagM462LewBeDQqTrraBaNYRPWcsjpgQPjBv0daifU0BmfjuWBzmiEikR1T3Ulqjzlf+SCQPc29NO+EWJy216lQ6ythRk3ysBAITsCHPAaAqcKIhFj+QTyM4IJ5lAgMBAAECgYEAg/vlMI7b3EkhBhw8JTVavLMnf8+1fe/JGXuMiU22oF5gBwCPmZ4upLwLDfJt2Q1J7WPTNetEMqgKEXUH1GwKJkFGm2tSEMHSHdTmUTQ3w6bS1C0peZghyhmlWRXUlpKk5tDOQ24sWO268YrwZyueXnVGKJ4s0hY0KOiZIU2trUECQQDxA+lzq/t/L09M/bUybjsiP6eb/HBeZeu+2+JnHekb8z9BMXTOKTHqAI0Cs9UvE6BDT3aU9IJbWHbRogIMypT9AkEArq0fccphwWtAIyS0+fns4Hqs4On7yTfXSXWiAbSVif1LxP60b5n5Xm8lo12oHkdwOvKaesvgDpnIGUM9xjFfiQJASTZrABxKNYRljnmzRTJ+/BRiEdxJNiO3zS52Q+SuHzNxD5i6ZrXU18R7EUsXg0lu8YN9/hmYT687yMpx3Pjc8QJAZBs1lSouQgIsPLfRvB1+otvLbg7KzPPivufak+2hcfanUNvEHt14a6V5RZnsOoYojK/y1oM3AkchxVCi+43aOQJAC0gI6qsZ3VaPu9QDddrHPJ1dCHTXyfcNJ0op3srCVF92HoBWX54pzeagj+9g/Z4oUT9IhaO0Q3YE07N03HuVrQ==" //encryptedBody := "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" - encryptedBody := "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" - + encryptedBody := "DiOna3ZZzIhriSHzQFMAyfRvt/aye8pkC/RBC6wKYoyrsjDQ+MjWYBkb8eml8S9zTyiuyRmsB/4Shr0O5HeFecA+XfyAIlk4tuVHnjK5eOLF6Ovfr2LlZ+yR23kB5jmJAVG7+n0Jovx9/UqMzl9pPafx47Rz3Ab+kOaL8ffjBspT5sRH7H+P4VgbQgTAPMAZNh7A+hREQneAPlhEefYs1bIyYof8E1QQk7K6HaoRCd9bwO1l3EiTsp8goOIH4LFFJISAsuAm63O1r9eIcCqWgR7DogD62mHCf4aCOwbGOrAojnq1nN1P2ITxJH78NjlFZvmMgwZrS8sOi5zuYyV+xCoyDokSe5sQxjG6QxnVT2qI38yuBJE4BYEev5bFVPEbVs0DOVMYnqULMX3yk8ggqTcx8C4lwlW5FYL0PJRiOpBQeomOzdH2Yp1ZE6s5o6EuQLiAvJoLAjf722jnYDYy6q0ZqgRdGp8HE/NsniZf+PASDD0e8JrhcvJ+lJbCpB9WksVT7fRXR8i1Pv2bQvNJtp3/M+C9XwvrMK/O/8FJZ51MKelMZi9l+YafvxboB0ryC9kmHO3P2aTZQnKrtYwbIIiuTgbdtMbVQAVvkMtBmC6i0apJTvxo1VA3j46agu6Mhi5jpo+mQXWqMbbMGZ+Sof2FmwZyBFjfLRntueyoAtU=" // Use DecryptWithPrivateKey function to decrypt decrypted, err := DecryptWithPrivateKey(prikey, encryptedBody) if err != nil { @@ -47,10 +46,4 @@ func TestDec(t *testing.T) { } t.Logf("Decrypted: %s", decrypted) t.Log("Test passed! Decryption matches Java result.") - - // const orderStr = 'alipay_sdk=xxx...'; - //const encodedPayUrl = encodeURIComponent(orderStr); - //alipay: //platformapi/startapp?appId=20000067&url=alipay-sdk-java-4.40.33.ALL%26app_id%3d2021005196642063%26biz_content%3d%257B%2522business_params%2522%253A%2522%257B%255C%2522outTradeRiskInfo%255C%2522%253A%255C%2522%257B%255C%255C%255C%2522sysVersion%255C%255C%255C%2522%253A%255C%255C%255C%2522iOS%2b26.2%255C%255C%255C%2522%252C%255C%255C%255C%2522mcCreateTradePackage%255C%255C%255C%2522%253A%255C%255C%255C%2522com.ltjy.uni1005507%255C%255C%255C%2522%252C%255C%255C%255C%2522mcCreateTradeTime%255C%255C%255C%2522%253A%255C%255C%255C%25222025-11-22%2b01%253A39%253A56%255C%255C%255C%2522%252C%255C%255C%255C%2522mcCreateTradeLbs%255C%255C%255C%2522%253A%255C%255C%255C%2522118.9885703919527%252C36.36133429662592%255C%255C%255C%2522%252C%255C%255C%255C%2522mobileOperatingPlatform%255C%255C%255C%2522%253A%255C%255C%255C%2522ios%255C%255C%255C%2522%252C%255C%255C%255C%2522platformType%255C%255C%255C%2522%253A%255C%255C%255C%2522iPad%2bPro%2b%252812.9-inch%2529%2b%25283rd%2bgeneration%2529%255C%255C%255C%2522%252C%255C%255C%255C%2522mcCreateTradeChannel%255C%255C%255C%2522%253A%255C%255C%255C%2522app%255C%255C%255C%2522%252C%255C%255C%255C%2522extraAccountRegTime%255C%255C%255C%2522%253A%255C%255C%255C%25222025-11-18%2b15%253A57%253A21%255C%255C%255C%2522%252C%255C%255C%255C%2522extraAccountPhone%255C%255C%255C%2522%253A%255C%255C%255C%252217862666120%255C%255C%255C%2522%252C%255C%255C%255C%2522netWork%255C%255C%255C%2522%253A%255C%255C%255C%2522unknown%255C%255C%255C%2522%257D%255C%2522%252C%255C%2522mc_create_trade_ip%255C%2522%253A%255C%2522123.168.253.227%255C%2522%257D%2522%252C%2522out_trade_no%2522%253A%25222511220139557299472%2522%252C%2522subject%2522%253A%2522%25E9%25AA%2586%25E9%25A9%25BC%25E6%258A%25B5%25E6%2589%25A3%25E5%2588%25B8-50%25E5%2585%2583%25E9%259D%25A2%25E5%2580%25BC%2522%252C%2522timeout_express%2522%253A%252210m%2522%252C%2522total_amount%2522%253A%252250.00%2522%257D%26charset%3dUTF-8%26format%3djson%26method%3dalipay.trade.app.pay%26notify_url%3dhttps%253A%252F%252Frecharge3.bac365.com%252Fpayment%252Falipay%252Fnotify%26sign%3dDQ0Kf5KCZ1jUHptXuAriStVEHDbKdeNWofxZr%252FAL%252BAA%252FYytu0kXNTDkviGLKMG9je2wS419dKGqX8inT9y7G%252FtcactTKoLavi3gy6RLys0Yof97B1umkopzqIhQ2nA9lxDsaLEfqXfdv33zaRetQFgPGzIv5KrOPRY18RWvwp5PqKodSp9AzbJ5bAGELTzbJ8Oj4YOeNSFelsOUaD0J4Ecw%252B7qmDIDb8UDIBYXFaNUquPtWE%252FjAZ%252FGM9PWtMYx7%252Fiq2mmbJJTe5ErN7L%252BtsXGV51axZ2f%252F0EZKISMKkgI0I5ECnHZjGa2pw%252FByMeMREYF05ZuF%252Fowcn53Snag3j0aw%253D%253D%26sign_type%3dRSA2%26timestamp%3d2025-11-22%2b01%253A39%253A56%26version%3d1.0 - //window.location.href = alipayScheme; - }