refactor(camel_oil): 优化账号登录逻辑及预拉取订单接口
- 删除旧的容量检查并触发账号登录代码,统一登录流程 - LoginAccount方法支持手机号去重,避免重复创建账号 - BatchLoginAccounts改用并发登录提高效率,支持int64数量参数 - camel_oil_api集成更新,调整接口调用地址和请求体,新增QueryOrder接口实现分页查询 - pig集成重试获取账号,增强鲁棒性 - 更新consts增加预拉取订单相关状态和类型常量及文本映射 - 服务接口新增预拉取订单相关方法和补充任务调度接口 - 调整部分测试代码,注释无效测试 - 代码格式和日志输出格式优化,增强可读性和维护性
This commit is contained in:
3
go.mod
3
go.mod
@@ -1,6 +1,6 @@
|
||||
module kami
|
||||
|
||||
go 1.24.0
|
||||
go 1.25
|
||||
|
||||
require (
|
||||
github.com/Lofanmi/pinyin-golang v0.0.0-20250305082105-87d20ae3d695
|
||||
@@ -33,7 +33,6 @@ require (
|
||||
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546
|
||||
golang.org/x/image v0.32.0
|
||||
golang.org/x/net v0.46.0
|
||||
golang.org/x/time v0.14.0
|
||||
)
|
||||
|
||||
require (
|
||||
|
||||
2
go.sum
2
go.sum
@@ -274,8 +274,6 @@ golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
|
||||
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
|
||||
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
|
||||
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
|
||||
@@ -125,6 +125,7 @@ const (
|
||||
CamelOilOrderChangeTypeFail CamelOilOrderChangeType = "fail" // 下单失败
|
||||
CamelOilOrderChangeTypeCallbackSuccess CamelOilOrderChangeType = "callback_success" // 回调商户成功
|
||||
CamelOilOrderChangeTypeCallbackFail CamelOilOrderChangeType = "callback_fail" // 回调商户失败
|
||||
CamelOilOrderChangeTypeFillCard CamelOilOrderChangeType = "fill_card" // 填写卡密和卡号
|
||||
)
|
||||
|
||||
// CamelOilOrderChangeTypeText 订单变更类型文本映射
|
||||
@@ -138,8 +139,75 @@ var CamelOilOrderChangeTypeText = map[CamelOilOrderChangeType]string{
|
||||
CamelOilOrderChangeTypeFail: "下单失败",
|
||||
CamelOilOrderChangeTypeCallbackSuccess: "回调成功",
|
||||
CamelOilOrderChangeTypeCallbackFail: "回调失败",
|
||||
CamelOilOrderChangeTypeFillCard: "填写卡密",
|
||||
}
|
||||
|
||||
// ====================================================================================
|
||||
// 预拉取订单相关常量定义
|
||||
// ====================================================================================
|
||||
|
||||
// CamelOilPrefetchOrderStatus 预拉取订单状态枚举
|
||||
type CamelOilPrefetchOrderStatus int
|
||||
|
||||
const (
|
||||
CamelOilPrefetchOrderStatusPending CamelOilPrefetchOrderStatus = 1 // 待匹配
|
||||
CamelOilPrefetchOrderStatusMatched CamelOilPrefetchOrderStatus = 2 // 已匹配
|
||||
CamelOilPrefetchOrderStatusExpired CamelOilPrefetchOrderStatus = 3 // 已过期
|
||||
CamelOilPrefetchOrderStatusInvalid CamelOilPrefetchOrderStatus = 4 // 已失效
|
||||
)
|
||||
|
||||
// CamelOilPrefetchOrderStatusText 预拉取订单状态文本映射
|
||||
var CamelOilPrefetchOrderStatusText = map[CamelOilPrefetchOrderStatus]string{
|
||||
CamelOilPrefetchOrderStatusPending: "待匹配",
|
||||
CamelOilPrefetchOrderStatusMatched: "已匹配",
|
||||
CamelOilPrefetchOrderStatusExpired: "已过期",
|
||||
CamelOilPrefetchOrderStatusInvalid: "已失效",
|
||||
}
|
||||
|
||||
// CamelOilPrefetchOrderChangeType 预拉取订单变更类型
|
||||
type CamelOilPrefetchOrderChangeType string
|
||||
|
||||
const (
|
||||
CamelOilPrefetchOrderChangeTypeCreate CamelOilPrefetchOrderChangeType = "create" // 创建预拉取订单
|
||||
CamelOilPrefetchOrderChangeTypeFetch CamelOilPrefetchOrderChangeType = "fetch" // 从骆驼平台拉取
|
||||
CamelOilPrefetchOrderChangeTypeMatch CamelOilPrefetchOrderChangeType = "match" // 与用户订单匹配
|
||||
CamelOilPrefetchOrderChangeTypeExpire CamelOilPrefetchOrderChangeType = "expire" // 过期失效
|
||||
CamelOilPrefetchOrderChangeTypeInvalidate CamelOilPrefetchOrderChangeType = "invalidate" // 标记为失效
|
||||
)
|
||||
|
||||
// CamelOilPrefetchOrderChangeTypeText 预拉取订单变更类型文本映射
|
||||
var CamelOilPrefetchOrderChangeTypeText = map[CamelOilPrefetchOrderChangeType]string{
|
||||
CamelOilPrefetchOrderChangeTypeCreate: "创建预拉取订单",
|
||||
CamelOilPrefetchOrderChangeTypeFetch: "从平台拉取",
|
||||
CamelOilPrefetchOrderChangeTypeMatch: "与订单匹配",
|
||||
CamelOilPrefetchOrderChangeTypeExpire: "订单过期",
|
||||
CamelOilPrefetchOrderChangeTypeInvalidate: "标记失效",
|
||||
}
|
||||
|
||||
// ====================================================================================
|
||||
// 预拉取订单业务配置常量
|
||||
// ====================================================================================
|
||||
|
||||
const (
|
||||
// CamelOilPrefetchOrderMinCapacity 预拉取订单最小库存阈值(当库存低于此值时触发补充)
|
||||
CamelOilPrefetchOrderMinCapacity = 1
|
||||
|
||||
// CamelOilPrefetchOrderTargetCapacity 预拉取订单目标库存(补充时的目标数量)
|
||||
CamelOilPrefetchOrderTargetCapacity = 20
|
||||
|
||||
// CamelOilPrefetchOrderExpireDuration 预拉取订单过期时间(小时)
|
||||
CamelOilPrefetchOrderExpireDuration = 24
|
||||
|
||||
// CamelOilPrefetchMaxConcurrency 预拉取最大并发账号数量
|
||||
CamelOilPrefetchMaxConcurrency = 5
|
||||
|
||||
// CamelOilPrefetchOrderLockKey Redis中预拉取订单的分布式锁键名前缀
|
||||
CamelOilPrefetchOrderLockKey = "camel_oil_api:prefetch:order:lock:"
|
||||
|
||||
// CamelOilPrefetchTaskLockKey Redis中预拉取任务的分布式锁键名
|
||||
CamelOilPrefetchTaskLockKey = "camel_oil_api:task:prefetch:lock"
|
||||
)
|
||||
|
||||
// ====================================================================================
|
||||
// 业务配置常量
|
||||
// ====================================================================================
|
||||
@@ -152,7 +220,7 @@ const (
|
||||
CamelOilMinAvailableCapacity = 50
|
||||
|
||||
// CamelOilTargetOnlineAccounts 目标在线账号数量
|
||||
CamelOilTargetOnlineAccounts = 5
|
||||
CamelOilTargetOnlineAccounts = 1
|
||||
|
||||
// CamelOilOrderExpireDuration 订单支付超时时间(小时)
|
||||
CamelOilOrderExpireDuration = 24
|
||||
|
||||
@@ -22,7 +22,6 @@ type V1CamelOilAccountHistoryDao struct {
|
||||
// V1CamelOilAccountHistoryColumns defines and stores column names for the table camel_oil_account_history.
|
||||
type V1CamelOilAccountHistoryColumns struct {
|
||||
Id string // 主键ID
|
||||
HistoryUuid string // 历史记录唯一标识
|
||||
AccountId string // 账号ID
|
||||
ChangeType string // 变更类型:create/login/offline/login_fail/pause/resume/invalidate/order_bind/order_complete/update/delete
|
||||
StatusBefore string // 变更前状态
|
||||
@@ -37,7 +36,6 @@ type V1CamelOilAccountHistoryColumns struct {
|
||||
// v1CamelOilAccountHistoryColumns holds the columns for the table camel_oil_account_history.
|
||||
var v1CamelOilAccountHistoryColumns = V1CamelOilAccountHistoryColumns{
|
||||
Id: "id",
|
||||
HistoryUuid: "history_uuid",
|
||||
AccountId: "account_id",
|
||||
ChangeType: "change_type",
|
||||
StatusBefore: "status_before",
|
||||
|
||||
@@ -29,6 +29,8 @@ type V1CamelOilOrderColumns struct {
|
||||
PlatformOrderNo string // 骆驼平台订单号
|
||||
Amount string // 订单金额
|
||||
AlipayUrl string // 支付宝支付链接
|
||||
CardPassword string // 卡密
|
||||
CardNumber string // 卡号
|
||||
Status string // 状态:1待支付 2已支付 3支付超时 4下单失败
|
||||
PayStatus string // 支付状态:0未支付 1已支付 2超时
|
||||
NotifyStatus string // 回调状态:0未回调 1已回调 2回调失败
|
||||
@@ -52,6 +54,8 @@ var v1CamelOilOrderColumns = V1CamelOilOrderColumns{
|
||||
PlatformOrderNo: "platform_order_no",
|
||||
Amount: "amount",
|
||||
AlipayUrl: "alipay_url",
|
||||
CardPassword: "card_password",
|
||||
CardNumber: "card_number",
|
||||
Status: "status",
|
||||
PayStatus: "pay_status",
|
||||
NotifyStatus: "notify_status",
|
||||
|
||||
@@ -22,7 +22,6 @@ type V1CamelOilOrderHistoryDao struct {
|
||||
// V1CamelOilOrderHistoryColumns defines and stores column names for the table camel_oil_order_history.
|
||||
type V1CamelOilOrderHistoryColumns struct {
|
||||
Id string // 主键ID
|
||||
HistoryUuid string // 历史记录唯一标识
|
||||
OrderNo string // 订单号
|
||||
ChangeType string // 变更类型:create/submit/get_pay_url/check_pay/paid/timeout/fail/callback_success/callback_fail
|
||||
AccountId string // 关联账号ID
|
||||
@@ -37,7 +36,6 @@ type V1CamelOilOrderHistoryColumns struct {
|
||||
// v1CamelOilOrderHistoryColumns holds the columns for the table camel_oil_order_history.
|
||||
var v1CamelOilOrderHistoryColumns = V1CamelOilOrderHistoryColumns{
|
||||
Id: "id",
|
||||
HistoryUuid: "history_uuid",
|
||||
OrderNo: "order_no",
|
||||
ChangeType: "change_type",
|
||||
AccountId: "account_id",
|
||||
|
||||
107
internal/dao/internal/v_1_camel_oil_prefetch_order.go
Normal file
107
internal/dao/internal/v_1_camel_oil_prefetch_order.go
Normal file
@@ -0,0 +1,107 @@
|
||||
// ==========================================================================
|
||||
// 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"
|
||||
)
|
||||
|
||||
// V1CamelOilPrefetchOrderDao is the data access object for the table camel_oil_prefetch_order.
|
||||
type V1CamelOilPrefetchOrderDao 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 V1CamelOilPrefetchOrderColumns // columns contains all the column names of Table for convenient usage.
|
||||
handlers []gdb.ModelHandler // handlers for customized model modification.
|
||||
}
|
||||
|
||||
// V1CamelOilPrefetchOrderColumns defines and stores column names for the table camel_oil_prefetch_order.
|
||||
type V1CamelOilPrefetchOrderColumns struct {
|
||||
Id string // 主键ID
|
||||
AccountId string // 拉取时使用的账号ID
|
||||
AccountName string // 账号名称
|
||||
Amount string // 预拉取订单金额
|
||||
PlatformOrderNo string // 骆驼平台订单号
|
||||
AlipayUrl string // 支付宝支付链接
|
||||
Status string // 预拉取订单状态:1待匹配 2已匹配 3已过期 4已失效
|
||||
OrderId string // 匹配后的订单ID(关联camel_oil_order表)
|
||||
MatchedAt string // 匹配时间
|
||||
ExpireAt string // 预拉取订单过期时间(通常为24小时后)
|
||||
FailureReason string // 失败原因
|
||||
Remark string // 备注信息
|
||||
CreatedAt string // 创建时间
|
||||
UpdatedAt string // 更新时间
|
||||
DeletedAt string // 删除时间(软删除)
|
||||
}
|
||||
|
||||
// v1CamelOilPrefetchOrderColumns holds the columns for the table camel_oil_prefetch_order.
|
||||
var v1CamelOilPrefetchOrderColumns = V1CamelOilPrefetchOrderColumns{
|
||||
Id: "id",
|
||||
AccountId: "account_id",
|
||||
AccountName: "account_name",
|
||||
Amount: "amount",
|
||||
PlatformOrderNo: "platform_order_no",
|
||||
AlipayUrl: "alipay_url",
|
||||
Status: "status",
|
||||
OrderId: "order_id",
|
||||
MatchedAt: "matched_at",
|
||||
ExpireAt: "expire_at",
|
||||
FailureReason: "failure_reason",
|
||||
Remark: "remark",
|
||||
CreatedAt: "created_at",
|
||||
UpdatedAt: "updated_at",
|
||||
DeletedAt: "deleted_at",
|
||||
}
|
||||
|
||||
// NewV1CamelOilPrefetchOrderDao creates and returns a new DAO object for table data access.
|
||||
func NewV1CamelOilPrefetchOrderDao(handlers ...gdb.ModelHandler) *V1CamelOilPrefetchOrderDao {
|
||||
return &V1CamelOilPrefetchOrderDao{
|
||||
group: "default",
|
||||
table: "camel_oil_prefetch_order",
|
||||
columns: v1CamelOilPrefetchOrderColumns,
|
||||
handlers: handlers,
|
||||
}
|
||||
}
|
||||
|
||||
// DB retrieves and returns the underlying raw database management object of the current DAO.
|
||||
func (dao *V1CamelOilPrefetchOrderDao) DB() gdb.DB {
|
||||
return g.DB(dao.group)
|
||||
}
|
||||
|
||||
// Table returns the table name of the current DAO.
|
||||
func (dao *V1CamelOilPrefetchOrderDao) Table() string {
|
||||
return dao.table
|
||||
}
|
||||
|
||||
// Columns returns all column names of the current DAO.
|
||||
func (dao *V1CamelOilPrefetchOrderDao) Columns() V1CamelOilPrefetchOrderColumns {
|
||||
return dao.columns
|
||||
}
|
||||
|
||||
// Group returns the database configuration group name of the current DAO.
|
||||
func (dao *V1CamelOilPrefetchOrderDao) 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 *V1CamelOilPrefetchOrderDao) 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 *V1CamelOilPrefetchOrderDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) {
|
||||
return dao.Ctx(ctx).Transaction(ctx, f)
|
||||
}
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
// V1CamelOilPrefetchOrderHistoryDao is the data access object for the table camel_oil_prefetch_order_history.
|
||||
type V1CamelOilPrefetchOrderHistoryDao 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 V1CamelOilPrefetchOrderHistoryColumns // columns contains all the column names of Table for convenient usage.
|
||||
handlers []gdb.ModelHandler // handlers for customized model modification.
|
||||
}
|
||||
|
||||
// V1CamelOilPrefetchOrderHistoryColumns defines and stores column names for the table camel_oil_prefetch_order_history.
|
||||
type V1CamelOilPrefetchOrderHistoryColumns struct {
|
||||
Id string // 主键ID
|
||||
PrefetchId string // 预拉取订单ID
|
||||
ChangeType string // 变更类型:create/fetch/match/expire/invalidate
|
||||
AccountId string // 关联账号ID
|
||||
AccountName string // 账号名称
|
||||
RawData string // 原始响应数据
|
||||
Remark string // 备注
|
||||
CreatedAt string // 创建时间
|
||||
UpdatedAt string // 更新时间
|
||||
DeletedAt string // 删除时间(软删除)
|
||||
}
|
||||
|
||||
// v1CamelOilPrefetchOrderHistoryColumns holds the columns for the table camel_oil_prefetch_order_history.
|
||||
var v1CamelOilPrefetchOrderHistoryColumns = V1CamelOilPrefetchOrderHistoryColumns{
|
||||
Id: "id",
|
||||
PrefetchId: "prefetch_id",
|
||||
ChangeType: "change_type",
|
||||
AccountId: "account_id",
|
||||
AccountName: "account_name",
|
||||
RawData: "raw_data",
|
||||
Remark: "remark",
|
||||
CreatedAt: "created_at",
|
||||
UpdatedAt: "updated_at",
|
||||
DeletedAt: "deleted_at",
|
||||
}
|
||||
|
||||
// NewV1CamelOilPrefetchOrderHistoryDao creates and returns a new DAO object for table data access.
|
||||
func NewV1CamelOilPrefetchOrderHistoryDao(handlers ...gdb.ModelHandler) *V1CamelOilPrefetchOrderHistoryDao {
|
||||
return &V1CamelOilPrefetchOrderHistoryDao{
|
||||
group: "default",
|
||||
table: "camel_oil_prefetch_order_history",
|
||||
columns: v1CamelOilPrefetchOrderHistoryColumns,
|
||||
handlers: handlers,
|
||||
}
|
||||
}
|
||||
|
||||
// DB retrieves and returns the underlying raw database management object of the current DAO.
|
||||
func (dao *V1CamelOilPrefetchOrderHistoryDao) DB() gdb.DB {
|
||||
return g.DB(dao.group)
|
||||
}
|
||||
|
||||
// Table returns the table name of the current DAO.
|
||||
func (dao *V1CamelOilPrefetchOrderHistoryDao) Table() string {
|
||||
return dao.table
|
||||
}
|
||||
|
||||
// Columns returns all column names of the current DAO.
|
||||
func (dao *V1CamelOilPrefetchOrderHistoryDao) Columns() V1CamelOilPrefetchOrderHistoryColumns {
|
||||
return dao.columns
|
||||
}
|
||||
|
||||
// Group returns the database configuration group name of the current DAO.
|
||||
func (dao *V1CamelOilPrefetchOrderHistoryDao) 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 *V1CamelOilPrefetchOrderHistoryDao) 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 *V1CamelOilPrefetchOrderHistoryDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) {
|
||||
return dao.Ctx(ctx).Transaction(ctx, f)
|
||||
}
|
||||
22
internal/dao/v_1_camel_oil_prefetch_order.go
Normal file
22
internal/dao/v_1_camel_oil_prefetch_order.go
Normal file
@@ -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"
|
||||
)
|
||||
|
||||
// v1CamelOilPrefetchOrderDao is the data access object for the table camel_oil_prefetch_order.
|
||||
// You can define custom methods on it to extend its functionality as needed.
|
||||
type v1CamelOilPrefetchOrderDao struct {
|
||||
*internal.V1CamelOilPrefetchOrderDao
|
||||
}
|
||||
|
||||
var (
|
||||
// V1CamelOilPrefetchOrder is a globally accessible object for table camel_oil_prefetch_order operations.
|
||||
V1CamelOilPrefetchOrder = v1CamelOilPrefetchOrderDao{internal.NewV1CamelOilPrefetchOrderDao()}
|
||||
)
|
||||
|
||||
// Add your custom methods and functionality below.
|
||||
22
internal/dao/v_1_camel_oil_prefetch_order_history.go
Normal file
22
internal/dao/v_1_camel_oil_prefetch_order_history.go
Normal file
@@ -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"
|
||||
)
|
||||
|
||||
// v1CamelOilPrefetchOrderHistoryDao is the data access object for the table camel_oil_prefetch_order_history.
|
||||
// You can define custom methods on it to extend its functionality as needed.
|
||||
type v1CamelOilPrefetchOrderHistoryDao struct {
|
||||
*internal.V1CamelOilPrefetchOrderHistoryDao
|
||||
}
|
||||
|
||||
var (
|
||||
// V1CamelOilPrefetchOrderHistory is a globally accessible object for table camel_oil_prefetch_order_history operations.
|
||||
V1CamelOilPrefetchOrderHistory = v1CamelOilPrefetchOrderHistoryDao{internal.NewV1CamelOilPrefetchOrderHistoryDao()}
|
||||
)
|
||||
|
||||
// Add your custom methods and functionality below.
|
||||
109
internal/internal/model/internal/v_1_camel_oil_account.go
Normal file
109
internal/internal/model/internal/v_1_camel_oil_account.go
Normal file
@@ -0,0 +1,109 @@
|
||||
// ==========================================================================
|
||||
// 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"
|
||||
)
|
||||
|
||||
// V1CamelOilAccountDao is the data access object for the table camel_oil_account.
|
||||
type V1CamelOilAccountDao 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 V1CamelOilAccountColumns // columns contains all the column names of Table for convenient usage.
|
||||
handlers []gdb.ModelHandler // handlers for customized model modification.
|
||||
}
|
||||
|
||||
// V1CamelOilAccountColumns defines and stores column names for the table camel_oil_account.
|
||||
type V1CamelOilAccountColumns struct {
|
||||
Id string // 主键ID
|
||||
AccountName string // 账号名称(备注)
|
||||
Phone string // 手机号(登录后记录,不可重复)
|
||||
Token string // 登录Token
|
||||
Status string // 状态:1待登录 2在线 3暂停 4已失效 5登录失败
|
||||
TokenExpireAt string // Token过期时间
|
||||
LastLoginAt string // 最后登录时间
|
||||
LastUsedAt string // 最后使用时间
|
||||
DailyOrderCount string // 当日已下单数量
|
||||
DailyOrderDate string // 当日订单日期
|
||||
TotalOrderCount string // 累计下单数量
|
||||
FailureReason string // 失败原因
|
||||
Remark string // 备注信息
|
||||
CreatedAt string // 创建时间
|
||||
UpdatedAt string // 更新时间
|
||||
DeletedAt string // 删除时间(软删除)
|
||||
}
|
||||
|
||||
// v1CamelOilAccountColumns holds the columns for the table camel_oil_account.
|
||||
var v1CamelOilAccountColumns = V1CamelOilAccountColumns{
|
||||
Id: "id",
|
||||
AccountName: "account_name",
|
||||
Phone: "phone",
|
||||
Token: "token",
|
||||
Status: "status",
|
||||
TokenExpireAt: "token_expire_at",
|
||||
LastLoginAt: "last_login_at",
|
||||
LastUsedAt: "last_used_at",
|
||||
DailyOrderCount: "daily_order_count",
|
||||
DailyOrderDate: "daily_order_date",
|
||||
TotalOrderCount: "total_order_count",
|
||||
FailureReason: "failure_reason",
|
||||
Remark: "remark",
|
||||
CreatedAt: "created_at",
|
||||
UpdatedAt: "updated_at",
|
||||
DeletedAt: "deleted_at",
|
||||
}
|
||||
|
||||
// NewV1CamelOilAccountDao creates and returns a new DAO object for table data access.
|
||||
func NewV1CamelOilAccountDao(handlers ...gdb.ModelHandler) *V1CamelOilAccountDao {
|
||||
return &V1CamelOilAccountDao{
|
||||
group: "default",
|
||||
table: "camel_oil_account",
|
||||
columns: v1CamelOilAccountColumns,
|
||||
handlers: handlers,
|
||||
}
|
||||
}
|
||||
|
||||
// DB retrieves and returns the underlying raw database management object of the current DAO.
|
||||
func (dao *V1CamelOilAccountDao) DB() gdb.DB {
|
||||
return g.DB(dao.group)
|
||||
}
|
||||
|
||||
// Table returns the table name of the current DAO.
|
||||
func (dao *V1CamelOilAccountDao) Table() string {
|
||||
return dao.table
|
||||
}
|
||||
|
||||
// Columns returns all column names of the current DAO.
|
||||
func (dao *V1CamelOilAccountDao) Columns() V1CamelOilAccountColumns {
|
||||
return dao.columns
|
||||
}
|
||||
|
||||
// Group returns the database configuration group name of the current DAO.
|
||||
func (dao *V1CamelOilAccountDao) 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 *V1CamelOilAccountDao) 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 *V1CamelOilAccountDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) {
|
||||
return dao.Ctx(ctx).Transaction(ctx, f)
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
// ==========================================================================
|
||||
// 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"
|
||||
)
|
||||
|
||||
// V1CamelOilAccountHistoryDao is the data access object for the table camel_oil_account_history.
|
||||
type V1CamelOilAccountHistoryDao 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 V1CamelOilAccountHistoryColumns // columns contains all the column names of Table for convenient usage.
|
||||
handlers []gdb.ModelHandler // handlers for customized model modification.
|
||||
}
|
||||
|
||||
// V1CamelOilAccountHistoryColumns defines and stores column names for the table camel_oil_account_history.
|
||||
type V1CamelOilAccountHistoryColumns struct {
|
||||
Id string // 主键ID
|
||||
HistoryUuid string // 历史记录唯一标识
|
||||
AccountId string // 账号ID
|
||||
ChangeType string // 变更类型:create/login/offline/login_fail/pause/resume/invalidate/order_bind/order_complete/update/delete
|
||||
StatusBefore string // 变更前状态
|
||||
StatusAfter string // 变更后状态
|
||||
FailureCount string // 失败次数
|
||||
Remark string // 备注
|
||||
CreatedAt string // 创建时间
|
||||
UpdatedAt string // 更新时间
|
||||
DeletedAt string // 删除时间(软删除)
|
||||
}
|
||||
|
||||
// v1CamelOilAccountHistoryColumns holds the columns for the table camel_oil_account_history.
|
||||
var v1CamelOilAccountHistoryColumns = V1CamelOilAccountHistoryColumns{
|
||||
Id: "id",
|
||||
HistoryUuid: "history_uuid",
|
||||
AccountId: "account_id",
|
||||
ChangeType: "change_type",
|
||||
StatusBefore: "status_before",
|
||||
StatusAfter: "status_after",
|
||||
FailureCount: "failure_count",
|
||||
Remark: "remark",
|
||||
CreatedAt: "created_at",
|
||||
UpdatedAt: "updated_at",
|
||||
DeletedAt: "deleted_at",
|
||||
}
|
||||
|
||||
// NewV1CamelOilAccountHistoryDao creates and returns a new DAO object for table data access.
|
||||
func NewV1CamelOilAccountHistoryDao(handlers ...gdb.ModelHandler) *V1CamelOilAccountHistoryDao {
|
||||
return &V1CamelOilAccountHistoryDao{
|
||||
group: "default",
|
||||
table: "camel_oil_account_history",
|
||||
columns: v1CamelOilAccountHistoryColumns,
|
||||
handlers: handlers,
|
||||
}
|
||||
}
|
||||
|
||||
// DB retrieves and returns the underlying raw database management object of the current DAO.
|
||||
func (dao *V1CamelOilAccountHistoryDao) DB() gdb.DB {
|
||||
return g.DB(dao.group)
|
||||
}
|
||||
|
||||
// Table returns the table name of the current DAO.
|
||||
func (dao *V1CamelOilAccountHistoryDao) Table() string {
|
||||
return dao.table
|
||||
}
|
||||
|
||||
// Columns returns all column names of the current DAO.
|
||||
func (dao *V1CamelOilAccountHistoryDao) Columns() V1CamelOilAccountHistoryColumns {
|
||||
return dao.columns
|
||||
}
|
||||
|
||||
// Group returns the database configuration group name of the current DAO.
|
||||
func (dao *V1CamelOilAccountHistoryDao) 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 *V1CamelOilAccountHistoryDao) 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 *V1CamelOilAccountHistoryDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) {
|
||||
return dao.Ctx(ctx).Transaction(ctx, f)
|
||||
}
|
||||
119
internal/internal/model/internal/v_1_camel_oil_order.go
Normal file
119
internal/internal/model/internal/v_1_camel_oil_order.go
Normal file
@@ -0,0 +1,119 @@
|
||||
// ==========================================================================
|
||||
// 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"
|
||||
)
|
||||
|
||||
// V1CamelOilOrderDao is the data access object for the table camel_oil_order.
|
||||
type V1CamelOilOrderDao 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 V1CamelOilOrderColumns // columns contains all the column names of Table for convenient usage.
|
||||
handlers []gdb.ModelHandler // handlers for customized model modification.
|
||||
}
|
||||
|
||||
// V1CamelOilOrderColumns defines and stores column names for the table camel_oil_order.
|
||||
type V1CamelOilOrderColumns struct {
|
||||
Id string // 主键ID
|
||||
OrderNo string // 系统订单号
|
||||
MerchantOrderId string // 商户订单号
|
||||
AccountId string // 使用的账号ID
|
||||
AccountName string // 账号名称
|
||||
PlatformOrderNo string // 骆驼平台订单号
|
||||
Amount string // 订单金额
|
||||
AlipayUrl string // 支付宝支付链接
|
||||
CardPassword string // 卡密
|
||||
CardNumber string // 卡号
|
||||
Status string // 状态:1待支付 2已支付 3支付超时 4下单失败
|
||||
PayStatus string // 支付状态:0未支付 1已支付 2超时
|
||||
NotifyStatus string // 回调状态:0未回调 1已回调 2回调失败
|
||||
NotifyCount string // 回调次数
|
||||
LastCheckAt string // 最后检测支付时间
|
||||
PaidAt string // 支付完成时间
|
||||
Attach string // 附加信息
|
||||
FailureReason string // 失败原因
|
||||
CreatedAt string // 创建时间
|
||||
UpdatedAt string // 更新时间
|
||||
DeletedAt string // 删除时间(软删除)
|
||||
}
|
||||
|
||||
// v1CamelOilOrderColumns holds the columns for the table camel_oil_order.
|
||||
var v1CamelOilOrderColumns = V1CamelOilOrderColumns{
|
||||
Id: "id",
|
||||
OrderNo: "order_no",
|
||||
MerchantOrderId: "merchant_order_id",
|
||||
AccountId: "account_id",
|
||||
AccountName: "account_name",
|
||||
PlatformOrderNo: "platform_order_no",
|
||||
Amount: "amount",
|
||||
AlipayUrl: "alipay_url",
|
||||
CardPassword: "card_password",
|
||||
CardNumber: "card_number",
|
||||
Status: "status",
|
||||
PayStatus: "pay_status",
|
||||
NotifyStatus: "notify_status",
|
||||
NotifyCount: "notify_count",
|
||||
LastCheckAt: "last_check_at",
|
||||
PaidAt: "paid_at",
|
||||
Attach: "attach",
|
||||
FailureReason: "failure_reason",
|
||||
CreatedAt: "created_at",
|
||||
UpdatedAt: "updated_at",
|
||||
DeletedAt: "deleted_at",
|
||||
}
|
||||
|
||||
// NewV1CamelOilOrderDao creates and returns a new DAO object for table data access.
|
||||
func NewV1CamelOilOrderDao(handlers ...gdb.ModelHandler) *V1CamelOilOrderDao {
|
||||
return &V1CamelOilOrderDao{
|
||||
group: "default",
|
||||
table: "camel_oil_order",
|
||||
columns: v1CamelOilOrderColumns,
|
||||
handlers: handlers,
|
||||
}
|
||||
}
|
||||
|
||||
// DB retrieves and returns the underlying raw database management object of the current DAO.
|
||||
func (dao *V1CamelOilOrderDao) DB() gdb.DB {
|
||||
return g.DB(dao.group)
|
||||
}
|
||||
|
||||
// Table returns the table name of the current DAO.
|
||||
func (dao *V1CamelOilOrderDao) Table() string {
|
||||
return dao.table
|
||||
}
|
||||
|
||||
// Columns returns all column names of the current DAO.
|
||||
func (dao *V1CamelOilOrderDao) Columns() V1CamelOilOrderColumns {
|
||||
return dao.columns
|
||||
}
|
||||
|
||||
// Group returns the database configuration group name of the current DAO.
|
||||
func (dao *V1CamelOilOrderDao) 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 *V1CamelOilOrderDao) 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 *V1CamelOilOrderDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) {
|
||||
return dao.Ctx(ctx).Transaction(ctx, f)
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
// ==========================================================================
|
||||
// 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"
|
||||
)
|
||||
|
||||
// V1CamelOilOrderHistoryDao is the data access object for the table camel_oil_order_history.
|
||||
type V1CamelOilOrderHistoryDao 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 V1CamelOilOrderHistoryColumns // columns contains all the column names of Table for convenient usage.
|
||||
handlers []gdb.ModelHandler // handlers for customized model modification.
|
||||
}
|
||||
|
||||
// V1CamelOilOrderHistoryColumns defines and stores column names for the table camel_oil_order_history.
|
||||
type V1CamelOilOrderHistoryColumns struct {
|
||||
Id string // 主键ID
|
||||
HistoryUuid string // 历史记录唯一标识
|
||||
OrderNo string // 订单号
|
||||
ChangeType string // 变更类型:create/submit/get_pay_url/check_pay/paid/timeout/fail/callback_success/callback_fail
|
||||
AccountId string // 关联账号ID
|
||||
AccountName string // 账号名称
|
||||
RawData string // 原始响应数据
|
||||
Remark string // 备注
|
||||
CreatedAt string // 创建时间
|
||||
UpdatedAt string // 更新时间
|
||||
DeletedAt string // 删除时间(软删除)
|
||||
}
|
||||
|
||||
// v1CamelOilOrderHistoryColumns holds the columns for the table camel_oil_order_history.
|
||||
var v1CamelOilOrderHistoryColumns = V1CamelOilOrderHistoryColumns{
|
||||
Id: "id",
|
||||
HistoryUuid: "history_uuid",
|
||||
OrderNo: "order_no",
|
||||
ChangeType: "change_type",
|
||||
AccountId: "account_id",
|
||||
AccountName: "account_name",
|
||||
RawData: "raw_data",
|
||||
Remark: "remark",
|
||||
CreatedAt: "created_at",
|
||||
UpdatedAt: "updated_at",
|
||||
DeletedAt: "deleted_at",
|
||||
}
|
||||
|
||||
// NewV1CamelOilOrderHistoryDao creates and returns a new DAO object for table data access.
|
||||
func NewV1CamelOilOrderHistoryDao(handlers ...gdb.ModelHandler) *V1CamelOilOrderHistoryDao {
|
||||
return &V1CamelOilOrderHistoryDao{
|
||||
group: "default",
|
||||
table: "camel_oil_order_history",
|
||||
columns: v1CamelOilOrderHistoryColumns,
|
||||
handlers: handlers,
|
||||
}
|
||||
}
|
||||
|
||||
// DB retrieves and returns the underlying raw database management object of the current DAO.
|
||||
func (dao *V1CamelOilOrderHistoryDao) DB() gdb.DB {
|
||||
return g.DB(dao.group)
|
||||
}
|
||||
|
||||
// Table returns the table name of the current DAO.
|
||||
func (dao *V1CamelOilOrderHistoryDao) Table() string {
|
||||
return dao.table
|
||||
}
|
||||
|
||||
// Columns returns all column names of the current DAO.
|
||||
func (dao *V1CamelOilOrderHistoryDao) Columns() V1CamelOilOrderHistoryColumns {
|
||||
return dao.columns
|
||||
}
|
||||
|
||||
// Group returns the database configuration group name of the current DAO.
|
||||
func (dao *V1CamelOilOrderHistoryDao) 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 *V1CamelOilOrderHistoryDao) 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 *V1CamelOilOrderHistoryDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) {
|
||||
return dao.Ctx(ctx).Transaction(ctx, f)
|
||||
}
|
||||
22
internal/internal/model/v_1_camel_oil_account.go
Normal file
22
internal/internal/model/v_1_camel_oil_account.go
Normal file
@@ -0,0 +1,22 @@
|
||||
// =================================================================================
|
||||
// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed.
|
||||
// =================================================================================
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"kami/internal/internal/model/internal"
|
||||
)
|
||||
|
||||
// v1CamelOilAccountDao is the data access object for the table camel_oil_account.
|
||||
// You can define custom methods on it to extend its functionality as needed.
|
||||
type v1CamelOilAccountDao struct {
|
||||
*internal.V1CamelOilAccountDao
|
||||
}
|
||||
|
||||
var (
|
||||
// V1CamelOilAccount is a globally accessible object for table camel_oil_account operations.
|
||||
V1CamelOilAccount = v1CamelOilAccountDao{internal.NewV1CamelOilAccountDao()}
|
||||
)
|
||||
|
||||
// Add your custom methods and functionality below.
|
||||
22
internal/internal/model/v_1_camel_oil_account_history.go
Normal file
22
internal/internal/model/v_1_camel_oil_account_history.go
Normal file
@@ -0,0 +1,22 @@
|
||||
// =================================================================================
|
||||
// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed.
|
||||
// =================================================================================
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"kami/internal/internal/model/internal"
|
||||
)
|
||||
|
||||
// v1CamelOilAccountHistoryDao is the data access object for the table camel_oil_account_history.
|
||||
// You can define custom methods on it to extend its functionality as needed.
|
||||
type v1CamelOilAccountHistoryDao struct {
|
||||
*internal.V1CamelOilAccountHistoryDao
|
||||
}
|
||||
|
||||
var (
|
||||
// V1CamelOilAccountHistory is a globally accessible object for table camel_oil_account_history operations.
|
||||
V1CamelOilAccountHistory = v1CamelOilAccountHistoryDao{internal.NewV1CamelOilAccountHistoryDao()}
|
||||
)
|
||||
|
||||
// Add your custom methods and functionality below.
|
||||
22
internal/internal/model/v_1_camel_oil_order.go
Normal file
22
internal/internal/model/v_1_camel_oil_order.go
Normal file
@@ -0,0 +1,22 @@
|
||||
// =================================================================================
|
||||
// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed.
|
||||
// =================================================================================
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"kami/internal/internal/model/internal"
|
||||
)
|
||||
|
||||
// v1CamelOilOrderDao is the data access object for the table camel_oil_order.
|
||||
// You can define custom methods on it to extend its functionality as needed.
|
||||
type v1CamelOilOrderDao struct {
|
||||
*internal.V1CamelOilOrderDao
|
||||
}
|
||||
|
||||
var (
|
||||
// V1CamelOilOrder is a globally accessible object for table camel_oil_order operations.
|
||||
V1CamelOilOrder = v1CamelOilOrderDao{internal.NewV1CamelOilOrderDao()}
|
||||
)
|
||||
|
||||
// Add your custom methods and functionality below.
|
||||
22
internal/internal/model/v_1_camel_oil_order_history.go
Normal file
22
internal/internal/model/v_1_camel_oil_order_history.go
Normal file
@@ -0,0 +1,22 @@
|
||||
// =================================================================================
|
||||
// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed.
|
||||
// =================================================================================
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"kami/internal/internal/model/internal"
|
||||
)
|
||||
|
||||
// v1CamelOilOrderHistoryDao is the data access object for the table camel_oil_order_history.
|
||||
// You can define custom methods on it to extend its functionality as needed.
|
||||
type v1CamelOilOrderHistoryDao struct {
|
||||
*internal.V1CamelOilOrderHistoryDao
|
||||
}
|
||||
|
||||
var (
|
||||
// V1CamelOilOrderHistory is a globally accessible object for table camel_oil_order_history operations.
|
||||
V1CamelOilOrderHistory = v1CamelOilOrderHistoryDao{internal.NewV1CamelOilOrderHistoryDao()}
|
||||
)
|
||||
|
||||
// Add your custom methods and functionality below.
|
||||
@@ -36,37 +36,6 @@ func (s *sCamelOil) GetAvailableOrderCapacity(ctx context.Context) (capacity int
|
||||
return result.TotalCapacity, nil
|
||||
}
|
||||
|
||||
// CheckAndTriggerAccountLogin 检查容量并触发账号登录
|
||||
// 如果可用订单容量<50,触发账号登录任务
|
||||
func (s *sCamelOil) CheckAndTriggerAccountLogin(ctx context.Context) (err error) {
|
||||
// 1. 获取当前可用容量
|
||||
capacity, err := s.GetAvailableOrderCapacity(ctx)
|
||||
if err != nil {
|
||||
glog.Error(ctx, "获取可用订单容量失败", err)
|
||||
return err
|
||||
}
|
||||
|
||||
glog.Info(ctx, "当前可用订单容量", capacity)
|
||||
|
||||
// 2. 如果容量充足,无需登录
|
||||
if capacity >= 50 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 3. 计算需要登录的账号数量
|
||||
needCount := (50 - capacity + 9) / 10 // 向上取整
|
||||
|
||||
glog.Info(ctx, "可用订单容量不足,需要登录账号", needCount)
|
||||
|
||||
// 4. 触发账号登录任务
|
||||
// 这里会在cron任务中调用LoginAccount方法
|
||||
// 暂时只记录日志,具体登录逻辑在account_login.go中实现
|
||||
if _, err = s.BatchLoginAccounts(ctx, needCount); err != nil {
|
||||
glog.Error(ctx, "批量登录失败", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetAccountPoolStatus 获取账号池状态统计
|
||||
func (s *sCamelOil) GetAccountPoolStatus(ctx context.Context) (status map[string]interface{}, err error) {
|
||||
m := dao.V1CamelOilAccount.Ctx(ctx).DB(config.GetDatabaseV1())
|
||||
|
||||
@@ -43,7 +43,6 @@ func (s *sCamelOil) GetAccountHistory(ctx context.Context, req *v1.AccountHistor
|
||||
items := make([]v1.AccountHistoryItem, 0, len(histories))
|
||||
for _, history := range histories {
|
||||
items = append(items, v1.AccountHistoryItem{
|
||||
HistoryUuid: history.HistoryUuid,
|
||||
AccountId: history.AccountId,
|
||||
ChangeType: consts.CamelOilAccountChangeType(history.ChangeType),
|
||||
ChangeText: getAccountChangeTypeText(history.ChangeType),
|
||||
|
||||
@@ -3,77 +3,91 @@ package camel_oil
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/os/glog"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"kami/internal/consts"
|
||||
"kami/internal/dao"
|
||||
"kami/internal/model/do"
|
||||
"kami/utility/config"
|
||||
"kami/utility/integration/camel_oil_api"
|
||||
"kami/utility/integration/pig"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/os/glog"
|
||||
)
|
||||
|
||||
// LoginAccount 执行账号登录流程
|
||||
// 注意:当前使用假数据,实际应对接骆驼加油平台和接码平台
|
||||
func (s *sCamelOil) LoginAccount(ctx context.Context) (err error) {
|
||||
// 对接接码平台
|
||||
phoneNumber, err := pig.NewClient().GetAccountInfo(ctx)
|
||||
if err != nil {
|
||||
return gerror.Wrap(err, "获取手机号失败")
|
||||
// 对接接码平台,获取手机号并检查是否已存在
|
||||
var phoneNumber string
|
||||
for {
|
||||
phoneNumber, err = pig.NewClient().GetAccountInfo(ctx)
|
||||
if err != nil {
|
||||
return gerror.Wrap(err, "获取手机号失败")
|
||||
}
|
||||
|
||||
// 检查手机号是否已存在
|
||||
existingAccount, checkErr := dao.V1CamelOilAccount.Ctx(ctx).DB(config.GetDatabaseV1()).
|
||||
Where(dao.V1CamelOilAccount.Columns().Phone, phoneNumber).
|
||||
One()
|
||||
if checkErr != nil {
|
||||
return gerror.Wrap(checkErr, "检查手机号是否存在失败")
|
||||
}
|
||||
|
||||
// 如果手机号已存在,继续获取新的手机号
|
||||
if existingAccount == nil {
|
||||
// 手机号不存在,可以使用
|
||||
break
|
||||
}
|
||||
glog.Infof(ctx, "手机号已存在,重新获取: %s", phoneNumber)
|
||||
}
|
||||
|
||||
isOk, err := camel_oil_api.NewClient().SendCaptcha(ctx, phoneNumber)
|
||||
if err != nil {
|
||||
return gerror.Wrap(err, "发送验证码失败")
|
||||
}
|
||||
if !isOk {
|
||||
return gerror.New("获取验证码失败")
|
||||
}
|
||||
|
||||
accountId, err := s.CreateAccount(ctx, phoneNumber, "创建账号")
|
||||
if err != nil {
|
||||
return gerror.Wrap(err, "创建账号失败")
|
||||
}
|
||||
//发送验证码
|
||||
isOk, err := camel_oil_api.NewClient().SendCaptcha(ctx, phoneNumber)
|
||||
if err != nil {
|
||||
_ = s.UpdateAccountStatus(ctx, accountId, consts.CamelOilAccountStatusInvalid, consts.CamelOilAccountChangeTypeLoginFail, "获取验证码失败")
|
||||
return gerror.Wrap(err, "发送验证码失败")
|
||||
}
|
||||
if !isOk {
|
||||
_ = s.UpdateAccountStatus(ctx, accountId, consts.CamelOilAccountStatusInvalid, consts.CamelOilAccountChangeTypeLoginFail, "获取验证码失败")
|
||||
return gerror.New("获取验证码失败")
|
||||
}
|
||||
// 更新状态为登录中
|
||||
_, err = dao.V1CamelOilAccount.Ctx(ctx).DB(config.GetDatabaseV1()).
|
||||
Where(dao.V1CamelOilAccount.Columns().Id, accountId).
|
||||
Update(do.V1CamelOilAccount{
|
||||
Status: consts.CamelOilAccountStatusSendCode,
|
||||
UpdatedAt: gtime.Now(),
|
||||
})
|
||||
if err != nil {
|
||||
_ = s.UpdateAccountStatus(ctx, accountId, consts.CamelOilAccountStatusInvalid, consts.CamelOilAccountChangeTypeLoginFail, "获取验证码失败")
|
||||
return gerror.Wrap(err, "更新账号状态为登录中失败")
|
||||
}
|
||||
|
||||
_ = s.UpdateAccountStatus(ctx, accountId, consts.CamelOilAccountStatusSendCode, consts.CamelOilAccountChangeTypeLogin, "获取验证码失败")
|
||||
_ = s.UpdateAccountStatus(ctx, accountId, consts.CamelOilAccountStatusSendCode, consts.CamelOilAccountChangeTypeLogin, "获取验证码成功")
|
||||
return nil
|
||||
}
|
||||
|
||||
// BatchLoginAccounts 批量登录账号
|
||||
func (s *sCamelOil) BatchLoginAccounts(ctx context.Context, count int) (successCount int, err error) {
|
||||
func (s *sCamelOil) BatchLoginAccounts(ctx context.Context, count int64) (successCount int64, err error) {
|
||||
if count <= 0 {
|
||||
return 0, gerror.New("登录数量必须大于0")
|
||||
}
|
||||
|
||||
// 逐个登录账号
|
||||
successCount = 0
|
||||
cycleCount := 0
|
||||
for {
|
||||
cycleCount++
|
||||
if successCount >= count || cycleCount >= 100 {
|
||||
for range 10 {
|
||||
if successCount >= count {
|
||||
break
|
||||
}
|
||||
loginErr := s.LoginAccount(ctx)
|
||||
if loginErr != nil {
|
||||
glog.Errorf(ctx, "账号登录失败,ID: %d, 错误: %v", loginErr)
|
||||
continue
|
||||
wg := sync.WaitGroup{}
|
||||
sem := make(chan struct{}, 1)
|
||||
for i := 0; i < int(count-successCount); i++ {
|
||||
sem <- struct{}{}
|
||||
wg.Go(func() {
|
||||
defer func() { <-sem }()
|
||||
loginErr := s.LoginAccount(ctx)
|
||||
if loginErr != nil {
|
||||
glog.Errorf(ctx, "账号登录失败,ID: %d, 错误: %v", loginErr)
|
||||
return
|
||||
}
|
||||
atomic.AddInt64(&successCount, 1)
|
||||
})
|
||||
}
|
||||
successCount += 1
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
glog.Infof(ctx, "批量登录完成,成功: %d", successCount)
|
||||
return successCount, nil
|
||||
}
|
||||
@@ -85,7 +99,6 @@ func (s *sCamelOil) markAccountInvalid(ctx context.Context, accountId int64, rea
|
||||
Update(do.V1CamelOilAccount{
|
||||
Status: consts.CamelOilAccountStatusInvalid,
|
||||
FailureReason: reason,
|
||||
UpdatedAt: gtime.Now(),
|
||||
})
|
||||
if err != nil {
|
||||
return gerror.Wrap(err, "标记账号为已失效失败")
|
||||
@@ -99,38 +112,3 @@ func (s *sCamelOil) markAccountInvalid(ctx context.Context, accountId int64, rea
|
||||
glog.Warningf(ctx, "账号已标记为失效,ID: %d, 原因: %s", accountId, reason)
|
||||
return nil
|
||||
}
|
||||
|
||||
// CheckAndLoginAccounts 检查容量并登录账号
|
||||
// 根据当前可用订单容量,决定是否需要登录新账号
|
||||
func (s *sCamelOil) CheckAndLoginAccounts(ctx context.Context) (err error) {
|
||||
// 计算当前可用订单容量
|
||||
capacity, err := s.GetAvailableOrderCapacity(ctx)
|
||||
if err != nil {
|
||||
return gerror.Wrap(err, "计算可用容量失败")
|
||||
}
|
||||
|
||||
glog.Infof(ctx, "当前可用订单容量: %d", capacity)
|
||||
|
||||
// 容量充足,无需登录
|
||||
if capacity >= 50 {
|
||||
glog.Infof(ctx, "当前容量充足 (%d >= 50),无需登录新账号", capacity)
|
||||
return nil
|
||||
}
|
||||
|
||||
// 计算需要登录的账号数量
|
||||
needCapacity := 50 - capacity
|
||||
needAccounts := (needCapacity + 9) / 10 // 向上取整
|
||||
|
||||
glog.Infof(ctx, "当前容量不足 (%d < 50),需要登录 %d 个账号", capacity, needAccounts)
|
||||
|
||||
// 批量登录账号
|
||||
successCount, err := s.BatchLoginAccounts(ctx, needAccounts)
|
||||
if err != nil {
|
||||
return gerror.Wrap(err, "批量登录账号失败")
|
||||
}
|
||||
|
||||
glog.Infof(ctx, "登录账号完成,成功: %d 个", successCount)
|
||||
return nil
|
||||
}
|
||||
|
||||
// 注意:RecordAccountHistory 方法已在 account.go 中定义,此处不重复定义
|
||||
|
||||
@@ -3,6 +3,7 @@ package camel_oil
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"kami/internal/consts"
|
||||
"kami/internal/dao"
|
||||
@@ -16,38 +17,79 @@ import (
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
)
|
||||
|
||||
// CronAccountLoginTask 账号登录任务 - 由cron调度器定期调用
|
||||
func (s *sCamelOil) CronAccountLoginTask(ctx context.Context) error {
|
||||
// CronAccountPrefetchTask 账户预拉取定时任务 - 由cron调度器定期调用
|
||||
// 流程:并发拉取账户到指定数量
|
||||
func (s *sCamelOil) CronAccountPrefetchTask(ctx context.Context) error {
|
||||
glog.Info(ctx, "开始执行账户预拉取任务")
|
||||
|
||||
// 检查可用订单容量
|
||||
capacity, err := s.GetAvailableOrderCapacity(ctx)
|
||||
// 1. 获取当前在线账号数量
|
||||
m := dao.V1CamelOilAccount.Ctx(ctx).DB(config.GetDatabaseV1())
|
||||
onlineCount, err := m.Where(dao.V1CamelOilAccount.Columns().Status, consts.CamelOilAccountStatusOnline).
|
||||
WhereOr(dao.V1CamelOilAccount.Columns().Status, consts.CamelOilAccountStatusSendCode).
|
||||
Count()
|
||||
if err != nil {
|
||||
glog.Error(ctx, "获取可用容量失败:", err)
|
||||
return err
|
||||
glog.Errorf(ctx, "获取在线账号数量失败: %v", err)
|
||||
onlineCount = 0
|
||||
}
|
||||
|
||||
glog.Infof(ctx, "当前可用订单容量: %d", capacity)
|
||||
glog.Infof(ctx, "当前在线账号数量: %d, 目标数量: %d", onlineCount, consts.CamelOilTargetOnlineAccounts)
|
||||
|
||||
// 如果容量低于50,继续登录新账号
|
||||
if capacity <= 50 {
|
||||
err = s.CheckAndTriggerAccountLogin(ctx)
|
||||
// 2. 如果在线账号少于目标数,触发并发登录
|
||||
if onlineCount < consts.CamelOilTargetOnlineAccounts {
|
||||
needCount := consts.CamelOilTargetOnlineAccounts - onlineCount
|
||||
glog.Infof(ctx, "在线账号不足,需要登录 %d 个账号", needCount)
|
||||
|
||||
// 使用并发登录提高效率
|
||||
successCount, err := s.BatchLoginAccounts(ctx, int64(needCount))
|
||||
if err != nil {
|
||||
glog.Error(ctx, "触发账号登录失败:", err)
|
||||
return err
|
||||
glog.Errorf(ctx, "批量登录账号失败: %v", err)
|
||||
// 不返回错误,继续执行
|
||||
} else {
|
||||
glog.Infof(ctx, "成功登录 %d 个账号", successCount)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CronPrefetchOrderSupplementTask 预拉取订单补充定时任务 - 由cron调度器定期调用
|
||||
// 流程:检查预拉取订单库存,不足时补充
|
||||
func (s *sCamelOil) CronPrefetchOrderSupplementTask(ctx context.Context) error {
|
||||
glog.Info(ctx, "开始执行预拉取订单补充任务")
|
||||
|
||||
// 1. 检查预拉取订单库存
|
||||
capacity, err := s.GetPrefetchOrderCapacity(ctx)
|
||||
if err != nil {
|
||||
glog.Errorf(ctx, "获取预拉取订单库存失败: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
glog.Infof(ctx, "当前预拉取订单库存: %d", capacity)
|
||||
|
||||
// 2. 如果库存不足则补充
|
||||
if capacity < consts.CamelOilPrefetchOrderMinCapacity {
|
||||
glog.Infof(ctx, "预拉取订单库存不足,开始补充任务")
|
||||
supplementedCount, err := s.SupplementPrefetchOrders(ctx)
|
||||
if err != nil {
|
||||
glog.Errorf(ctx, "预拉取订单补充失败: %v", err)
|
||||
return err
|
||||
}
|
||||
glog.Infof(ctx, "预拉取订单补充完成,补充数量: %d", supplementedCount)
|
||||
} else {
|
||||
glog.Infof(ctx, "预拉取订单库存充足 (%d >= %d),无需补充", capacity, consts.CamelOilPrefetchOrderMinCapacity)
|
||||
}
|
||||
|
||||
glog.Info(ctx, "预拉取订单补充任务完成")
|
||||
return nil
|
||||
}
|
||||
|
||||
// CronOrderPaymentCheckTask 订单支付状态检测任务 - 由cron调度器定期调用
|
||||
func (s *sCamelOil) CronOrderPaymentCheckTask(ctx context.Context) error {
|
||||
glog.Info(ctx, "开始执行订单支付状态检测任务")
|
||||
|
||||
// 查询待支付订单(创建时间在24小时内)
|
||||
var orders []*entity.V1CamelOilOrder
|
||||
err := dao.V1CamelOilOrder.Ctx(ctx).DB(config.GetDatabaseV1()).
|
||||
Where(dao.V1CamelOilOrder.Columns().PayStatus, consts.CamelOilPaymentStatusUnpaid).
|
||||
WhereGTE(dao.V1CamelOilOrder.Columns().CreatedAt, gtime.Now().AddDate(0, 0, -1)).
|
||||
WhereGTE(dao.V1CamelOilOrder.Columns().CreatedAt, gtime.Now().Add(time.Hour*6)).
|
||||
Scan(&orders)
|
||||
|
||||
if err != nil {
|
||||
@@ -62,7 +104,6 @@ func (s *sCamelOil) CronOrderPaymentCheckTask(ctx context.Context) error {
|
||||
|
||||
glog.Infof(ctx, "查询到 %d 个待支付订单", len(orders))
|
||||
|
||||
// 检测每个订单的支付状态(使用假数据)
|
||||
paidCount := 0
|
||||
timeoutCount := 0
|
||||
|
||||
@@ -70,32 +111,49 @@ func (s *sCamelOil) CronOrderPaymentCheckTask(ctx context.Context) error {
|
||||
accountInfo, err2 := s.GetAccountInfo(ctx, order.AccountId)
|
||||
if err2 != nil {
|
||||
glog.Error(ctx, "获取账号信息失败:", err2)
|
||||
return err2
|
||||
// 记录该订单检查失败
|
||||
_ = s.UpdateOrderStatus(ctx, order.Id, consts.CamelOilOrderStatusFailed, consts.CamelOilOrderChangeTypeFail, "", fmt.Sprintf("查询账户失败: %v", err2))
|
||||
continue
|
||||
}
|
||||
ok, err := camel_oil_api.NewClient().QueryOrder(ctx, accountInfo.Phone, accountInfo.Token, order.PlatformOrderNo)
|
||||
if err != nil {
|
||||
glog.Error(ctx, "查询订单状态失败:", err)
|
||||
|
||||
// 查询订单状态
|
||||
queryResult, err2 := camel_oil_api.NewClient().QueryOrder(ctx, accountInfo.Phone, accountInfo.Token, order.PlatformOrderNo)
|
||||
if err2 != nil {
|
||||
glog.Error(ctx, "查询订单状态失败:", err2)
|
||||
_ = s.RecordOrderHistory(ctx, order.OrderNo, consts.CamelOilOrderChangeType("query_failed"), "", fmt.Sprintf("查询订单失败: %v", err))
|
||||
continue
|
||||
}
|
||||
if ok {
|
||||
// 更新状态
|
||||
|
||||
// 订单已支付
|
||||
if queryResult != nil {
|
||||
_ = s.fillOrderCard(ctx, order.OrderNo, queryResult.CardNumber, queryResult.CardNumber)
|
||||
if order.PayStatus != int(consts.CamelOilPaymentStatusPaid) {
|
||||
glog.Infof(ctx, "订单%s已支付,金额: %.2f", order.OrderNo, queryResult.Balance)
|
||||
_, _ = dao.V1CamelOilOrder.Ctx(ctx).DB(config.GetDatabaseV1()).
|
||||
Where(dao.V1CamelOilOrder.Columns().Id, order.Id).
|
||||
Update(&do.V1CamelOilOrder{
|
||||
CardNumber: queryResult.CardNumber,
|
||||
CardPassword: queryResult.CardPassword,
|
||||
PaidAt: gtime.Now(),
|
||||
PayStatus: consts.CamelOilPaymentStatusPaid,
|
||||
})
|
||||
_ = s.RecordOrderHistory(ctx, order.OrderNo, consts.CamelOilOrderChangeTypePaid, "", fmt.Sprintf("支付成功,金额: %.2f", queryResult.Balance))
|
||||
paidCount++
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// 订单未支付,检查是否超时(24小时)
|
||||
if gtime.Now().Sub(order.CreatedAt).Hours() >= 1 {
|
||||
glog.Warningf(ctx, "订单%s支付超时,创建时间: %v", order.OrderNo, order.CreatedAt)
|
||||
_, _ = dao.V1CamelOilOrder.Ctx(ctx).DB(config.GetDatabaseV1()).
|
||||
Where(dao.V1CamelOilOrder.Columns().Id, order.Id).
|
||||
Update(&do.V1CamelOilOrder{
|
||||
PaidAt: gtime.Now(),
|
||||
PayStatus: int(consts.CamelOilPaymentStatusPaid),
|
||||
Status: consts.CamelOilOrderStatusFailed,
|
||||
PayStatus: consts.CamelOilPaymentStatusTimeout,
|
||||
FailureReason: "支付时间超过24小时,支付超时",
|
||||
})
|
||||
}
|
||||
// 模拟支付状态检测
|
||||
// 如果订单创建超过1小时,标记为超时
|
||||
if gtime.Now().Sub(order.CreatedAt).Hours() > 1 {
|
||||
// 更新为超时
|
||||
_, _ = dao.V1CamelOilOrder.Ctx(ctx).DB(config.GetDatabaseV1()).
|
||||
Where(dao.V1CamelOilOrder.Columns().Id, order.Id).
|
||||
Update(&do.V1CamelOilOrder{
|
||||
PayStatus: int(consts.CamelOilPaymentStatusTimeout),
|
||||
FailureReason: "支付时间超过一个小时,支付超时",
|
||||
})
|
||||
_ = s.RecordOrderHistory(ctx, order.OrderNo, "payment_timeout", "", "订单支付超时")
|
||||
_ = s.RecordOrderHistory(ctx, order.OrderNo, consts.CamelOilOrderChangeTypeTimeout, "", "订单支付超时")
|
||||
timeoutCount++
|
||||
}
|
||||
}
|
||||
@@ -187,6 +245,15 @@ func (s *sCamelOil) CronVerifyCodeCheckTask(ctx context.Context) error {
|
||||
camelClient := camel_oil_api.NewClient()
|
||||
|
||||
for _, account := range accounts {
|
||||
//如果时间超过 1 分钟,就是过期
|
||||
if gtime.Now().Sub(account.CreatedAt).Minutes() > 1 {
|
||||
glog.Warningf(ctx, "验证码已过期,账号ID: %d, 手机号: %s", account.Id, account.Phone)
|
||||
_ = s.UpdateAccountStatus(ctx, account.Id, consts.CamelOilAccountStatusInvalid,
|
||||
consts.CamelOilAccountChangeTypeLoginFail, "验证码已过期")
|
||||
failCount++
|
||||
continue
|
||||
}
|
||||
|
||||
// 从野猪平台检测验证码是否已接收
|
||||
verifyCode, received, err := pigClient.CheckVerifyCode(ctx, account.Phone)
|
||||
if err != nil {
|
||||
@@ -233,6 +300,7 @@ func (s *sCamelOil) CronVerifyCodeCheckTask(ctx context.Context) error {
|
||||
consts.CamelOilAccountChangeTypeLogin, fmt.Sprintf("登录成功,手机号: %s", account.Phone))
|
||||
|
||||
glog.Infof(ctx, "账号登录成功,ID: %d, 手机号: %s, Token: %s", account.Id, account.Phone, token)
|
||||
|
||||
successCount++
|
||||
}
|
||||
|
||||
|
||||
@@ -3,14 +3,14 @@ package camel_oil
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"kami/utility/utils"
|
||||
|
||||
"github.com/gogf/gf/v2/os/glog"
|
||||
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gmlock"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"github.com/shopspring/decimal"
|
||||
"kami/utility/integration/camel_oil_api"
|
||||
"kami/utility/utils"
|
||||
|
||||
v1 "kami/api/camel_oil/v1"
|
||||
"kami/internal/consts"
|
||||
@@ -24,141 +24,152 @@ import (
|
||||
// 订单管理相关方法
|
||||
// ====================================================================================
|
||||
|
||||
// UpdateOrderStatus 更新订单状态并记录历史
|
||||
func (s *sCamelOil) UpdateOrderStatus(ctx context.Context, orderId int64, newStatus consts.CamelOilOrderStatus, operationType consts.CamelOilOrderChangeType, rawData string, description string) (err error) {
|
||||
m := dao.V1CamelOilOrder.Ctx(ctx).DB(config.GetDatabaseV1())
|
||||
|
||||
// 获取当前订单信息
|
||||
var order *entity.V1CamelOilOrder
|
||||
err = m.Where(dao.V1CamelOilOrder.Columns().Id, orderId).Scan(&order)
|
||||
if err != nil {
|
||||
return gerror.Wrap(err, "查询订单失败")
|
||||
}
|
||||
|
||||
if order == nil {
|
||||
return gerror.New("订单不存在")
|
||||
}
|
||||
|
||||
oldStatus := consts.CamelOilOrderStatus(order.Status)
|
||||
|
||||
// 如果状态没有变化,则不更新
|
||||
if oldStatus == newStatus {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 更新订单状态
|
||||
_, err = m.Where(dao.V1CamelOilOrder.Columns().Id, orderId).Update(&do.V1CamelOilOrder{
|
||||
Status: int(newStatus),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return gerror.Wrap(err, "更新订单状态失败")
|
||||
}
|
||||
|
||||
// 记录订单变更历史
|
||||
_ = s.RecordOrderHistory(ctx, order.OrderNo, operationType, rawData, description)
|
||||
|
||||
g.Log().Infof(ctx, "订单状态更新成功,订单号=%s, 原状态=%d, 新状态=%d", order.OrderNo, oldStatus, newStatus)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SubmitOrder 提交订单并返回支付宝支付链接
|
||||
func (s *sCamelOil) SubmitOrder(ctx context.Context, req *v1.SubmitOrderReq) (res *v1.SubmitOrderRes, err error) {
|
||||
// 1. 检查可用订单容量,低于50则触发账号登录任务
|
||||
capacity, err := s.GetAvailableOrderCapacity(ctx)
|
||||
if err != nil {
|
||||
return nil, gerror.Wrap(err, "检查账号容量失败")
|
||||
}
|
||||
|
||||
// 容量不足50,触发异步登录任务
|
||||
if capacity <= 50 {
|
||||
g.Log().Infof(ctx, "可用订单容量不足50(当前:%d),触发账号登录任务", capacity)
|
||||
go func() {
|
||||
if err = s.CheckAndTriggerAccountLogin(context.Background()); err != nil {
|
||||
g.Log().Errorf(ctx, "触发账号登录任务失败:%v", err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
accountCount, _ := s.GetOrderCountByStatus(ctx, consts.CamelOilAccountStatusOnline)
|
||||
for i := 0; i < accountCount; i++ {
|
||||
account, err := s.GetAvailableAccount(ctx)
|
||||
if err != nil {
|
||||
return nil, gerror.Wrap(err, "获取可用账号失败")
|
||||
}
|
||||
if account == nil {
|
||||
return nil, gerror.New("暂无可用账号,请稍后重试")
|
||||
}
|
||||
platformOrderId, payId, err := camel_oil_api.NewClient().CreateOrder(ctx, account.Phone, account.Token, req.Amount)
|
||||
if err != nil {
|
||||
if err.Error() == "auth_error" {
|
||||
_ = s.UpdateAccountStatus(ctx, account.Id, consts.CamelOilAccountStatusInvalid, consts.CamelOilAccountChangeTypeInvalidate, "账号token失效")
|
||||
continue
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
// 生成系统订单号
|
||||
orderNo := fmt.Sprintf("CO%s", utils.GenerateRandomUUID())
|
||||
|
||||
gmlock.LockFunc(fmt.Sprintf("camelSubmitOrder_%d", account.Id), func() {
|
||||
// 4. 保存订单记录并更新账号使用信息(使用事务)
|
||||
err = dao.V1CamelOilOrder.Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
|
||||
// 插入订单
|
||||
_, err = dao.V1CamelOilOrder.Ctx(ctx).DB(config.GetDatabaseV1()).Insert(&do.V1CamelOilOrder{
|
||||
OrderNo: orderNo,
|
||||
MerchantOrderId: req.MerchantOrderId,
|
||||
AccountId: account.Id,
|
||||
AccountName: account.AccountName,
|
||||
PlatformOrderNo: platformOrderId,
|
||||
Amount: decimal.NewFromFloat(req.Amount),
|
||||
AlipayUrl: payId,
|
||||
Status: 1, // 1=待支付
|
||||
PayStatus: 0, // 0=未支付
|
||||
NotifyStatus: 0, // 0=未回调
|
||||
NotifyCount: 0,
|
||||
Attach: req.Attach,
|
||||
})
|
||||
if err != nil {
|
||||
return gerror.Wrap(err, "保存订单记录失败")
|
||||
}
|
||||
|
||||
_, err = dao.V1CamelOilAccount.Ctx(ctx).DB(config.GetDatabaseV1()).
|
||||
Where(dao.V1CamelOilAccount.Columns().Id, account.Id).
|
||||
Increment(dao.V1CamelOilAccount.Columns().DailyOrderCount, 1)
|
||||
if err != nil {
|
||||
return gerror.Wrap(err, "更新账号使用记录失败")
|
||||
}
|
||||
|
||||
_, err = dao.V1CamelOilAccount.Ctx(ctx).DB(config.GetDatabaseV1()).
|
||||
Where(dao.V1CamelOilAccount.Columns().Id, account.Id).
|
||||
Increment(dao.V1CamelOilAccount.Columns().TotalOrderCount, 1)
|
||||
if err != nil {
|
||||
return gerror.Wrap(err, "更新账号使用记录失败")
|
||||
}
|
||||
|
||||
// 检查账号是否达到单日限额(10单)
|
||||
var updatedAccount *entity.V1CamelOilAccount
|
||||
err = dao.V1CamelOilAccount.Ctx(ctx).DB(config.GetDatabaseV1()).
|
||||
Where(dao.V1CamelOilAccount.Columns().Id, account.Id).
|
||||
Scan(&updatedAccount)
|
||||
if err != nil {
|
||||
return gerror.Wrap(err, "查询账号失败")
|
||||
}
|
||||
|
||||
if updatedAccount.DailyOrderCount >= 10 {
|
||||
// 达到限额,标记为已暂停 (status=3)
|
||||
_, err = dao.V1CamelOilAccount.Ctx(ctx).DB(config.GetDatabaseV1()).
|
||||
Where(dao.V1CamelOilAccount.Columns().Id, account.Id).
|
||||
Update(do.V1CamelOilAccount{
|
||||
Status: consts.CamelOilAccountStatusPaused, // 3=暂停
|
||||
})
|
||||
if err != nil {
|
||||
return gerror.Wrap(err, "更新账号状态失败")
|
||||
}
|
||||
|
||||
g.Log().Infof(ctx, "账号[%s]达到单日限额10单,已暂停", account.Phone)
|
||||
}
|
||||
|
||||
// 记录订单历史
|
||||
_, err = dao.V1CamelOilOrderHistory.Ctx(ctx).DB(config.GetDatabaseV1()).Data(&do.V1CamelOilOrderHistory{
|
||||
HistoryUuid: utils.GenerateRandomUUID(),
|
||||
OrderNo: orderNo,
|
||||
ChangeType: "create",
|
||||
AccountId: account.Id,
|
||||
AccountName: account.AccountName,
|
||||
Remark: fmt.Sprintf("创建订单:商户订单号=%s,平台订单号=%s", req.MerchantOrderId, platformOrderId),
|
||||
}).Insert()
|
||||
if err != nil {
|
||||
g.Log().Errorf(ctx, "记录订单历史失败:%v", err)
|
||||
}
|
||||
|
||||
// 记录账号历史
|
||||
_ = s.RecordAccountHistory(ctx, account.Id, consts.CamelOilAccountChangeTypeOrderBind,
|
||||
consts.CamelOilAccountStatus(account.Status), consts.CamelOilAccountStatus(account.Status),
|
||||
fmt.Sprintf("绑定订单:%s", orderNo))
|
||||
|
||||
return nil
|
||||
})
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 5. 返回支付链接
|
||||
res = &v1.SubmitOrderRes{
|
||||
OrderNo: orderNo,
|
||||
AlipayUrl: "",
|
||||
var order *entity.V1CamelOilOrder
|
||||
_ = dao.V1CamelOilOrder.Ctx(ctx).DB(config.GetDatabaseV1()).Where(dao.V1CamelOilOrder.Columns().MerchantOrderId, req.MerchantOrderId).Scan(&order)
|
||||
if order != nil {
|
||||
return &v1.SubmitOrderRes{
|
||||
OrderNo: order.OrderNo,
|
||||
AlipayUrl: order.AlipayUrl,
|
||||
Amount: req.Amount,
|
||||
CreatedAt: gtime.Now(),
|
||||
}
|
||||
|
||||
g.Log().Infof(ctx, "订单创建成功:商户订单号=%s,平台订单号=%s,账号=%s 系统订单号=%s",
|
||||
req.MerchantOrderId, platformOrderId, account.Phone, orderNo)
|
||||
|
||||
return res, nil
|
||||
CreatedAt: order.CreatedAt,
|
||||
}, nil
|
||||
}
|
||||
return nil, gerror.New("暂无可用账号,请稍后重试")
|
||||
|
||||
//// 直接拉取一单
|
||||
//platformOrderId, payUrl, err := camel_oil_api.NewClient().CreateOrder(ctx, account.Phone, account.Token, req.Amount)
|
||||
//if err != nil {
|
||||
// if err.Error() == "auth_error" {
|
||||
// _ = s.UpdateAccountStatus(ctx, account.Id, consts.CamelOilAccountStatusInvalid, consts.CamelOilAccountChangeTypeInvalidate, "账号token失效")
|
||||
// }
|
||||
// return nil, gerror.Wrap(err, "拉取订单失败")
|
||||
//}
|
||||
prefetchOrder, err := s.PrefetchOrderConcurrently(ctx, req.Amount)
|
||||
if err != nil {
|
||||
return nil, gerror.Wrap(err, "拉取订单失败")
|
||||
}
|
||||
// 2. 创建空订单记录
|
||||
orderNo := fmt.Sprintf("CO%s", utils.GenerateRandomUUID())
|
||||
_, err = dao.V1CamelOilOrder.Ctx(ctx).DB(config.GetDatabaseV1()).Insert(&do.V1CamelOilOrder{
|
||||
OrderNo: orderNo,
|
||||
MerchantOrderId: req.MerchantOrderId,
|
||||
Amount: decimal.NewFromFloat(req.Amount),
|
||||
Status: consts.CamelOilOrderStatusPending, // 1=待支付
|
||||
PayStatus: consts.CamelOilPaymentStatusUnpaid, // 0=未支付
|
||||
NotifyStatus: consts.CamelOilCallbackStatusPending, // 0=未回调
|
||||
NotifyCount: 0,
|
||||
AccountId: prefetchOrder.AccountId,
|
||||
AccountName: prefetchOrder.AccountName,
|
||||
PlatformOrderNo: prefetchOrder.PlatformOrderNo,
|
||||
AlipayUrl: prefetchOrder.AlipayUrl,
|
||||
Attach: req.Attach,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, gerror.Wrap(err, "更新订单失败")
|
||||
}
|
||||
|
||||
// 更新账号使用记录
|
||||
_, _ = dao.V1CamelOilAccount.Ctx(ctx).DB(config.GetDatabaseV1()).
|
||||
Where(dao.V1CamelOilAccount.Columns().Id, prefetchOrder.AccountId).
|
||||
Increment(dao.V1CamelOilAccount.Columns().DailyOrderCount, 1)
|
||||
|
||||
_, _ = dao.V1CamelOilAccount.Ctx(ctx).DB(config.GetDatabaseV1()).
|
||||
Where(dao.V1CamelOilAccount.Columns().Id, prefetchOrder.AccountId).
|
||||
Increment(dao.V1CamelOilAccount.Columns().TotalOrderCount, 1)
|
||||
|
||||
glog.Infof(ctx, "订单提交成功: 订单号=%s", orderNo)
|
||||
|
||||
res = &v1.SubmitOrderRes{
|
||||
OrderNo: orderNo,
|
||||
AlipayUrl: prefetchOrder.AlipayUrl,
|
||||
Amount: req.Amount,
|
||||
CreatedAt: gtime.Now(),
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// ====================================================================================
|
||||
// 卡密填写相关方法
|
||||
// ====================================================================================
|
||||
|
||||
// FillOrderCard 填写订单卡密和卡号
|
||||
func (s *sCamelOil) fillOrderCard(ctx context.Context, orderNo string, cardPassword string, cardNumber string) error {
|
||||
// 1. 查询订单信息
|
||||
var order *entity.V1CamelOilOrder
|
||||
err := dao.V1CamelOilOrder.Ctx(ctx).DB(config.GetDatabaseV1()).
|
||||
Where(dao.V1CamelOilOrder.Columns().OrderNo, orderNo).
|
||||
Scan(&order)
|
||||
if err != nil {
|
||||
return gerror.Wrap(err, "查询订单失败")
|
||||
}
|
||||
if order == nil {
|
||||
return gerror.New("订单不存在")
|
||||
}
|
||||
|
||||
// 2. 更新卡密和卡号
|
||||
_, err = dao.V1CamelOilOrder.Ctx(ctx).DB(config.GetDatabaseV1()).
|
||||
Where(dao.V1CamelOilOrder.Columns().OrderNo, orderNo).
|
||||
Update(&do.V1CamelOilOrder{
|
||||
CardPassword: cardPassword,
|
||||
CardNumber: cardNumber,
|
||||
})
|
||||
if err != nil {
|
||||
return gerror.Wrap(err, "更新卡密失败")
|
||||
}
|
||||
|
||||
// 3. 记录操作历史
|
||||
remark := fmt.Sprintf("填写卡密和卡号")
|
||||
_, err = dao.V1CamelOilOrderHistory.Ctx(ctx).DB(config.GetDatabaseV1()).Data(&do.V1CamelOilOrderHistory{
|
||||
OrderNo: orderNo,
|
||||
ChangeType: string(consts.CamelOilOrderChangeTypeFillCard),
|
||||
AccountId: order.AccountId,
|
||||
AccountName: order.AccountName,
|
||||
Remark: remark,
|
||||
}).Insert()
|
||||
if err != nil {
|
||||
g.Log().Errorf(ctx, "记录订单历史失败:%v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ func (s *sCamelOil) updateCallbackResult(ctx context.Context, order *entity.V1Ca
|
||||
return gerror.Wrap(err, "更新回调成功状态失败")
|
||||
}
|
||||
|
||||
_ = s.RecordOrderHistory(ctx, order.OrderNo, "callback_success", "", historyDesc)
|
||||
_ = s.RecordOrderHistory(ctx, order.OrderNo, consts.CamelOilOrderChangeTypeCallbackSuccess, "", historyDesc)
|
||||
} else {
|
||||
// 回调失败
|
||||
notifyCount := order.NotifyCount + 1
|
||||
@@ -54,7 +54,7 @@ func (s *sCamelOil) updateCallbackResult(ctx context.Context, order *entity.V1Ca
|
||||
return gerror.Wrap(err, "更新回调失败状态失败")
|
||||
}
|
||||
|
||||
_ = s.RecordOrderHistory(ctx, order.OrderNo, "callback_fail", "", historyDesc)
|
||||
_ = s.RecordOrderHistory(ctx, order.OrderNo, consts.CamelOilOrderChangeTypeCallbackFail, "", historyDesc)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
"kami/internal/model/do"
|
||||
"kami/internal/model/entity"
|
||||
"kami/utility/config"
|
||||
"kami/utility/utils"
|
||||
|
||||
"github.com/gogf/gf/v2/os/glog"
|
||||
)
|
||||
@@ -46,7 +45,6 @@ func (s *sCamelOil) GetOrderHistory(ctx context.Context, req *v1.OrderHistoryReq
|
||||
items := make([]v1.OrderHistoryItem, 0, len(histories))
|
||||
for _, history := range histories {
|
||||
items = append(items, v1.OrderHistoryItem{
|
||||
HistoryUuid: history.HistoryUuid,
|
||||
OrderNo: history.OrderNo,
|
||||
ChangeType: consts.CamelOilOrderChangeType(history.ChangeType),
|
||||
ChangeText: getOrderChangeTypeText(history.ChangeType),
|
||||
@@ -66,15 +64,14 @@ func (s *sCamelOil) GetOrderHistory(ctx context.Context, req *v1.OrderHistoryReq
|
||||
}
|
||||
|
||||
// RecordOrderHistory 记录订单历史
|
||||
func (s *sCamelOil) RecordOrderHistory(ctx context.Context, orderNo, changeType, rawData, remark string) error {
|
||||
func (s *sCamelOil) RecordOrderHistory(ctx context.Context, orderNo string, changeType consts.CamelOilOrderChangeType, rawData string, remark string) error {
|
||||
m := dao.V1CamelOilOrderHistory.Ctx(ctx).DB(config.GetDatabaseV1())
|
||||
|
||||
_, err := m.Insert(&do.V1CamelOilOrderHistory{
|
||||
HistoryUuid: utils.GenerateRandomUUID(),
|
||||
OrderNo: orderNo,
|
||||
ChangeType: changeType,
|
||||
RawData: rawData,
|
||||
Remark: remark,
|
||||
OrderNo: orderNo,
|
||||
ChangeType: string(changeType),
|
||||
RawData: rawData,
|
||||
Remark: remark,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
|
||||
417
internal/logic/camel_oil/prefetch_order.go
Normal file
417
internal/logic/camel_oil/prefetch_order.go
Normal file
@@ -0,0 +1,417 @@
|
||||
package camel_oil
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/gogf/gf/v2/os/glog"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/os/gmlock"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"github.com/shopspring/decimal"
|
||||
|
||||
"kami/internal/consts"
|
||||
"kami/internal/dao"
|
||||
"kami/internal/model"
|
||||
"kami/internal/model/do"
|
||||
"kami/internal/model/entity"
|
||||
"kami/utility/config"
|
||||
"kami/utility/integration/camel_oil_api"
|
||||
)
|
||||
|
||||
// ====================================================================================
|
||||
// 预拉取订单管理相关方法
|
||||
// ====================================================================================
|
||||
|
||||
// GetPrefetchOrderCapacity 获取当前可用订单容量
|
||||
func (s *sCamelOil) GetPrefetchOrderCapacity(ctx context.Context) (capacity int, err error) {
|
||||
m := dao.V1CamelOilPrefetchOrder.Ctx(ctx).DB(config.GetDatabaseV1())
|
||||
|
||||
count, err := m.Where(dao.V1CamelOilPrefetchOrder.Columns().Status, consts.CamelOilPrefetchOrderStatusPending).Count()
|
||||
if err != nil {
|
||||
return 0, gerror.Wrap(err, "查询预拉取订单库存失败")
|
||||
}
|
||||
|
||||
return count, nil
|
||||
}
|
||||
|
||||
// PrefetchOrderConcurrently 使用所有可用账号并发拉取订单,直到获取到可用订单为止
|
||||
func (s *sCamelOil) PrefetchOrderConcurrently(ctx context.Context, amount float64) (result *model.PrefetchOrderResult, err error) {
|
||||
// 1. 获取所有在线账号
|
||||
m := dao.V1CamelOilAccount.Ctx(ctx).DB(config.GetDatabaseV1())
|
||||
var onlineAccounts []*entity.V1CamelOilAccount
|
||||
err = m.Where(dao.V1CamelOilAccount.Columns().Status, consts.CamelOilAccountStatusOnline).Scan(&onlineAccounts)
|
||||
if err != nil {
|
||||
return nil, gerror.Wrap(err, "查询在线账号失败")
|
||||
}
|
||||
|
||||
if len(onlineAccounts) == 0 {
|
||||
return nil, gerror.New("暂无在线账号可用")
|
||||
}
|
||||
|
||||
// 2. 使用控制并发量的信信道控制并发
|
||||
concurrencyLimit := min(len(onlineAccounts), consts.CamelOilPrefetchMaxConcurrency)
|
||||
|
||||
var (
|
||||
resultChan = make(chan *model.PrefetchOrderResult, 1)
|
||||
errorChan = make(chan error, len(onlineAccounts))
|
||||
semaphore = make(chan struct{}, concurrencyLimit) // 信信道控制并发量
|
||||
wg sync.WaitGroup
|
||||
mu sync.Mutex
|
||||
found = false
|
||||
)
|
||||
|
||||
// 3. 每个账号起一个协程尝试拉取,控制并发量
|
||||
for _, account := range onlineAccounts {
|
||||
acc := account // 避免闭包陷阱
|
||||
wg.Go(func() {
|
||||
// 获取信信道控制
|
||||
semaphore <- struct{}{}
|
||||
defer func() { <-semaphore }()
|
||||
|
||||
// 检查是否已经找到订单
|
||||
mu.Lock()
|
||||
if found {
|
||||
mu.Unlock()
|
||||
return
|
||||
}
|
||||
mu.Unlock()
|
||||
|
||||
// 拉取订单
|
||||
platformOrderId, payUrl, err2 := camel_oil_api.NewClient().CreateOrder(ctx, acc.Phone, acc.Token, amount)
|
||||
if err2 != nil {
|
||||
if err2.Error() == "auth_error" {
|
||||
_ = s.UpdateAccountStatus(ctx, acc.Id, consts.CamelOilAccountStatusInvalid, consts.CamelOilAccountChangeTypeInvalidate, "账号token失效")
|
||||
}
|
||||
errorChan <- err2
|
||||
return
|
||||
}
|
||||
|
||||
// 成功拉取订单,返回结果
|
||||
mu.Lock()
|
||||
if !found {
|
||||
found = true
|
||||
resultChan <- &model.PrefetchOrderResult{
|
||||
PlatformOrderNo: platformOrderId,
|
||||
AlipayUrl: payUrl,
|
||||
AccountId: acc.Id,
|
||||
AccountName: acc.AccountName,
|
||||
Amount: amount,
|
||||
}
|
||||
}
|
||||
mu.Unlock()
|
||||
})
|
||||
}
|
||||
|
||||
// 4. 等待所有协程完成並关闭Channel
|
||||
go func() {
|
||||
wg.Wait()
|
||||
close(resultChan)
|
||||
close(errorChan)
|
||||
}()
|
||||
|
||||
// 5. 等待第一个成功的订单
|
||||
if res := <-resultChan; res != nil {
|
||||
glog.Infof(ctx, "并发拉取订单成功,账号=%s, 金额=%.2f", res.AccountName, res.Amount)
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// 6. 所有账号都拉取失败
|
||||
var lastErr error
|
||||
for err = range errorChan {
|
||||
lastErr = err
|
||||
}
|
||||
|
||||
return nil, gerror.Wrap(lastErr, "所有账号拉取订单失败")
|
||||
}
|
||||
|
||||
// PrefetchOrder 拉取单个订单(用于单个账号)
|
||||
func (s *sCamelOil) PrefetchOrder(ctx context.Context, account *entity.V1CamelOilAccount, amount float64) (prefetchId int64, err error) {
|
||||
// 1. 从骆驼平台拉取订单
|
||||
platformOrderId, payUrl, err := camel_oil_api.NewClient().CreateOrder(ctx, account.Phone, account.Token, amount)
|
||||
if err != nil {
|
||||
if err.Error() == "auth_error" {
|
||||
_ = s.UpdateAccountStatus(ctx, account.Id, consts.CamelOilAccountStatusInvalid, consts.CamelOilAccountChangeTypeInvalidate, "账号token失效")
|
||||
return 0, gerror.Wrap(err, "账号token失效,拉取订单失败")
|
||||
}
|
||||
return 0, gerror.Wrap(err, "从骆驼平台拉取订单失败")
|
||||
}
|
||||
|
||||
// 2. 计算过期时间
|
||||
expireAt := gtime.Now().AddDate(0, 0, 1) // 24小时后过期
|
||||
|
||||
// 3. 保存预拉取订单记录
|
||||
result, err := dao.V1CamelOilPrefetchOrder.Ctx(ctx).DB(config.GetDatabaseV1()).Insert(&do.V1CamelOilPrefetchOrder{
|
||||
AccountId: account.Id,
|
||||
AccountName: account.AccountName,
|
||||
Amount: decimal.NewFromFloat(amount),
|
||||
PlatformOrderNo: platformOrderId,
|
||||
AlipayUrl: payUrl,
|
||||
Status: int(consts.CamelOilPrefetchOrderStatusPending), // 待匹配
|
||||
ExpireAt: expireAt,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return 0, gerror.Wrap(err, "保存预拉取订单失败")
|
||||
}
|
||||
|
||||
prefetchId, err = result.LastInsertId()
|
||||
if err != nil {
|
||||
return 0, gerror.Wrap(err, "获取预拉取订单ID失败")
|
||||
}
|
||||
|
||||
// 4. 记录预拉取订单历史
|
||||
_ = s.RecordPrefetchOrderHistory(ctx, prefetchId, consts.CamelOilPrefetchOrderChangeTypeFetch,
|
||||
account.Id, account.AccountName, fmt.Sprintf("从骆驼平台拉取订单,平台订单号: %s", platformOrderId))
|
||||
|
||||
glog.Infof(ctx, "预拉取订单创建成功,ID=%d, 账号=%s, 平台订单号=%s", prefetchId, account.Phone, platformOrderId)
|
||||
|
||||
return prefetchId, nil
|
||||
}
|
||||
|
||||
// ConcurrentPrefetchOrders 使用多个账号并发拉取订单
|
||||
func (s *sCamelOil) ConcurrentPrefetchOrders(ctx context.Context, amount float64, targetCount int) (successCount int, err error) {
|
||||
// 1. 获取所有在线账号
|
||||
m := dao.V1CamelOilAccount.Ctx(ctx).DB(config.GetDatabaseV1())
|
||||
var onlineAccounts []*entity.V1CamelOilAccount
|
||||
|
||||
err = m.Where(dao.V1CamelOilAccount.Columns().Status, consts.CamelOilAccountStatusOnline).Scan(&onlineAccounts)
|
||||
if err != nil {
|
||||
return 0, gerror.Wrap(err, "查询在线账号失败")
|
||||
}
|
||||
|
||||
if len(onlineAccounts) == 0 {
|
||||
return 0, gerror.New("暂无在线账号可用于拉取订单")
|
||||
}
|
||||
|
||||
// 2. 使用协程池并发拉取
|
||||
concurrencyLimit := consts.CamelOilPrefetchMaxConcurrency
|
||||
if len(onlineAccounts) < concurrencyLimit {
|
||||
concurrencyLimit = len(onlineAccounts)
|
||||
}
|
||||
|
||||
var (
|
||||
wg sync.WaitGroup
|
||||
semaphore = make(chan struct{}, concurrencyLimit)
|
||||
successCounter int32
|
||||
accountIndex int32
|
||||
accountMutex sync.Mutex
|
||||
)
|
||||
|
||||
targetRemaining := targetCount
|
||||
accountIndex = 0
|
||||
|
||||
for targetRemaining > 0 {
|
||||
account := onlineAccounts[int(atomic.LoadInt32(&accountIndex))%len(onlineAccounts)]
|
||||
atomic.AddInt32(&accountIndex, 1)
|
||||
|
||||
semaphore <- struct{}{} // 获取信号量
|
||||
|
||||
wg.Go(func() {
|
||||
defer wg.Done()
|
||||
defer func() { <-semaphore }() // 释放信号量
|
||||
|
||||
// 为了提高效率,每个账号可以拉取多单
|
||||
for i := 0; i < 2 && targetRemaining > 0; i++ {
|
||||
_, err := s.PrefetchOrder(ctx, account, amount)
|
||||
if err != nil {
|
||||
glog.Warningf(ctx, "账号%s拉取订单失败: %v", account.Phone, err)
|
||||
continue
|
||||
}
|
||||
|
||||
atomic.AddInt32(&successCounter, 1)
|
||||
accountMutex.Lock()
|
||||
targetRemaining--
|
||||
accountMutex.Unlock()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
successCount = int(atomic.LoadInt32(&successCounter))
|
||||
glog.Infof(ctx, "并发拉取订单完成,目标: %d, 成功: %d", targetCount, successCount)
|
||||
|
||||
return successCount, nil
|
||||
}
|
||||
|
||||
// SupplementPrefetchOrders 补充预拉取订单,当库存不足时调用
|
||||
func (s *sCamelOil) SupplementPrefetchOrders(ctx context.Context) (supplementedCount int, err error) {
|
||||
gmlock.Lock(consts.CamelOilPrefetchTaskLockKey)
|
||||
defer gmlock.Unlock(consts.CamelOilPrefetchTaskLockKey)
|
||||
|
||||
// 1. 获取当前库存
|
||||
capacity, err := s.GetPrefetchOrderCapacity(ctx)
|
||||
if err != nil {
|
||||
return 0, gerror.Wrap(err, "获取预拉取订单库存失败")
|
||||
}
|
||||
|
||||
glog.Infof(ctx, "当前预拉取订单库存: %d", capacity)
|
||||
|
||||
// 2. 如果库存充足,无需补充
|
||||
if capacity >= consts.CamelOilPrefetchOrderMinCapacity {
|
||||
glog.Infof(ctx, "预拉取订单库存充足 (%d >= %d),无需补充", capacity, consts.CamelOilPrefetchOrderMinCapacity)
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// 3. 计算需要补充的数量
|
||||
needCount := consts.CamelOilPrefetchOrderTargetCapacity - capacity
|
||||
glog.Infof(ctx, "预拉取订单库存不足,需要补充 %d 单,金额: 100元", needCount)
|
||||
|
||||
// 4. 并发拉取订单
|
||||
successCount, err := s.ConcurrentPrefetchOrders(ctx, 100, needCount)
|
||||
if err != nil {
|
||||
return 0, gerror.Wrap(err, "并发拉取订单失败")
|
||||
}
|
||||
|
||||
return successCount, nil
|
||||
}
|
||||
|
||||
// MatchPrefetchOrder 将预拉取订单与用户订单进行匹配
|
||||
func (s *sCamelOil) MatchPrefetchOrder(ctx context.Context, orderId int64) (prefetchId 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. 查询待匹配的预拉取订单(同金额)
|
||||
var prefetchOrder *entity.V1CamelOilPrefetchOrder
|
||||
err = dao.V1CamelOilPrefetchOrder.Ctx(ctx).DB(config.GetDatabaseV1()).
|
||||
Where(dao.V1CamelOilPrefetchOrder.Columns().Status, consts.CamelOilPrefetchOrderStatusPending).
|
||||
Where(dao.V1CamelOilPrefetchOrder.Columns().Amount, order.Amount).
|
||||
Where(dao.V1CamelOilPrefetchOrder.Columns().ExpireAt+">?", gtime.Now()). // 未过期
|
||||
OrderAsc(dao.V1CamelOilPrefetchOrder.Columns().CreatedAt). // 优先选择最早的
|
||||
Scan(&prefetchOrder)
|
||||
|
||||
if err != nil {
|
||||
return 0, gerror.Wrap(err, "查询预拉取订单失败")
|
||||
}
|
||||
|
||||
if prefetchOrder == nil {
|
||||
return 0, gerror.New("暂无匹配的预拉取订单,请稍后重试")
|
||||
}
|
||||
|
||||
// 3. 使用事务更新预拉取订单和用户订单
|
||||
err = dao.V1CamelOilPrefetchOrder.Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
|
||||
// 3.1 更新预拉取订单:标记为已匹配
|
||||
_, err = dao.V1CamelOilPrefetchOrder.Ctx(ctx).DB(config.GetDatabaseV1()).
|
||||
Where(dao.V1CamelOilPrefetchOrder.Columns().Id, prefetchOrder.Id).
|
||||
Update(&do.V1CamelOilPrefetchOrder{
|
||||
Status: int(consts.CamelOilPrefetchOrderStatusMatched),
|
||||
OrderId: orderId,
|
||||
MatchedAt: gtime.Now(),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return gerror.Wrap(err, "更新预拉取订单状态失败")
|
||||
}
|
||||
|
||||
// 3.2 更新用户订单:填充支付链接和平台订单号
|
||||
_, err = dao.V1CamelOilOrder.Ctx(ctx).DB(config.GetDatabaseV1()).
|
||||
Where(dao.V1CamelOilOrder.Columns().Id, orderId).
|
||||
Update(&do.V1CamelOilOrder{
|
||||
PlatformOrderNo: prefetchOrder.PlatformOrderNo,
|
||||
AlipayUrl: prefetchOrder.AlipayUrl,
|
||||
AccountId: prefetchOrder.AccountId,
|
||||
AccountName: prefetchOrder.AccountName,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return gerror.Wrap(err, "更新用户订单失败")
|
||||
}
|
||||
|
||||
// 3.3 记录预拉取订单历史
|
||||
_ = s.RecordPrefetchOrderHistory(ctx, prefetchOrder.Id, consts.CamelOilPrefetchOrderChangeTypeMatch,
|
||||
prefetchOrder.AccountId, prefetchOrder.AccountName,
|
||||
fmt.Sprintf("与用户订单匹配,订单ID=%d", orderId))
|
||||
|
||||
// 3.4 记录用户订单历史
|
||||
_ = s.RecordOrderHistory(ctx, order.OrderNo, consts.CamelOilOrderChangeTypeGetPayUrl,
|
||||
fmt.Sprintf("预拉取订单ID=%d", prefetchOrder.Id),
|
||||
fmt.Sprintf("从预拉取订单获取支付链接,平台订单号=%s", prefetchOrder.PlatformOrderNo))
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
prefetchId = prefetchOrder.Id
|
||||
glog.Infof(ctx, "预拉取订单匹配成功,预拉取ID=%d, 用户订单ID=%d, 平台订单号=%s",
|
||||
prefetchId, orderId, prefetchOrder.PlatformOrderNo)
|
||||
|
||||
return prefetchId, nil
|
||||
}
|
||||
|
||||
// RecordPrefetchOrderHistory 记录预拉取订单历史
|
||||
func (s *sCamelOil) RecordPrefetchOrderHistory(ctx context.Context, prefetchId int64,
|
||||
changeType consts.CamelOilPrefetchOrderChangeType, accountId int64, accountName, remark string) error {
|
||||
|
||||
_, err := dao.V1CamelOilPrefetchOrderHistory.Ctx(ctx).DB(config.GetDatabaseV1()).Insert(&do.V1CamelOilPrefetchOrderHistory{
|
||||
PrefetchId: prefetchId,
|
||||
ChangeType: string(changeType),
|
||||
AccountId: accountId,
|
||||
AccountName: accountName,
|
||||
Remark: remark,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
glog.Errorf(ctx, "记录预拉取订单历史失败: %v", err)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// CleanExpiredPrefetchOrders 清理过期的预拉取订单
|
||||
func (s *sCamelOil) CleanExpiredPrefetchOrders(ctx context.Context) (cleanedCount int, err error) {
|
||||
m := dao.V1CamelOilPrefetchOrder.Ctx(ctx).DB(config.GetDatabaseV1())
|
||||
|
||||
// 查询已过期的待匹配订单
|
||||
var expiredOrders []*entity.V1CamelOilPrefetchOrder
|
||||
err = m.Where(dao.V1CamelOilPrefetchOrder.Columns().Status, consts.CamelOilPrefetchOrderStatusPending).
|
||||
Where(dao.V1CamelOilPrefetchOrder.Columns().ExpireAt+"<?", gtime.Now()).
|
||||
Scan(&expiredOrders)
|
||||
|
||||
if err != nil {
|
||||
return 0, gerror.Wrap(err, "查询过期订单失败")
|
||||
}
|
||||
|
||||
if len(expiredOrders) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// 标记为已过期
|
||||
for _, order := range expiredOrders {
|
||||
_, err = m.Where(dao.V1CamelOilPrefetchOrder.Columns().Id, order.Id).
|
||||
Update(&do.V1CamelOilPrefetchOrder{
|
||||
Status: int(consts.CamelOilPrefetchOrderStatusExpired),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
glog.Warningf(ctx, "标记预拉取订单为过期失败,ID=%d: %v", order.Id, err)
|
||||
continue
|
||||
}
|
||||
|
||||
// 记录历史
|
||||
_ = s.RecordPrefetchOrderHistory(ctx, order.Id, consts.CamelOilPrefetchOrderChangeTypeExpire,
|
||||
order.AccountId, order.AccountName, "订单已过期,自动标记失效")
|
||||
|
||||
cleanedCount++
|
||||
}
|
||||
|
||||
glog.Infof(ctx, "清理过期预拉取订单完成,清理数量: %d", cleanedCount)
|
||||
return cleanedCount, nil
|
||||
}
|
||||
@@ -3,9 +3,6 @@ package card_apple_account
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gcfg"
|
||||
|
||||
"kami/api/commonApi"
|
||||
_ "kami/internal/logic/sys_user_payment"
|
||||
"kami/internal/model"
|
||||
@@ -14,9 +11,7 @@ import (
|
||||
_ "github.com/gogf/gf/contrib/nosql/redis/v2"
|
||||
|
||||
"github.com/gogf/gf/v2/os/gctx"
|
||||
"github.com/gogf/gf/v2/os/glog"
|
||||
"github.com/gogf/gf/v2/test/gtest"
|
||||
"github.com/shopspring/decimal"
|
||||
)
|
||||
|
||||
func Test_sAppleAccount_AddWalletAmount(t *testing.T) {
|
||||
@@ -38,21 +33,21 @@ func Test_sAppleAccount_IsUnscopedTodayRepeatedByName(t *testing.T) {
|
||||
a.IsUnscopedTodayRepeatedByName(gctx.New(), "123456")
|
||||
}
|
||||
|
||||
func Test_sAppleAccount_GetAccordingAccount(t *testing.T) {
|
||||
ctx := gctx.New()
|
||||
result, err := New().GetAccordingAccount(ctx, "123456", decimal.NewFromInt(100))
|
||||
if err != nil {
|
||||
glog.Error(ctx, err)
|
||||
}
|
||||
glog.Info(ctx, "当前账号", result)
|
||||
}
|
||||
// func Test_sAppleAccount_GetAccordingAccount(t *testing.T) {
|
||||
// ctx := gctx.New()
|
||||
// result, err := New().GetAccordingAccount(ctx, "123456", decimal.NewFromInt(100))
|
||||
// if err != nil {
|
||||
// glog.Error(ctx, err)
|
||||
// }
|
||||
// glog.Info(ctx, "当前账号", result)
|
||||
// }
|
||||
|
||||
func Test_sAppleAccount_GetAccordingAccountV2(t *testing.T) {
|
||||
g.Cfg().GetAdapter().(*gcfg.AdapterFile).SetFileName("/manifest/config/config.yaml")
|
||||
ctx := gctx.New()
|
||||
data, err := New().GetAccordingAccountV2(ctx, "123456", decimal.NewFromInt(100))
|
||||
if err != nil {
|
||||
glog.Error(ctx, err)
|
||||
}
|
||||
glog.Info(ctx, "当前账号", data)
|
||||
}
|
||||
// func Test_sAppleAccount_GetAccordingAccountV2(t *testing.T) {
|
||||
// g.Cfg().GetAdapter().(*gcfg.AdapterFile).SetFileName("/manifest/config/config.yaml")
|
||||
// ctx := gctx.New()
|
||||
// data, err := New().GetAccordingAccountV2(ctx, "123456", decimal.NewFromInt(100))
|
||||
// if err != nil {
|
||||
// glog.Error(ctx, err)
|
||||
// }
|
||||
// glog.Info(ctx, "当前账号", data)
|
||||
// }
|
||||
|
||||
33
internal/logic/card_apple_account/cron.go
Normal file
33
internal/logic/card_apple_account/cron.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package card_apple_account
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/gogf/gf/v2/os/glog"
|
||||
"kami/internal/consts"
|
||||
"kami/internal/dao"
|
||||
"kami/internal/model/entity"
|
||||
"kami/utility/config"
|
||||
"kami/utility/integration/apple"
|
||||
)
|
||||
|
||||
func (a *sAppleAccount) CronHealthCheck(ctx context.Context) {
|
||||
m := dao.V1CardAppleAccountInfo.Ctx(ctx).DB(config.GetDatabaseV1()).Where(dao.V1CardAppleAccountInfo.Columns().Status, consts.AppleAccountNormal)
|
||||
count, _ := m.Count()
|
||||
for i := 0; i < int(count); i++ {
|
||||
data := make(map[string]*entity.V1CardAppleAccountInfo)
|
||||
_ = m.Page(i+1, 100).Scan(&data)
|
||||
for _, accountInfo := range data {
|
||||
resp, err := apple.NewClient().Heartbeat(ctx, &apple.HeartBeatReq{
|
||||
Account: accountInfo.Account,
|
||||
Password: accountInfo.Password,
|
||||
OrderId: accountInfo.Id,
|
||||
})
|
||||
if err != nil {
|
||||
glog.Error(ctx, "请求错误", err)
|
||||
}
|
||||
if resp.Data.Status != 0 {
|
||||
_ = a.ModifyStatus(ctx, accountInfo.Id, consts.AppleAccountWrongPassword, nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
1
internal/logic/card_apple_order/cron.go
Normal file
1
internal/logic/card_apple_order/cron.go
Normal file
@@ -0,0 +1 @@
|
||||
package card_apple_order
|
||||
@@ -24,15 +24,15 @@ import (
|
||||
)
|
||||
|
||||
// handleRedeemResult 处理核销结果,根据状态码分类进行对应处理
|
||||
func (h *sAppleOrder) handleRedeemResult(ctx context.Context, orderEntity *entity.V1CardAppleRechargeInfo, accountInfo *entity.V1CardAppleAccountInfo) error {
|
||||
func (h *sAppleOrder) handleRedeemResult(ctx context.Context, orderInfo *entity.V1CardAppleRechargeInfo, accountInfo *entity.V1CardAppleAccountInfo) error {
|
||||
// 调用 Apple 服务进行核销(同步等待)
|
||||
redeemClient := apple.NewClient()
|
||||
// 准备推送请求
|
||||
redeemReq := &apple.RedeemReq{
|
||||
Account: accountInfo.Account,
|
||||
Password: accountInfo.Password,
|
||||
OrderId: orderEntity.OrderNo,
|
||||
RedemptionCode: orderEntity.CardPass,
|
||||
OrderId: orderInfo.OrderNo,
|
||||
RedemptionCode: orderInfo.CardPass,
|
||||
}
|
||||
resp, err := redeemClient.Redeem(ctx, redeemReq)
|
||||
if err != nil {
|
||||
@@ -43,27 +43,27 @@ func (h *sAppleOrder) handleRedeemResult(ctx context.Context, orderEntity *entit
|
||||
switch {
|
||||
// 1. 成功状态(CodeSuccess = 0)
|
||||
case resp.Code == apple.CodeSuccess:
|
||||
return h.handleRedeemSuccess(ctx, orderEntity, accountInfo, resp.Data.Amount, resp.Data.BalanceBefore, resp.Data.BalanceAfter)
|
||||
return h.handleRedeemSuccess(ctx, orderInfo, accountInfo, resp.Data.Amount, resp.Data.BalanceBefore, resp.Data.BalanceAfter)
|
||||
|
||||
// 2. 网络请求或系统资源错误(5000-5999)
|
||||
case resp.Code >= 5000 && resp.Code < 6000:
|
||||
return h.handleSystemError(ctx, orderEntity, accountInfo, resp.Code, resp.Message)
|
||||
return h.handleSystemError(ctx, orderInfo, accountInfo, resp.Code, resp.Message)
|
||||
|
||||
// 3. 苹果账户原因错误(8001-8005)
|
||||
case resp.Code >= 8001 && resp.Code <= 8005:
|
||||
return h.handleAccountError(ctx, orderEntity, accountInfo, resp.Code, resp.Message)
|
||||
return h.handleAccountError(ctx, orderInfo, accountInfo, resp.Code, resp.Message)
|
||||
|
||||
// 4. 充值限制错误(8010-8012)
|
||||
case resp.Code >= 8010 && resp.Code <= 8012:
|
||||
return h.handleRedeemLimitError(ctx, orderEntity, accountInfo, resp.Code, resp.Message)
|
||||
return h.handleRedeemLimitError(ctx, orderInfo, accountInfo, resp.Code, resp.Message)
|
||||
|
||||
// 5. 卡密错误(8013-8014)
|
||||
case resp.Code >= 8013 && resp.Code <= 8014:
|
||||
return h.handleCardCodeError(ctx, orderEntity, accountInfo, resp.Code, resp.Message)
|
||||
return h.handleCardCodeError(ctx, orderInfo, accountInfo, resp.Code, resp.Message)
|
||||
|
||||
// 未知错误
|
||||
default:
|
||||
return h.handleRedeemFailed(ctx, orderEntity, accountInfo, resp.Message)
|
||||
return h.handleRedeemFailed(ctx, orderInfo, accountInfo, resp.Message)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
10
internal/model/camel_oil.go
Normal file
10
internal/model/camel_oil.go
Normal file
@@ -0,0 +1,10 @@
|
||||
package model
|
||||
|
||||
// PrefetchOrderResult 预拉取订单结果
|
||||
type PrefetchOrderResult struct {
|
||||
PlatformOrderNo string // 骆驼平台订单号
|
||||
AlipayUrl string // 支付宝支付链接
|
||||
AccountId int64 // 账号ID
|
||||
AccountName string // 账号名称
|
||||
Amount float64 // 订单金额
|
||||
}
|
||||
@@ -13,7 +13,6 @@ import (
|
||||
type V1CamelOilAccountHistory struct {
|
||||
g.Meta `orm:"table:camel_oil_account_history, do:true"`
|
||||
Id any // 主键ID
|
||||
HistoryUuid any // 历史记录唯一标识
|
||||
AccountId any // 账号ID
|
||||
ChangeType any // 变更类型:create/login/offline/login_fail/pause/resume/invalidate/order_bind/order_complete/update/delete
|
||||
StatusBefore any // 变更前状态
|
||||
|
||||
@@ -20,6 +20,8 @@ type V1CamelOilOrder struct {
|
||||
PlatformOrderNo any // 骆驼平台订单号
|
||||
Amount any // 订单金额
|
||||
AlipayUrl any // 支付宝支付链接
|
||||
CardPassword any // 卡密
|
||||
CardNumber any // 卡号
|
||||
Status any // 状态:1待支付 2已支付 3支付超时 4下单失败
|
||||
PayStatus any // 支付状态:0未支付 1已支付 2超时
|
||||
NotifyStatus any // 回调状态:0未回调 1已回调 2回调失败
|
||||
|
||||
@@ -13,7 +13,6 @@ import (
|
||||
type V1CamelOilOrderHistory struct {
|
||||
g.Meta `orm:"table:camel_oil_order_history, do:true"`
|
||||
Id any // 主键ID
|
||||
HistoryUuid any // 历史记录唯一标识
|
||||
OrderNo any // 订单号
|
||||
ChangeType any // 变更类型:create/submit/get_pay_url/check_pay/paid/timeout/fail/callback_success/callback_fail
|
||||
AccountId any // 关联账号ID
|
||||
|
||||
30
internal/model/do/v_1_camel_oil_prefetch_order.go
Normal file
30
internal/model/do/v_1_camel_oil_prefetch_order.go
Normal file
@@ -0,0 +1,30 @@
|
||||
// =================================================================================
|
||||
// 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"
|
||||
)
|
||||
|
||||
// V1CamelOilPrefetchOrder is the golang structure of table camel_oil_prefetch_order for DAO operations like Where/Data.
|
||||
type V1CamelOilPrefetchOrder struct {
|
||||
g.Meta `orm:"table:camel_oil_prefetch_order, do:true"`
|
||||
Id any // 主键ID
|
||||
AccountId any // 拉取时使用的账号ID
|
||||
AccountName any // 账号名称
|
||||
Amount any // 预拉取订单金额
|
||||
PlatformOrderNo any // 骆驼平台订单号
|
||||
AlipayUrl any // 支付宝支付链接
|
||||
Status any // 预拉取订单状态:1待匹配 2已匹配 3已过期 4已失效
|
||||
OrderId any // 匹配后的订单ID(关联camel_oil_order表)
|
||||
MatchedAt *gtime.Time // 匹配时间
|
||||
ExpireAt *gtime.Time // 预拉取订单过期时间(通常为24小时后)
|
||||
FailureReason any // 失败原因
|
||||
Remark any // 备注信息
|
||||
CreatedAt *gtime.Time // 创建时间
|
||||
UpdatedAt *gtime.Time // 更新时间
|
||||
DeletedAt *gtime.Time // 删除时间(软删除)
|
||||
}
|
||||
25
internal/model/do/v_1_camel_oil_prefetch_order_history.go
Normal file
25
internal/model/do/v_1_camel_oil_prefetch_order_history.go
Normal file
@@ -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"
|
||||
)
|
||||
|
||||
// V1CamelOilPrefetchOrderHistory is the golang structure of table camel_oil_prefetch_order_history for DAO operations like Where/Data.
|
||||
type V1CamelOilPrefetchOrderHistory struct {
|
||||
g.Meta `orm:"table:camel_oil_prefetch_order_history, do:true"`
|
||||
Id any // 主键ID
|
||||
PrefetchId any // 预拉取订单ID
|
||||
ChangeType any // 变更类型:create/fetch/match/expire/invalidate
|
||||
AccountId any // 关联账号ID
|
||||
AccountName any // 账号名称
|
||||
RawData any // 原始响应数据
|
||||
Remark any // 备注
|
||||
CreatedAt *gtime.Time // 创建时间
|
||||
UpdatedAt *gtime.Time // 更新时间
|
||||
DeletedAt *gtime.Time // 删除时间(软删除)
|
||||
}
|
||||
@@ -11,7 +11,6 @@ import (
|
||||
// V1CamelOilAccountHistory is the golang structure for table v1camel_oil_account_history.
|
||||
type V1CamelOilAccountHistory struct {
|
||||
Id int64 `json:"id" orm:"id" description:"主键ID"`
|
||||
HistoryUuid string `json:"historyUuid" orm:"history_uuid" description:"历史记录唯一标识"`
|
||||
AccountId int64 `json:"accountId" orm:"account_id" description:"账号ID"`
|
||||
ChangeType string `json:"changeType" orm:"change_type" description:"变更类型:create/login/offline/login_fail/pause/resume/invalidate/order_bind/order_complete/update/delete"`
|
||||
StatusBefore int `json:"statusBefore" orm:"status_before" description:"变更前状态"`
|
||||
|
||||
@@ -19,6 +19,8 @@ type V1CamelOilOrder struct {
|
||||
PlatformOrderNo string `json:"platformOrderNo" orm:"platform_order_no" description:"骆驼平台订单号"`
|
||||
Amount decimal.Decimal `json:"amount" orm:"amount" description:"订单金额"`
|
||||
AlipayUrl string `json:"alipayUrl" orm:"alipay_url" description:"支付宝支付链接"`
|
||||
CardPassword string `json:"cardPassword" orm:"card_password" description:"卡密"`
|
||||
CardNumber string `json:"cardNumber" orm:"card_number" description:"卡号"`
|
||||
Status int `json:"status" orm:"status" description:"状态:1待支付 2已支付 3支付超时 4下单失败"`
|
||||
PayStatus int `json:"payStatus" orm:"pay_status" description:"支付状态:0未支付 1已支付 2超时"`
|
||||
NotifyStatus int `json:"notifyStatus" orm:"notify_status" description:"回调状态:0未回调 1已回调 2回调失败"`
|
||||
|
||||
@@ -11,7 +11,6 @@ import (
|
||||
// V1CamelOilOrderHistory is the golang structure for table v1camel_oil_order_history.
|
||||
type V1CamelOilOrderHistory struct {
|
||||
Id int64 `json:"id" orm:"id" description:"主键ID"`
|
||||
HistoryUuid string `json:"historyUuid" orm:"history_uuid" description:"历史记录唯一标识"`
|
||||
OrderNo string `json:"orderNo" orm:"order_no" description:"订单号"`
|
||||
ChangeType string `json:"changeType" orm:"change_type" description:"变更类型:create/submit/get_pay_url/check_pay/paid/timeout/fail/callback_success/callback_fail"`
|
||||
AccountId int64 `json:"accountId" orm:"account_id" description:"关联账号ID"`
|
||||
|
||||
29
internal/model/entity/v_1_camel_oil_prefetch_order.go
Normal file
29
internal/model/entity/v_1_camel_oil_prefetch_order.go
Normal file
@@ -0,0 +1,29 @@
|
||||
// =================================================================================
|
||||
// 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"
|
||||
)
|
||||
|
||||
// V1CamelOilPrefetchOrder is the golang structure for table v1camel_oil_prefetch_order.
|
||||
type V1CamelOilPrefetchOrder struct {
|
||||
Id int64 `json:"id" orm:"id" description:"主键ID"`
|
||||
AccountId int64 `json:"accountId" orm:"account_id" description:"拉取时使用的账号ID"`
|
||||
AccountName string `json:"accountName" orm:"account_name" description:"账号名称"`
|
||||
Amount decimal.Decimal `json:"amount" orm:"amount" description:"预拉取订单金额"`
|
||||
PlatformOrderNo string `json:"platformOrderNo" orm:"platform_order_no" description:"骆驼平台订单号"`
|
||||
AlipayUrl string `json:"alipayUrl" orm:"alipay_url" description:"支付宝支付链接"`
|
||||
Status int `json:"status" orm:"status" description:"预拉取订单状态:1待匹配 2已匹配 3已过期 4已失效"`
|
||||
OrderId int64 `json:"orderId" orm:"order_id" description:"匹配后的订单ID(关联camel_oil_order表)"`
|
||||
MatchedAt *gtime.Time `json:"matchedAt" orm:"matched_at" description:"匹配时间"`
|
||||
ExpireAt *gtime.Time `json:"expireAt" orm:"expire_at" description:"预拉取订单过期时间(通常为24小时后)"`
|
||||
FailureReason string `json:"failureReason" orm:"failure_reason" 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:"删除时间(软删除)"`
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
// =================================================================================
|
||||
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
|
||||
// =================================================================================
|
||||
|
||||
package entity
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
)
|
||||
|
||||
// V1CamelOilPrefetchOrderHistory is the golang structure for table v1camel_oil_prefetch_order_history.
|
||||
type V1CamelOilPrefetchOrderHistory struct {
|
||||
Id int64 `json:"id" orm:"id" description:"主键ID"`
|
||||
PrefetchId int64 `json:"prefetchId" orm:"prefetch_id" description:"预拉取订单ID"`
|
||||
ChangeType string `json:"changeType" orm:"change_type" description:"变更类型:create/fetch/match/expire/invalidate"`
|
||||
AccountId int64 `json:"accountId" orm:"account_id" description:"关联账号ID"`
|
||||
AccountName string `json:"accountName" orm:"account_name" description:"账号名称"`
|
||||
RawData string `json:"rawData" orm:"raw_data" 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:"删除时间(软删除)"`
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"context"
|
||||
v1 "kami/api/camel_oil/v1"
|
||||
"kami/internal/consts"
|
||||
"kami/internal/model"
|
||||
"kami/internal/model/entity"
|
||||
)
|
||||
|
||||
@@ -35,9 +36,6 @@ type (
|
||||
// GetAvailableOrderCapacity 获取当前可用订单容量
|
||||
// 计算所有在线账号的剩余可下单数之和
|
||||
GetAvailableOrderCapacity(ctx context.Context) (capacity int, err error)
|
||||
// CheckAndTriggerAccountLogin 检查容量并触发账号登录
|
||||
// 如果可用订单容量<50,触发账号登录任务
|
||||
CheckAndTriggerAccountLogin(ctx context.Context) (err error)
|
||||
// GetAccountPoolStatus 获取账号池状态统计
|
||||
GetAccountPoolStatus(ctx context.Context) (status map[string]interface{}, err error)
|
||||
// GetAccountHistory 获取账号历史记录
|
||||
@@ -46,10 +44,7 @@ type (
|
||||
// 注意:当前使用假数据,实际应对接骆驼加油平台和接码平台
|
||||
LoginAccount(ctx context.Context) (err error)
|
||||
// BatchLoginAccounts 批量登录账号
|
||||
BatchLoginAccounts(ctx context.Context, count int) (successCount int, err error)
|
||||
// CheckAndLoginAccounts 检查容量并登录账号
|
||||
// 根据当前可用订单容量,决定是否需要登录新账号
|
||||
CheckAndLoginAccounts(ctx context.Context) (err error)
|
||||
BatchLoginAccounts(ctx context.Context, count int64) (successCount int64, err error)
|
||||
// GetAvailableAccount 获取可用账号(按last_used_at轮询)
|
||||
// 选择条件:
|
||||
// 1. status=2(在线)
|
||||
@@ -59,8 +54,12 @@ type (
|
||||
GetAvailableAccount(ctx context.Context) (account *entity.V1CamelOilAccount, err error)
|
||||
// GetAccountStatistics 获取账号统计信息
|
||||
GetAccountStatistics(ctx context.Context, req *v1.AccountStatisticsReq) (res *v1.AccountStatisticsRes, err error)
|
||||
// CronAccountLoginTask 账号登录任务 - 由cron调度器定期调用
|
||||
CronAccountLoginTask(ctx context.Context) error
|
||||
// CronAccountPrefetchTask 账户预拉取定时任务 - 由cron调度器定期调用
|
||||
// 流程:并发拉取账户到指定数量
|
||||
CronAccountPrefetchTask(ctx context.Context) error
|
||||
// CronPrefetchOrderSupplementTask 预拉取订单补充定时任务 - 由cron调度器定期调用
|
||||
// 流程:检查预拉取订单库存,不足时补充
|
||||
CronPrefetchOrderSupplementTask(ctx context.Context) error
|
||||
// CronOrderPaymentCheckTask 订单支付状态检测任务 - 由cron调度器定期调用
|
||||
CronOrderPaymentCheckTask(ctx context.Context) error
|
||||
// CronAccountDailyResetTask 账号日重置任务 - 由cron调度器在每日00:05调用
|
||||
@@ -68,6 +67,8 @@ type (
|
||||
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)
|
||||
// TriggerOrderCallback 触发订单回调
|
||||
TriggerOrderCallback(ctx context.Context, req *v1.OrderCallbackReq) (res *v1.OrderCallbackRes, err error)
|
||||
// ProcessPendingCallbacks 处理待回调订单(定时任务使用)
|
||||
@@ -75,13 +76,29 @@ type (
|
||||
// GetOrderHistory 获取订单历史记录
|
||||
GetOrderHistory(ctx context.Context, req *v1.OrderHistoryReq) (res *v1.OrderHistoryRes, err error)
|
||||
// RecordOrderHistory 记录订单历史
|
||||
RecordOrderHistory(ctx context.Context, orderNo string, changeType string, rawData string, remark string) error
|
||||
RecordOrderHistory(ctx context.Context, orderNo string, changeType consts.CamelOilOrderChangeType, rawData string, remark string) error
|
||||
// GetAccountOrders 查询账号关联订单
|
||||
GetAccountOrders(ctx context.Context, req *v1.AccountOrderListReq) (res *v1.AccountOrderListRes, err error)
|
||||
// ListOrder 查询订单列表
|
||||
ListOrder(ctx context.Context, req *v1.ListOrderReq) (res *v1.ListOrderRes, err error)
|
||||
// OrderDetail 查询订单详情
|
||||
OrderDetail(ctx context.Context, req *v1.OrderDetailReq) (res *v1.OrderDetailRes, err error)
|
||||
// GetPrefetchOrderCapacity 获取当前可用订单容量
|
||||
GetPrefetchOrderCapacity(ctx context.Context) (capacity int, err error)
|
||||
// PrefetchOrderConcurrently 使用所有可用账号并发拉取订单,直到获取到可用订单为止
|
||||
PrefetchOrderConcurrently(ctx context.Context, amount float64) (result *model.PrefetchOrderResult, err error)
|
||||
// PrefetchOrder 拉取单个订单(用于单个账号)
|
||||
PrefetchOrder(ctx context.Context, account *entity.V1CamelOilAccount, amount float64) (prefetchId int64, err error)
|
||||
// ConcurrentPrefetchOrders 使用多个账号并发拉取订单
|
||||
ConcurrentPrefetchOrders(ctx context.Context, amount float64, targetCount int) (successCount int, err error)
|
||||
// SupplementPrefetchOrders 补充预拉取订单,当库存不足时调用
|
||||
SupplementPrefetchOrders(ctx context.Context) (supplementedCount int, err error)
|
||||
// MatchPrefetchOrder 将预拉取订单与用户订单进行匹配
|
||||
MatchPrefetchOrder(ctx context.Context, orderId int64) (prefetchId int64, err error)
|
||||
// RecordPrefetchOrderHistory 记录预拉取订单历史
|
||||
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)
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -58,6 +58,7 @@ type (
|
||||
GetExcludeAccounts(ctx context.Context, machineId string) (m []*model.AccountIdInfo, err error)
|
||||
// ClearCurrentTargetAccount 清空缓存
|
||||
ClearCurrentTargetAccount(ctx context.Context, machineId string) (err error)
|
||||
CronHealthCheck(ctx context.Context)
|
||||
// GetHistoryOneByOrderNo 根据ID获取账号历史
|
||||
GetHistoryOneByOrderNo(ctx context.Context, accountId string, orderNo string) (data *entity.V1CardAppleAccountInfoHistory, err error)
|
||||
// GetAccordingAccountV3 账户分配算法,适合多线程
|
||||
|
||||
@@ -43,6 +43,8 @@ CREATE TABLE `camel_oil_order` (
|
||||
`platform_order_no` varchar(128) DEFAULT NULL COMMENT '骆驼平台订单号',
|
||||
`amount` decimal(10,2) NOT NULL COMMENT '订单金额',
|
||||
`alipay_url` text DEFAULT NULL COMMENT '支付宝支付链接',
|
||||
`card_password` varchar(256) DEFAULT NULL COMMENT '卡密',
|
||||
`card_number` varchar(256) DEFAULT NULL COMMENT '卡号',
|
||||
`status` tinyint NOT NULL DEFAULT 1 COMMENT '状态:1待支付 2已支付 3支付超时 4下单失败',
|
||||
`pay_status` tinyint NOT NULL DEFAULT 0 COMMENT '支付状态:0未支付 1已支付 2超时',
|
||||
`notify_status` tinyint NOT NULL DEFAULT 0 COMMENT '回调状态:0未回调 1已回调 2回调失败',
|
||||
@@ -65,6 +67,8 @@ CREATE TABLE `camel_oil_order` (
|
||||
KEY `idx_notify_status` (`notify_status`),
|
||||
KEY `idx_created_at` (`created_at`),
|
||||
KEY `idx_account_status` (`account_id`, `status`),
|
||||
KEY `idx_card_password` (`card_password`),
|
||||
KEY `idx_card_number` (`card_number`),
|
||||
KEY `idx_deleted_at` (`deleted_at`)
|
||||
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '骆驼加油订单表';
|
||||
|
||||
@@ -73,7 +77,6 @@ DROP TABLE IF EXISTS `camel_oil_account_history`;
|
||||
|
||||
CREATE TABLE `camel_oil_account_history` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`history_uuid` varchar(36) NOT NULL COMMENT '历史记录唯一标识',
|
||||
`account_id` bigint NOT NULL COMMENT '账号ID',
|
||||
`change_type` varchar(32) NOT NULL COMMENT '变更类型:create/login/offline/login_fail/pause/resume/invalidate/order_bind/order_complete/update/delete',
|
||||
`status_before` tinyint DEFAULT NULL COMMENT '变更前状态',
|
||||
@@ -84,7 +87,6 @@ CREATE TABLE `camel_oil_account_history` (
|
||||
`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_history_uuid` (`history_uuid`),
|
||||
KEY `idx_account_id` (`account_id`),
|
||||
CONSTRAINT `fk_camel_oil_account_history_account_id` FOREIGN KEY (`account_id`) REFERENCES `camel_oil_account` (`id`) ON DELETE CASCADE,
|
||||
KEY `idx_change_type` (`change_type`),
|
||||
@@ -97,7 +99,6 @@ DROP TABLE IF EXISTS `camel_oil_order_history`;
|
||||
|
||||
CREATE TABLE `camel_oil_order_history` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`history_uuid` varchar(36) NOT NULL COMMENT '历史记录唯一标识',
|
||||
`order_no` varchar(64) NOT NULL COMMENT '订单号',
|
||||
`change_type` varchar(32) NOT NULL COMMENT '变更类型:create/submit/get_pay_url/check_pay/paid/timeout/fail/callback_success/callback_fail',
|
||||
`account_id` bigint DEFAULT NULL COMMENT '关联账号ID',
|
||||
@@ -108,7 +109,6 @@ CREATE TABLE `camel_oil_order_history` (
|
||||
`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_history_uuid` (`history_uuid`),
|
||||
KEY `idx_order_no` (`order_no`),
|
||||
KEY `idx_account_id` (`account_id`),
|
||||
CONSTRAINT `fk_camel_oil_order_history_account_id` FOREIGN KEY (`account_id`) REFERENCES `camel_oil_account` (`id`) ON DELETE SET NULL,
|
||||
@@ -116,3 +116,58 @@ CREATE TABLE `camel_oil_order_history` (
|
||||
KEY `idx_created_at` (`created_at`),
|
||||
KEY `idx_deleted_at` (`deleted_at`)
|
||||
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '骆驼加油订单历史表';
|
||||
|
||||
-- 5. 骆驼加油预拉取订单表
|
||||
DROP TABLE IF EXISTS `camel_oil_prefetch_order`;
|
||||
|
||||
CREATE TABLE `camel_oil_prefetch_order` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`account_id` bigint NOT NULL COMMENT '拉取时使用的账号ID',
|
||||
`account_name` varchar(128) DEFAULT NULL COMMENT '账号名称',
|
||||
`amount` decimal(10,2) NOT NULL COMMENT '预拉取订单金额',
|
||||
`platform_order_no` varchar(128) DEFAULT NULL COMMENT '骆驼平台订单号',
|
||||
`alipay_url` text DEFAULT NULL COMMENT '支付宝支付链接',
|
||||
`status` tinyint NOT NULL DEFAULT 1 COMMENT '预拉取订单状态:1待匹配 2已匹配 3已过期 4已失效',
|
||||
`order_id` bigint DEFAULT NULL COMMENT '匹配后的订单ID(关联camel_oil_order表)',
|
||||
`matched_at` datetime DEFAULT NULL COMMENT '匹配时间',
|
||||
`expire_at` datetime DEFAULT NULL COMMENT '预拉取订单过期时间(通常为24小时后)',
|
||||
`failure_reason` text 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`),
|
||||
KEY `idx_account_id` (`account_id`),
|
||||
CONSTRAINT `fk_camel_oil_prefetch_order_account_id` FOREIGN KEY (`account_id`) REFERENCES `camel_oil_account` (`id`) ON DELETE RESTRICT,
|
||||
KEY `idx_status` (`status`),
|
||||
KEY `idx_status_expire` (`status`, `expire_at`),
|
||||
KEY `idx_order_id` (`order_id`),
|
||||
CONSTRAINT `fk_camel_oil_prefetch_order_order_id` FOREIGN KEY (`order_id`) REFERENCES `camel_oil_order` (`id`) ON DELETE SET NULL,
|
||||
KEY `idx_platform_order_no` (`platform_order_no`),
|
||||
KEY `idx_created_at` (`created_at`),
|
||||
KEY `idx_deleted_at` (`deleted_at`)
|
||||
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '骆驼加油预拉取订单表';
|
||||
|
||||
-- 6. 骆驼加油预拉取订单历史表
|
||||
DROP TABLE IF EXISTS `camel_oil_prefetch_order_history`;
|
||||
|
||||
CREATE TABLE `camel_oil_prefetch_order_history` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`prefetch_id` bigint NOT NULL COMMENT '预拉取订单ID',
|
||||
`change_type` varchar(32) NOT NULL COMMENT '变更类型:create/fetch/match/expire/invalidate',
|
||||
`account_id` bigint DEFAULT NULL COMMENT '关联账号ID',
|
||||
`account_name` varchar(128) DEFAULT NULL COMMENT '账号名称',
|
||||
`raw_data` text 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`),
|
||||
KEY `idx_prefetch_id` (`prefetch_id`),
|
||||
CONSTRAINT `fk_camel_oil_prefetch_order_history_prefetch_id` FOREIGN KEY (`prefetch_id`) REFERENCES `camel_oil_prefetch_order` (`id`) ON DELETE CASCADE,
|
||||
KEY `idx_account_id` (`account_id`),
|
||||
CONSTRAINT `fk_camel_oil_prefetch_order_history_account_id` FOREIGN KEY (`account_id`) REFERENCES `camel_oil_account` (`id`) ON DELETE SET NULL,
|
||||
KEY `idx_change_type` (`change_type`),
|
||||
KEY `idx_created_at` (`created_at`),
|
||||
KEY `idx_deleted_at` (`deleted_at`)
|
||||
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '骆驼加油预拉取订单历史表';
|
||||
|
||||
@@ -12,6 +12,13 @@ import (
|
||||
|
||||
// Register 注册定时任务
|
||||
func Register(ctx context.Context) {
|
||||
//registerMainTasks(ctx)
|
||||
|
||||
// 骆驼加油模块定时任务
|
||||
registerCamelOilTasks(ctx)
|
||||
}
|
||||
|
||||
func registerMainTasks(ctx context.Context) {
|
||||
//每日0时执行
|
||||
_, _ = gcron.AddSingleton(ctx, "0 0 0 * * ?", func(ctx context.Context) {
|
||||
tracer := gtrace.NewTracer("每日0时定时任务")
|
||||
@@ -65,31 +72,24 @@ func Register(ctx context.Context) {
|
||||
glog.Error(ctx, "京东支付状态监控任务失败", err)
|
||||
}
|
||||
})
|
||||
|
||||
// 骆驼加油模块定时任务
|
||||
//registerCamelOilTasks(ctx)
|
||||
}
|
||||
|
||||
// registerCamelOilTasks 注册骆驼加油模块的定时任务
|
||||
func registerCamelOilTasks(ctx context.Context) {
|
||||
// 账号登录任务 - 每5分钟执行
|
||||
_, _ = gcron.AddSingleton(ctx, "@every 5m", func(ctx context.Context) {
|
||||
_ = service.CamelOil().CronAccountLoginTask(ctx)
|
||||
}, "CamelOilAccountLogin")
|
||||
// 账户预拉取任务 - 每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 5s", func(ctx context.Context) {
|
||||
_ = service.CamelOil().CronVerifyCodeCheckTask(ctx)
|
||||
}, "CamelOilVerifyCodeCheck")
|
||||
|
||||
// 订单支付状态检测任务 - 每1分钟执行
|
||||
_, _ = gcron.AddSingleton(ctx, "@every 10s", func(ctx context.Context) {
|
||||
_ = service.CamelOil().CronOrderPaymentCheckTask(ctx)
|
||||
}, "CamelOilOrderPaymentCheck")
|
||||
|
||||
// 账号日重置任务 - 每日00:00执行
|
||||
_, _ = gcron.AddSingleton(ctx, "0 0 * * *", func(ctx context.Context) {
|
||||
_ = service.CamelOil().CronAccountDailyResetTask(ctx)
|
||||
}, "CamelOilAccountDailyReset")
|
||||
//// 流程:检查预拉取订单库存,不足时补充
|
||||
//_, _ = gcron.AddSingleton(ctx, "@every 10s", func(ctx context.Context) {
|
||||
// _ = service.CamelOil().CronPrefetchOrderSupplementTask(ctx)
|
||||
//}, "CamelOilPrefetchOrderSupplement")
|
||||
|
||||
glog.Info(ctx, "骆驼加油模块定时任务注册完成")
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@ import (
|
||||
"errors"
|
||||
"github.com/gogf/gf/v2/net/gclient"
|
||||
"github.com/gogf/gf/v2/os/glog"
|
||||
"github.com/google/uuid"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
@@ -20,25 +22,23 @@ func NewClient() *Client {
|
||||
|
||||
func (c *Client) SendCaptcha(ctx context.Context, phone string) (bool, error) {
|
||||
req := struct {
|
||||
OpenId string `json:"openId"`
|
||||
Phone string `json:"phone"`
|
||||
CouponStatus string `json:"couponStatus"`
|
||||
Channel string `json:"channel"`
|
||||
Phone string `json:"phone"`
|
||||
Channel string `json:"channel"`
|
||||
}{
|
||||
OpenId: "app2511181557205741495",
|
||||
Phone: phone,
|
||||
CouponStatus: "unused",
|
||||
Channel: "app",
|
||||
Phone: phone,
|
||||
Channel: "app",
|
||||
}
|
||||
resp, err := c.Client.Post(ctx, "https://recharge3.bac365.com/camel_wechat_mini_oil_server/refueling/getUserCouponList", req)
|
||||
resp, err := c.Client.ContentJson().Post(ctx, "https://recharge3.bac365.com/camel_wechat_mini_oil_server/sendVerifyMessage", req)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
respStr := resp.ReadAllString()
|
||||
glog.Info(ctx, "获取信息", respStr)
|
||||
respStruct := struct {
|
||||
Code string `json:"code"`
|
||||
Message string `json:"message"`
|
||||
}{}
|
||||
err = json.Unmarshal(resp.ReadAll(), &respStruct)
|
||||
err = json.Unmarshal([]byte(respStr), &respStruct)
|
||||
return respStruct.Code == "success", err
|
||||
}
|
||||
|
||||
@@ -52,11 +52,12 @@ func (c *Client) LoginWithCaptcha(ctx context.Context, phone string, code string
|
||||
Codes: code,
|
||||
Channel: "app",
|
||||
}
|
||||
resp, err := c.Client.Post(ctx, "https://recharge3.bac365.com/camel_wechat_mini_oil_server/loginApp", req)
|
||||
resp, err := c.Client.ContentJson().Post(ctx, "https://recharge3.bac365.com/camel_wechat_mini_oil_server/loginApp", req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
glog.Info(ctx, "登录", req, resp.ReadAllString())
|
||||
respStr := resp.ReadAllString()
|
||||
glog.Info(ctx, "登录", req, respStr)
|
||||
respStruct := struct {
|
||||
LoginUser struct {
|
||||
UserIdApp string `json:"userIdApp"`
|
||||
@@ -65,18 +66,31 @@ func (c *Client) LoginWithCaptcha(ctx context.Context, phone string, code string
|
||||
LoginTime string `json:"loginTime"`
|
||||
ExpireTime string `json:"expireTime"`
|
||||
Ipaddr string `json:"ipaddr"`
|
||||
}
|
||||
} `json:"loginUser,omitempty"`
|
||||
Code string `json:"code"`
|
||||
Message string `json:"message"`
|
||||
Token string `json:"token"`
|
||||
Token string `json:"token,omitempty"`
|
||||
}{}
|
||||
err = json.Unmarshal(resp.ReadAll(), &respStruct)
|
||||
err = json.Unmarshal([]byte(respStr), &respStruct)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if respStruct.Code != "success" {
|
||||
return "", errors.New(respStruct.Message)
|
||||
}
|
||||
return respStruct.Token, err
|
||||
}
|
||||
|
||||
func (c *Client) getAuth(ctx context.Context, auth string) string {
|
||||
base64Private := "MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAIHSkp+Z0Lu+lZWr/wKcMT3EcWEIihKTg/jEOyKaczqG9hWL9UULJ1dFtIQNlpWRySsVZcJLoGTFdGam557lVzpY/tbN73KG9iVMBaKALLF52cgmyg0DRve4atc0OnkhTjv7Rf8B85UokdHCAM/5MgNcjXwqBGHohJ2LGC9yN2erAgMBAAECgYATfTeqww4daTaOkhQF4cnYonl83inQMRoSSe8wuiwLQMCHqounEk4VIW9AlcOh75FaKOuuV+kbx7K6SFskNPy7nGYfS22t2aM9E9Rt+JH+caEniYi5qAfb3gCIgsGExUNI6iuSM2p3/R542EDGc2FyfPPqyht+jR4CjLOLoXHfoQJBALwvF6uIOSW0Lxh7Lo/JsKpWJ1qffDvXWYag605L9JAyP0yO64woF60Tn+mGRzcaEhNDSEjinKQqPEJnxDUGYaECQQCwm1mQKD95MaeKWBiOVJZsdzL5aJsW42xyiu0ZwA7bZUgJyUskzXG0ubeIHK/czlJbev9ODubbMNJFcngX4N3LAkAJaxH0M80oZew1fXTHHYEKBWXS00iUdiK06jjcolCLJvikDEMdsKP+tYy7U00dJODitetYOn88eCCr8iWPwdIBAkBtUjzGt6NS6iHDyXSp5kKXMdIkAVS/flgLL2RFpFWOCcvmAuy5A1N3g97QKrHSBQWGC0UulJri4/3Fb25XmaKxAkBifs9dbUifeqZRNVh2Omck4xedb1FyQPLDicUycjYug3Vca0T/LRr80aX/NhbhtpSdwzF1ukiZ6W46O9DmGuNy" // 私钥 base64 或带 PEM
|
||||
base64Pub := "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDpwypBPN8r3Zwv3T0XUh1Ka2m2hUe3KBgIyH4fHfN/T1jsBWnbwotKEQdZfRva7mRYiz9YrTHoH/eUAuv+WYqPMubaiqpWOu0l+BzEX1kPGA98qRC06IF2Tk4Z5xAmQ8p8u3O5jxohYFkO2XlDvPU+W9SDZgSEBTe8p80LExgo6wIDAQAB" // 公钥 base64 或带 PEM
|
||||
authRes, _ := DecryptWithPrivateThenEncryptWithPublic(base64Private, base64Pub, auth)
|
||||
return authRes
|
||||
}
|
||||
|
||||
func (c *Client) CreateOrder(ctx context.Context, phone, token string, amount float64) (orderId string, payUrl string, err error) {
|
||||
//c.Client.SetHeader("Authorization", token)
|
||||
resp, err := c.Client.Post(ctx, "https://recharge3.bac365.com/camel_wechat_mini_oil_server/eCardMall/wechatCardGoods", struct {
|
||||
c.Client.SetHeader("Authorization", "Bearer "+c.getAuth(ctx, token))
|
||||
resp, err := c.Client.ContentJson().Post(ctx, "https://recharge3.bac365.com/camel_wechat_mini_oil_server/eCardMall/wechatCardGoods", struct {
|
||||
Channel string `json:"channel"`
|
||||
}{
|
||||
Channel: "app",
|
||||
@@ -109,22 +123,60 @@ func (c *Client) CreateOrder(ctx context.Context, phone, token string, amount fl
|
||||
if goodId == "" {
|
||||
return "", "", errors.New("当前金额不支持")
|
||||
}
|
||||
//遍历 100次
|
||||
for i := 0; i < 100; i++ {
|
||||
for range 100 {
|
||||
bodyStr := struct {
|
||||
OpenId string `json:"openId"`
|
||||
Phone string `json:"phone"`
|
||||
GoodId string `json:"goodId"`
|
||||
GoodNum int `json:"goodNum"`
|
||||
BindPhone string `json:"bindPhone"`
|
||||
PayType string `json:"payType"`
|
||||
ParamY float64 `json:"paramY"`
|
||||
ParamX float64 `json:"paramX"`
|
||||
Yanqian bool `json:"yanqian"`
|
||||
MobileOperatingPlatform string `json:"mobileOperatingPlatform"`
|
||||
SysVersion string `json:"sysVersion"`
|
||||
PlatformType string `json:"platformType"`
|
||||
NetWork string `json:"netWork"`
|
||||
Platform string `json:"platform"`
|
||||
Brand string `json:"brand"`
|
||||
DeviceId string `json:"deviceId"`
|
||||
}{
|
||||
OpenId: "app2511181557205741495",
|
||||
Phone: phone,
|
||||
GoodId: goodId,
|
||||
GoodNum: 1,
|
||||
BindPhone: phone,
|
||||
PayType: "appAli",
|
||||
ParamY: 26.996671,
|
||||
ParamX: 77.450347,
|
||||
Yanqian: true,
|
||||
MobileOperatingPlatform: "iOS",
|
||||
SysVersion: "iOS 15.7",
|
||||
PlatformType: "iPad Pro (12.9-inch) (3rd generation)",
|
||||
NetWork: "unknown",
|
||||
Platform: "ios",
|
||||
Brand: "apple",
|
||||
DeviceId: strings.Replace(uuid.NewString(), "-", "", -1),
|
||||
}
|
||||
pubkey := "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCkc6Xr/JhWEx/WPxG2q3VHLQ+FYk/oCmQ1y14B5j4xOJY+mAWoDOOti3sAXg0Kk662gWjWET1nLI6YED4wb9HWon1NAZn47lgc5ohIpEdU91Jao85X/kgkD3NvTTvhFicttepUOsrYUZN8rAQCE7AhzwGgKnCiIRY/kE8jOCCeZQIDAQAB"
|
||||
body, _ := json.Marshal(&bodyStr)
|
||||
bodyS, _ := EncryptWithPublicKey(pubkey, string(body))
|
||||
req := struct {
|
||||
BodyStr string `json:"bodyStr"`
|
||||
Channel string `json:"channel"`
|
||||
Yanqian bool `json:"yanqian"`
|
||||
}{
|
||||
BodyStr: "",
|
||||
BodyStr: bodyS,
|
||||
Channel: "app",
|
||||
Yanqian: true,
|
||||
}
|
||||
resp, err = c.Client.Post(ctx, "https://recharge3.bac365.com/camel_wechat_mini_oil_server/eCardMall/createShoppingOrder", req)
|
||||
resp, err = c.Client.ContentJson().Post(ctx, "https://recharge3.bac365.com/camel_wechat_mini_oil_server/eCardMall/createShoppingOrder", req)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
glog.Info(ctx, "登录", req, resp.ReadAllString())
|
||||
respStr := resp.ReadAllString()
|
||||
glog.Info(ctx, "登录", req, respStr)
|
||||
respStruct := struct {
|
||||
Code string `json:"code"`
|
||||
Message string `json:"message"`
|
||||
@@ -133,24 +185,103 @@ func (c *Client) CreateOrder(ctx context.Context, phone, token string, amount fl
|
||||
} `json:"orderRes,omitempty"`
|
||||
OrderId string `json:"orderid,omitempty"`
|
||||
}{}
|
||||
err = json.Unmarshal(resp.ReadAll(), &respStruct)
|
||||
err = json.Unmarshal([]byte(respStr), &respStruct)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
if respStruct.Code == "limit" {
|
||||
continue
|
||||
}
|
||||
if respStruct.Code == "auth_error" {
|
||||
return "", "", errors.New("auth_error")
|
||||
}
|
||||
return respStruct.OrderId, respStruct.OrderRes.Body, err
|
||||
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("创建订单超时")
|
||||
}
|
||||
|
||||
// QueryOrder 查询对应订单
|
||||
func (c *Client) QueryOrder(ctx context.Context, phone, token, orderId string) (status bool, err error) {
|
||||
//req := struct {
|
||||
// OrderId string `json:"orderId"`
|
||||
//}{
|
||||
// OrderId: orderId,
|
||||
//}
|
||||
return false, nil
|
||||
// 返回值说明:
|
||||
// - result != nil && err == nil: 查询成功,找到订单
|
||||
// - result == nil && err == nil: 查询成功,但未找到订单
|
||||
// - result == nil && err != nil: 查询失败,有错误
|
||||
func (c *Client) QueryOrder(ctx context.Context, phone, token, orderId string) (result *QueryResult, err error) {
|
||||
c.Client.SetHeader("Authorization", "Bearer "+c.getAuth(ctx, token))
|
||||
|
||||
// 最多查询10页,防止无限循环
|
||||
maxPages := 100
|
||||
pageNum := 1
|
||||
|
||||
for pageNum <= maxPages {
|
||||
reqBody := struct {
|
||||
OrderId string `json:"orderId"`
|
||||
Sign string `json:"sign"`
|
||||
Channel string `json:"channel"`
|
||||
Status string `json:"status"`
|
||||
PageNum int `json:"pageNum"`
|
||||
PageSize int `json:"pageSize"`
|
||||
OpenId string `json:"openId"`
|
||||
}{
|
||||
OrderId: orderId,
|
||||
Sign: Sign("app2511181557205741495"),
|
||||
Channel: "app",
|
||||
Status: "unused",
|
||||
PageNum: pageNum,
|
||||
PageSize: 50,
|
||||
OpenId: "app2511181557205741495",
|
||||
}
|
||||
|
||||
respData, err := c.Client.Post(ctx, "https://recharge3.bac365.com/camel_wechat_mini_oil_server/eCardMall/queryWechatUserECards", reqBody)
|
||||
if err != nil {
|
||||
glog.Errorf(ctx, "查询卡券失败,第%d页,错误: %v", pageNum, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
respStruct := struct {
|
||||
Total string `json:"total"`
|
||||
Code string `json:"code"`
|
||||
Cards []struct {
|
||||
RecordId string `json:"recordId"`
|
||||
Denomination float64 `json:"denomination"`
|
||||
CreateTime string `json:"createTime"`
|
||||
ExpireTime string `json:"expireTime"`
|
||||
Status string `json:"status"`
|
||||
OrderId string `json:"orderId"`
|
||||
ECardNo string `json:"ecardNo"`
|
||||
ECardCode string `json:"ecardCode"`
|
||||
} `json:"cards,omitempty"`
|
||||
}{}
|
||||
|
||||
err = json.Unmarshal(respData.ReadAll(), &respStruct)
|
||||
if err != nil {
|
||||
glog.Errorf(ctx, "解析响应失败,第%d页,错误: %v", pageNum, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 如果当前页没有卡券数据,说明已查询所有数据
|
||||
if len(respStruct.Cards) == 0 {
|
||||
glog.Debugf(ctx, "查询订单%s已完成,共查询%d页,未找到", orderId, pageNum-1)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// 在当前页查找目标订单
|
||||
for _, card := range respStruct.Cards {
|
||||
if card.OrderId == orderId {
|
||||
glog.Infof(ctx, "查询订单%s成功,金额: %.2f", orderId, card.Denomination)
|
||||
return &QueryResult{
|
||||
Balance: card.Denomination,
|
||||
CardNumber: card.ECardNo,
|
||||
CardPassword: card.ECardCode,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
// 当前页未找到,继续查询下一页
|
||||
pageNum++
|
||||
}
|
||||
|
||||
glog.Warningf(ctx, "查询订单%s超过最大页数%d,未找到", orderId, maxPages)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
8
utility/integration/camel_oil_api/api_test.go
Normal file
8
utility/integration/camel_oil_api/api_test.go
Normal file
@@ -0,0 +1,8 @@
|
||||
package camel_oil_api
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestClient_SendCaptcha(t *testing.T) {
|
||||
}
|
||||
207
utility/integration/camel_oil_api/encrypt.go
Normal file
207
utility/integration/camel_oil_api/encrypt.go
Normal file
@@ -0,0 +1,207 @@
|
||||
package camel_oil_api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/md5"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"github.com/duke-git/lancet/v2/convertor"
|
||||
)
|
||||
|
||||
// EncryptWithPublicKey encrypts the input JSON string using RSA public key with block processing for large data.
|
||||
// It uses PKCS1Padding and supports chunking based on key size minus 11 bytes for padding.
|
||||
func EncryptWithPublicKey(base64PublicKey, jsonString string) (string, error) {
|
||||
publicKey, err := parsePublicKey(base64PublicKey)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
data := []byte(jsonString)
|
||||
|
||||
// Calculate maximum plaintext block size: modulus size - 11 bytes for PKCS1Padding
|
||||
modulusSizeBytes := publicKey.N.BitLen() / 8
|
||||
maxBlockSize := modulusSizeBytes - 11
|
||||
|
||||
var buf bytes.Buffer
|
||||
|
||||
offset := 0
|
||||
for offset < len(data) {
|
||||
end := min(offset+maxBlockSize, len(data))
|
||||
|
||||
encryptedBlock, err := rsa.EncryptPKCS1v15(rand.Reader, publicKey, data[offset:end])
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Each encrypted block is always the size of the modulus
|
||||
buf.Write(encryptedBlock)
|
||||
offset = end
|
||||
}
|
||||
|
||||
return convertor.ToStdBase64(buf.Bytes()), nil
|
||||
}
|
||||
|
||||
// DecryptWithPrivateKey decrypts the base64-encoded ciphertext using RSA private key with block processing for large data.
|
||||
// It uses PKCS1Padding and follows the Java implementation: Math.min(blockSize, data.length - offset)
|
||||
func DecryptWithPrivateKey(base64PrivateKey, ciphertext string) (string, error) {
|
||||
privateKey, err := parsePrivateKey(base64PrivateKey)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Decode base64-encoded ciphertext
|
||||
cipherData, err := base64.StdEncoding.DecodeString(ciphertext)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// RSA modulus size: each encrypted block is exactly this size
|
||||
blockSize := privateKey.N.BitLen() / 8 // 1024-bit key = 128 bytes
|
||||
|
||||
var buf bytes.Buffer
|
||||
|
||||
offset := 0
|
||||
for offset < len(cipherData) {
|
||||
// Calculate current block size: min(blockSize, remaining data)
|
||||
currentBlockSize := blockSize
|
||||
if currentBlockSize > len(cipherData)-offset {
|
||||
currentBlockSize = len(cipherData) - offset
|
||||
}
|
||||
|
||||
decryptedBlock, err := rsa.DecryptPKCS1v15(rand.Reader, privateKey, cipherData[offset:offset+currentBlockSize])
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
buf.Write(decryptedBlock)
|
||||
offset += currentBlockSize
|
||||
}
|
||||
|
||||
return buf.String(), nil
|
||||
}
|
||||
|
||||
// DecryptWithPrivateThenEncryptWithPublic decrypts ciphertext with private key,
|
||||
// then encrypts the result with public key. Commonly used for authorization token transformation.
|
||||
func DecryptWithPrivateThenEncryptWithPublic(base64PrivateKey, base64PublicKey, ciphertext string) (string, error) {
|
||||
privateKey, err := parsePrivateKey(base64PrivateKey)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
publicKey, err := parsePublicKey(base64PublicKey)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Decrypt ciphertext using private key
|
||||
cipherData, err := base64.StdEncoding.DecodeString(ciphertext)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
plaintext, err := rsa.DecryptPKCS1v15(rand.Reader, privateKey, cipherData)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Encrypt plaintext using public key
|
||||
encrypted, err := rsa.EncryptPKCS1v15(rand.Reader, publicKey, plaintext)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return convertor.ToStdBase64(encrypted), nil
|
||||
}
|
||||
|
||||
// parsePrivateKey parses a private key from base64 or PEM format.
|
||||
// Supports both base64-encoded PKCS#8 keys and PEM-wrapped keys.
|
||||
func parsePrivateKey(keyStr string) (*rsa.PrivateKey, error) {
|
||||
var keyBytes []byte
|
||||
var err error
|
||||
|
||||
// Decode base64 if not PEM format
|
||||
if strings.Contains(keyStr, "-----BEGIN") {
|
||||
keyBytes = []byte(keyStr)
|
||||
} else {
|
||||
keyBytes, err = base64.StdEncoding.DecodeString(keyStr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Extract key data from PEM block if present
|
||||
if strings.Contains(keyStr, "-----BEGIN") {
|
||||
block, _ := pem.Decode(keyBytes)
|
||||
if block == nil {
|
||||
return nil, errors.New("failed to parse PEM block")
|
||||
}
|
||||
keyBytes = block.Bytes
|
||||
}
|
||||
|
||||
// Parse PKCS#8 private key
|
||||
parsedKey, err := x509.ParsePKCS8PrivateKey(keyBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
privateKey, ok := parsedKey.(*rsa.PrivateKey)
|
||||
if !ok {
|
||||
return nil, errors.New("not an RSA private key")
|
||||
}
|
||||
|
||||
return privateKey, nil
|
||||
}
|
||||
|
||||
// parsePublicKey parses a public key from base64 or PEM format.
|
||||
// Supports both base64-encoded X.509 keys and PEM-wrapped keys.
|
||||
func parsePublicKey(keyStr string) (*rsa.PublicKey, error) {
|
||||
var keyBytes []byte
|
||||
var err error
|
||||
|
||||
// Decode base64 if not PEM format
|
||||
if strings.Contains(keyStr, "-----BEGIN") {
|
||||
keyBytes = []byte(keyStr)
|
||||
} else {
|
||||
keyBytes, err = base64.StdEncoding.DecodeString(keyStr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Extract key data from PEM block if present
|
||||
if strings.Contains(keyStr, "-----BEGIN") {
|
||||
block, _ := pem.Decode(keyBytes)
|
||||
if block == nil {
|
||||
return nil, errors.New("failed to parse PEM block")
|
||||
}
|
||||
keyBytes = block.Bytes
|
||||
}
|
||||
|
||||
// Parse X.509 public key
|
||||
parsedKey, err := x509.ParsePKIXPublicKey(keyBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
publicKey, ok := parsedKey.(*rsa.PublicKey)
|
||||
if !ok {
|
||||
return nil, errors.New("not an RSA public key")
|
||||
}
|
||||
|
||||
return publicKey, nil
|
||||
}
|
||||
|
||||
// Sign generates MD5 signature for the given openId.
|
||||
// Signature = MD5("a488109389ef32d6ad546296b3260562" + openId)
|
||||
func Sign(openId string) string {
|
||||
data := "a488109389ef32d6ad546296b3260562" + openId
|
||||
hash := md5.Sum([]byte(data))
|
||||
return hex.EncodeToString(hash[:])
|
||||
}
|
||||
56
utility/integration/camel_oil_api/encrypt_test.go
Normal file
56
utility/integration/camel_oil_api/encrypt_test.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package camel_oil_api
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestDecryptWithPrivateThenEncryptWithPublic(t *testing.T) {
|
||||
base64PublicKey := "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDpwypBPN8r3Zwv3T0XUh1Ka2m2hUe3KBgIyH4fHfN/T1jsBWnbwotKEQdZfRva7mRYiz9YrTHoH/eUAuv+WYqPMubaiqpWOu0l+BzEX1kPGA98qRC06IF2Tk4Z5xAmQ8p8u3O5jxohYFkO2XlDvPU+W9SDZgSEBTe8p80LExgo6wIDAQAB" // 公钥 base64 或带 PEM
|
||||
base64PrivateKey := "MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAIHSkp+Z0Lu+lZWr/wKcMT3EcWEIihKTg/jEOyKaczqG9hWL9UULJ1dFtIQNlpWRySsVZcJLoGTFdGam557lVzpY/tbN73KG9iVMBaKALLF52cgmyg0DRve4atc0OnkhTjv7Rf8B85UokdHCAM/5MgNcjXwqBGHohJ2LGC9yN2erAgMBAAECgYATfTeqww4daTaOkhQF4cnYonl83inQMRoSSe8wuiwLQMCHqounEk4VIW9AlcOh75FaKOuuV+kbx7K6SFskNPy7nGYfS22t2aM9E9Rt+JH+caEniYi5qAfb3gCIgsGExUNI6iuSM2p3/R542EDGc2FyfPPqyht+jR4CjLOLoXHfoQJBALwvF6uIOSW0Lxh7Lo/JsKpWJ1qffDvXWYag605L9JAyP0yO64woF60Tn+mGRzcaEhNDSEjinKQqPEJnxDUGYaECQQCwm1mQKD95MaeKWBiOVJZsdzL5aJsW42xyiu0ZwA7bZUgJyUskzXG0ubeIHK/czlJbev9ODubbMNJFcngX4N3LAkAJaxH0M80oZew1fXTHHYEKBWXS00iUdiK06jjcolCLJvikDEMdsKP+tYy7U00dJODitetYOn88eCCr8iWPwdIBAkBtUjzGt6NS6iHDyXSp5kKXMdIkAVS/flgLL2RFpFWOCcvmAuy5A1N3g97QKrHSBQWGC0UulJri4/3Fb25XmaKxAkBifs9dbUifeqZRNVh2Omck4xedb1FyQPLDicUycjYug3Vca0T/LRr80aX/NhbhtpSdwzF1ukiZ6W46O9DmGuNy" // 私钥 base64 或带 PEM
|
||||
token := "M20UkqDpKp7u3wO1wxSGlBvFWS4AAVjcglRw5Feh6ezvxW4NtoX0V8dGajhCK1+9Gf+Yqb+dzrQ0evYKe7kpqyeXtqg5xobQ6Sx1MQsf6eV9kUBU1u/qS71wlIgDYJhtMk81qfF+Ojx1FCFzV+89jKLr3jeXf2QeRv/ZqMXIqb0=" // 待私钥解密的密文 base64
|
||||
result, _ := DecryptWithPrivateThenEncryptWithPublic(base64PrivateKey, base64PublicKey, token)
|
||||
t.Log(result)
|
||||
}
|
||||
|
||||
func TestEnc(t *testing.T) {
|
||||
pubkey := "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCkc6Xr/JhWEx/WPxG2q3VHLQ+FYk/oCmQ1y14B5j4xOJY+mAWoDOOti3sAXg0Kk662gWjWET1nLI6YED4wb9HWon1NAZn47lgc5ohIpEdU91Jao85X/kgkD3NvTTvhFicttepUOsrYUZN8rAQCE7AhzwGgKnCiIRY/kE8jOCCeZQIDAQAB"
|
||||
jsonString := "{\"openId\":\"app2511152349246268055\",\"phone\":\"13349946900\",\"goodId\":\"202511051701\",\"goodNum\":1,\"bindPhone\":\"13349946900\",\"payType\":\"appAli\",\"paramY\":26.996671,\"paramX\":77.450347,\"yanqian\":true,\"mobileOperatingPlatform\":\"iOS\",\"sysVersion\":\"iOS 15.7\",\"platformType\":\"iPhone X\",\"netWork\":\"wifi\",\"platform\":\"iOS\",\"brand\":\"Apple\",\"deviceId\":\"A6F2C1C0-8E65-4E4A-9A1B-3D8C9879A41E\"}"
|
||||
result, _ := EncryptWithPublicKey(pubkey, jsonString)
|
||||
t.Log(result)
|
||||
}
|
||||
|
||||
func TestSign(t *testing.T) {
|
||||
// 测试 Sign 函数
|
||||
openId := "app2511181557205741495"
|
||||
result := Sign(openId)
|
||||
t.Logf("Sign result: %s", result)
|
||||
|
||||
// Java 中的签名结果(根据 demo.java)
|
||||
// 不需要匹配具体值,简单验证应输出 MD5 值
|
||||
if result == "" {
|
||||
t.Fatalf("Sign result is empty")
|
||||
}
|
||||
t.Logf("Test passed! OpenId: %s, Signature: %s", openId, result)
|
||||
}
|
||||
|
||||
func TestDec(t *testing.T) {
|
||||
// 使用 Java demo.java 中的源数据进行测试
|
||||
// 这是用来加密的公钥
|
||||
|
||||
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 := "YhOjfuxANpCb0a4/ohwCWA5RFnj9EqY0r75nGD0IUJRrt0W4t1mOg0BcpHMwq0y3V+83ZmiLOh8+ckAzvQ4U6dQxYekcSmD9rOTGUE3mDWBpsOCP4gIGs5qitJKekvaQ/ILymtGvunhEOS3iEOfFZUptwi6zPOIzEU7kRlCNKXeSlj603PZo2vqwy3tn4osNOFPsZP+09FeudAbz9Ad0Niq8jfYbdXmopauoj2+nQFfBdw486wN4buGfJ5ZC1tWwWzzj1X0Mcibu68vp/uH5xYLn0/zJm/bDlxnTbcCA1aFd6bp/wom+KmvdhJfpS5bDlfddHg/5NefufG6uc2mWaDo41phUx9zgpJZkBjK5OEA1JeRz8KCHUiEET6pZsf65zFgGTE0qbieiHOkfYY4jb88AvzJgN5LgBYiG2GoCIzE0Zt8XlUfNwlZST9TW0FaYd0ftvtebKJbZ6ilJPiRgAW9Mh4i6RakbrVBSP/Y/CpL1uzpoQkiIdQ0YV0ED0rFSI4BsOExYl5dSsUfk1JJgFD2+NaU6uqyrxNMp8bySly1n0jU7TaHyxQVxb5tX/WOn78hwc0Qu7tMAnn7PcLWvf7FRG9ILGRYoS1odVhg4BvqSfHL/abZWriWP3bY+gNgK5U6GbtQr8wTGm0eyse5lrWTw1+OezT+jxOyCdJN6Z/uFTRvW5m9Hv1xxDg2qUOW4YKqU0fbb0vmOiF/2KpxvA3y8FLJ2jx3lDFaxzf8w9djwA8YkoiLWlE+/b5iQtOVqHeiYxySOvF+NTIh3AldbVGIUMc8BeCNMjM32Lbz9dkKl+gJeh/cKKZYP8n58ux48KxjR4LkDJAkpUJTQLy389mdizwGxswa8DkhUtAsGNw4apgh58wAUt/AVj92XiH+BCGrcqxfkiw792ycGs7DHAEuuWBnvROteGhFQ98d1nGoktrbh/p0M6jmygktNWcD4gNeXgZGvV+2BUdZSWNNdH2iDImscuYXOHtti6cDBZcULhWeiOVIIBMK1hIBevKsfoYbnhe8U32w//vJUotUlLywOiC0O1uVS5moS4/jWRo0XFCd6lUyxZ1sq1bKO9p9K3lxVlswMoDyLIAc1/xmDnJs6umzGuMPytdOgoHXloI2dnMuJw2aEZOfzTAte6aicsUbaqdJ6Mzhr869b0l4lhMyUpNFWLpccNVkKLP9BpSRDheWw1shKf1fX6eQLLOq6m7n5bSMAZ1psYDTUbq3SZCVx2j47MxHRl8SD4iGirFmye2k04I3d0Ldwtzq36yoNKmsRh7NtxBc2iKT2XmcRxYheh1cx+tnccmnII7ajIEKTwFBO4GRxfT1fKDt4mWvbFaXu+ZYNPIdgw505zutEVmJVaY+JGtTI0euGDEzPfhWGhbxmlGc/u5QpajatUE7ZiXLuQiBbNCpvIvf6ZsAzUPsV7Mzw7qfUMtSol7qW1cghgMwQvwSsr4F0FRFnJDY9Eox76LLKKwuTUJFKUuRadjRqyV2HfMSp4EE54LJS0zNmdeIxfiimIbz2D5Y/7S+lQ72a/wyKI1MtY1jCnK7IP+No3Vp0p1MmnXjVtmuR2zsniUSDJRnc+Pyb6+PaBtM0pTFzp6ghKlEeKYkKB/Jt2vDqqNO2/C20e1Fl93f0bf5mL3H6ZQU4ok8sUMWYnRHHK83fNP5MPwlD6a0ZsWEAdjLmsR1LjzB2qYzXTUxKf8d0fLg7txW6VdajDVsCSzhpkdxGjRhGZsiMpPuJDG1nJgQO140csQ2kch5cSA4DyCTkzV02Xe6ztLkXS2MnDLHHwbmB7qrYsyLQcsl5ldOqs7i4A38UgikEli/fm7kVrhLQMjJUlyhvtESTKkf4tm4gLacOSIl1BdS5YfkgFIpaEYglW+C7ChOk/YFq+ZYKR9akRK+xnDUrG+p0KhiQ5bdiYxGWEO/RbrHA9pa0iDyQQ103HNRNfmSFLWgrzVTT+gxjw3hpe7Pd4MenVYCZZSxNTR+RqC8/rUW5+nINr9F2HDTDrvg5ZDDND4itVCp7gXoiN1E/0m3V16k5Zcw9e+vJeb3Sld2g1/VRULDAgXCuhC4XIak1oH6jelrJMVJzCizYUGUgZbbGrByw1EsWddE1xBpK+5vGkvsjkjflXN1YQsIzZskcw7qHETilmp1U3kCvBlOPirU84lmR/5j9sjgkxfsrSpKJoxGFQ1OxuOwA2pvbbnc7ENK6gbTeV91fQ/JZ8FeycgdXAuFefi68WmGcl64h+jpfcmrlKJjaCei6VjWj3xiuTHtnv7SlXNaRPzZ1Nv183c8IpUtd5jWEaufpSTe+RYLFvZa2K0FRyYoPtf0gtIJOqpdxAXCT8m7jjxTU9bNWVrHAdsCxpeNGNRQerhp82zFL0UksbekzoYj5k06R6i+RNeE0jJLYBd1jeRWvj622m5Mo+Mqe0hvLq4WnwJa0ZIhxWR15eaE1D3Ad94sbDS9lUDTH/BLVXOXI5EODwmOcnvahxWl1VIokYqQHDsqHKidzSO63MMSvySIwFWLpKXe5oCuqbYyMs+oJop20NdCTjsW0cnQqDGakwyyXTMqgOoWkweWXqduE6ZmnNvRZKxZTgvpte9LcG4hRB9sLFt2/544uwSqQNO4GYHOFjMG0xRStBNTBPie4c5GHyuBAXMboKpt5466PIaTVW/AnCLRk3uDZHq6r3mYJkBv8peqQpW/LcZPHZVakiiLSQrxNOTi532jxmH5COu4FsCk4mDCK5fYS4N/rnZkBTgfHJY3StmhR5xmj0FrUthGe2qDTWT/MSTs3U2+Ryj3dT02pQHISoIGFQ3Mh612G9Q5VAt6CAqn3HrX/TfIODCnMP9wpBwA08/pvnGgOykVvVdGwXX/oDk8pVaTJAYPymTW0h5qug92Ms0I0LR0JxaMPbQ=="
|
||||
|
||||
encryptedBody := "YAV23MC+VJTC/vvj8kyBJ7GqqSVb3dRB2cDTVbMGAxJQDv2L/Up5j2nplPOJymLwkbWmLbeVF6KwTeG8/j3O+oLubtGqnXeeFZPWng+GDgrJ02kqGIVRo//K2H2l75Lzvmk575m605k17vlvoeh4buvhsbPFZ2KDeZI2F/qHvJpTrQliFhHlNm4dODdiSkpQyWm42qaEs2yc0SeyCysBPkovuMtLclG24tAu/MC5pZVieOlsDpHunN85OpmEOngrVX+s1QnOAcW/EjpkZI0K++5gwRNzB260UC3DKiGW84ro4RkhcV9kJqXikb8X3PQqy854nbnsKiBqDNVdQXkAxAKQ8x1VdvN1tP3Szuynzfb9jX5RZGfBiV2ZG06MgWu1CU60WJ00KubHF5DG0aQwTXDPqm7L5o7xymdSxmswA1da3senR6+ZII0Pc2/Gq8YH/qgzoJzTIyDQggkt8jDEvbtDOp4qzylnDwYQBxwFe2LSw8Wrtlm04wLkdrmyraz9BZ+VWRFxYp/bWf38XrIUerlPWxmUjBl/FaqXcrmf8BpoOXHJre9+UZtwtQzumW2sWj+ExFNqCPCp9gtU1xYSlEOE+ow6SQ51UOnQq0YH4BqkPtWWDezIBA+JO88ibdv2pA55FWVmNGCvC7mHpmst01+M4jaA/qAFy5IOW3J9PZoeonPykit5uvh1lX24d2p1ZzdiizoomhJM4ly8yD0kpAFtOG9K4FGVmaHGJ/ug0xvubfgBt0rzwMryHcJ1VKi+2O1Lqyn/Mb9h3GejxsuHR0FEFDZyO1Vmq0hO3XfT3mBjpRIKbUvZoDycv83wtZprEzDiK4ypsvAF3qGlLSC6D097SMM+WrYEki5YZi7+XEVgOUUIxXswCnwYH5ZDyWvtpXk1mw5E9rvJ6x2rg76Ng3rcpiutTQcSyM+JcxOMl5yKvQ2S64UOFJ4gcj4dyvVpyyt4rgPE9LYHN4F00mEkx6u9aEofxVCNLbvWZ3urZA9KQx0uREaZ9Vcbux6hzMr3eGkEQ5ZsZSMy/Liypt0cOMpfNF+cUNMv6mCkPSKI9yZngonZKVX0S6Ifr+HiuQnnrJivriPj93cFvGZMwKfwskbaqKVtLPWFy9GXvbdWarXDi4xVK5nDePmhhKiYe3jgjMJQaqE9M012cGydpJQCiyJ6FQkIqQvPXrPgQI8tpwKjJV1yONRauwsxbJZjUllMe+gm49ubfJzJCDoG21fJpZByrx406mAi9kGQrSUPRzJGsEg5bs+/ogTauIGvvJn6WhWB3bSjC8carumyhr9l19LW5brxoZpgO/r4ONpaKIxfbF5FqdFj3elC64AgZXZQp7FJQcN2pQbjspUimZfMa3VP8Q6+cxpVpd4Zbq9R2hZ9wbuxaB9iMbnEl5nVS+/OWVgB3qe5UBxjwAcE1SBDGVx+8ZvIkJI1xjiuSsQCeVg9bcxvI6vlwcQ54vqT881JIaPXBbrZSNLqlM8uPISGWi2tCG4tpTl4IxeAOyH3GzI21wv3nkM7UVm4/C6FBEPTjjB7DRl7KCgyIFhrTXK3qGdLkgBoHjWS9mlxqS93u8YKZ3wcMaYkBPsoNFe9qPMV5lZue7DCRIncjs0xpzFNav75v70WM7jQ5dtI33VaB45aXepbtsaFCAVRGRUhxddIQbQzAr1+/zzfOrxuBm4333+tcddLQ+7vUYYpk8WHR/ZxyfHhosP/1iOlsCaA6FkihNnXhqz4LN5VgBwhdkVvEtMeQaKj9CIUwTp+d6www+397EXcpuvldHAJh4wKtf+fRB4Oq3cKhHCgpwrP9pjfZrGWKkMF7t8GFdQYToHDRdLycsVsnM1PreL3/pMUBphRGTVwoVvexZbKtT+Ju/nVLgVtnhURoQqtyYGCSz8wbM7CvqbZ65DO8w3zh7uZcNMcKQwV8LD8U0bdT38Xk9OG5v3ThmUNBsw5w6poDMrxNqkZkEOrbYTUvBB0SJ1ZyWR1ha4pVnuitzJk1KlDkKTJppfsNlQtw+yuXa58txnqxojO3hVJWWY9jM4vkew6zySjDeA3ZxuR5jw9y2W8mPG8hpWmH3/J5g3Y5gFDoPJl724+Vgoyw7xZZJ3ERtTXV/xCltsTgOs+DcWOm9LG3FU3gmg048NaSz7P4KPg73aE9pGns6QWkbRit0N05kw439/mYkKnn1Um6j5iSNbFAfyew2eE8ONTBA3381KNNZnJSG9ygSSpomUhHmAOWrawrS+amFO4OtC4+T7s6Z3Th44Ypkqc1j5NcXD8jR3KjjFL8ebVnB1BmJ/goIWY/4DDg3RbatfeGhTZOwwRP4rwg3QA/wvx8EztDvgJaB98f+cHym1kK1vK4iJok4AToBvgmt7PYxPmIvoe3vvSSUZD4CKUCU4T3R0zw+8Xw53LNz33vsWOuqINIVqBID3GpJEl5sELP1SYZY9nQn77UiwUHK2PPOfXxitXcdSyLfhSsyXHbgpbdhxCuwFNo5UZMCJX/O4KVCk4PlQc7TWAgJxAnev/N6GGmoesAzjWuE1+QuOC+k9L22/i0iQmfrmzpdLaN3CNCOdOKBz1TX/WLLli5J8XkYDQWmoLC3wbflB1DW7Cn8Jyn0C2lXYZiZFYwSSKlsmeHHGo4xuPHTWDZGEDhQ2ln5+GYPvqDyV8RKb4lhxddLMiPn3M/rQuQLeCR08DXq7Y5YlgDYm75pPgdo+oQasnejsrxNgWxnapPlW6peN3xyAew0HgE7/swMt5k8h2szESEjCqMNW7IyX/qMZuF1rSn9h3DyVuvbnBYGON1CK3u2ryBR9w6jnobIAjs1BkBjCXobM7pnh2+EzOWHAjq44KvkgfQtEmZup/AhDVfIM0DezeMQhq+D1s6fjHnw29Qu/rZuCVWUrRm7j5gqiVv92tlA=="
|
||||
|
||||
// Use DecryptWithPrivateKey function to decrypt
|
||||
decrypted, err := DecryptWithPrivateKey(prikey, encryptedBody)
|
||||
if err != nil {
|
||||
t.Fatalf("Dec failed: %v", err)
|
||||
}
|
||||
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;
|
||||
|
||||
}
|
||||
7
utility/integration/camel_oil_api/models.go
Normal file
7
utility/integration/camel_oil_api/models.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package camel_oil_api
|
||||
|
||||
type QueryResult struct {
|
||||
CardNumber string `json:"card_number"`
|
||||
CardPassword string `json:"card_password"`
|
||||
Balance float64 `json:"balance"`
|
||||
}
|
||||
@@ -63,27 +63,35 @@ func (c *InternalClient) getToken(ctx context.Context) (string, error) {
|
||||
|
||||
// GetAccountInfo 获取账号信息
|
||||
func (c *InternalClient) GetAccountInfo(ctx context.Context) (string, error) {
|
||||
token, err := c.getToken(ctx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
respBody := struct {
|
||||
Token string `json:"token"`
|
||||
SID int `json:"sid"`
|
||||
}{
|
||||
Token: token,
|
||||
SID: 21108,
|
||||
}
|
||||
//尝试100次,直到获取到号码为止
|
||||
for range 100 {
|
||||
token, err := c.getToken(ctx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
respBody := struct {
|
||||
Token string `json:"token"`
|
||||
SID int `json:"sid"`
|
||||
}{
|
||||
Token: token,
|
||||
SID: 21108,
|
||||
}
|
||||
|
||||
resp, err := c.Client.Post(ctx, fmt.Sprintf("https://api.haozhuyun.com/sms?api=getPhone&token=%s&sid=%d&Province=&ascription=&isp=", respBody.Token, respBody.SID))
|
||||
if err != nil {
|
||||
return "", err
|
||||
resp, err := c.Client.Post(ctx, fmt.Sprintf("https://api.haozhuyun.com/sms?api=getPhone&token=%s&sid=%d&Province=&ascription=&isp=", respBody.Token, respBody.SID))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
respStr := resp.ReadAllString()
|
||||
glog.Info(ctx, "获取信息", respStr)
|
||||
respStruct := struct {
|
||||
Phone string `json:"phone"`
|
||||
}{}
|
||||
err = json.Unmarshal([]byte(respStr), &respStruct)
|
||||
if respStruct.Phone != "" {
|
||||
return respStruct.Phone, nil
|
||||
}
|
||||
}
|
||||
respStruct := struct {
|
||||
Phone string `json:"phone"`
|
||||
}{}
|
||||
err = json.Unmarshal(resp.ReadAll(), &respStruct)
|
||||
return respStruct.Phone, err
|
||||
return "", fmt.Errorf("获取账号失败")
|
||||
}
|
||||
|
||||
// CheckVerifyCode 检测验证码是否已接收
|
||||
@@ -97,13 +105,15 @@ func (c *InternalClient) CheckVerifyCode(ctx context.Context, phone string) (cod
|
||||
if err != nil {
|
||||
return "", false, err
|
||||
}
|
||||
respStr := resp.ReadAllString()
|
||||
glog.Info(ctx, "获取信息", respStr)
|
||||
respStruct := struct {
|
||||
Code json.Number `json:"code"`
|
||||
Msg string `json:"msg"`
|
||||
Sms string `json:"sms"`
|
||||
Yzm string `json:"yzm"`
|
||||
}{}
|
||||
err = json.Unmarshal(resp.ReadAll(), &respStruct)
|
||||
err = json.Unmarshal([]byte(respStr), &respStruct)
|
||||
if err != nil {
|
||||
return "", false, err
|
||||
}
|
||||
|
||||
@@ -5,15 +5,11 @@ import (
|
||||
_ "github.com/gogf/gf/contrib/nosql/redis/v2"
|
||||
"github.com/gogf/gf/v2/os/glog"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestInternalClient_GetAccountInfo(t *testing.T) {
|
||||
account, _ := NewClient().GetAccountInfo(t.Context())
|
||||
count := 10 * 100
|
||||
glog.Info(t.Context(), "账号信息:", account)
|
||||
for i := 0; i < count; i++ {
|
||||
time.Sleep(time.Second * 5)
|
||||
NewClient().CheckVerifyCode(t.Context(), account)
|
||||
for i := 0; i < 10; i++ {
|
||||
account, _ := NewClient().GetAccountInfo(t.Context())
|
||||
glog.Info(t.Context(), account)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user