This commit is contained in:
邹宗楠
2025-03-19 18:50:13 +08:00
parent b308322826
commit 2663099187
8 changed files with 513 additions and 1 deletions

View File

@@ -58,7 +58,7 @@ func NewQBiDa(account, password string, config ...*platformapi.APIConfig) *Api {
config: &curConfig,
}
if err := api.CheckTokenExpiration(); err != nil {
panic(err)
fmt.Println(err)
}
return api

View File

@@ -0,0 +1,134 @@
package quick_recharge
const (
AppKey = "48ef1387dbea225786b910c6fce4c1fa" // api权限秘钥
UserID = "100220" // 平台商户ID
UrlLink = "http://ads.haoshengkm.com" // 域名数据访问连接
ResponseCodeSuccess = 1000
)
// GetRechargeGoodsResp 获取商品列表
type GetRechargeGoodsResp struct {
Code int `json:"code"`
Msg string `json:"msg"`
AllPage int `json:"allPage"` // 总页数
AllCount int `json:"allCount"` // 总条数
Data []*RechargeGoodsRespData `json:"data"` // 商品列表
}
type RechargeGoodsRespData struct {
Id int `json:"id"` // 商品ID
KeyID int `json:"keyId"` // 废弃
Price float64 `json:"price"` // 商品面值
Money float64 `json:"money"` // 购买价格(多规格时显示本平台的默认价格)
Name string `json:"name"` // 名称
Number int `json:"number"` // 商品编号
Day int `json:"day"` // 质保天数为0时无限制
Type int `json:"type"` // 商品类型1、卡密3、人工代充
Multiple int `json:"multiple"` // 发货数量买1件时的实际发货数量
Status int `json:"status"` // 销售状态1、销售2、暂停3、禁售
IsRepeat int `json:"isRepeat"` // 重复下单0、不允许1、允许
SkuType int `json:"skuType"` // 规格类型0、单规格1、多规格
IsRefOrder int `json:"isRefOrder"` // 不知道是啥
IsRefMoney int `json:"isRefMoney"` // 不知道是撒
}
// GetGoodsDetailResp 根据ID获取商品详情数据
type GetGoodsDetailResp struct {
Code int `json:"code"`
Msg string `json:"msg"`
Data []*GetGoodsDetailData `json:"data"`
}
type GetGoodsDetailData struct {
Id int `json:"id"` // 商品ID
Number int `json:"number"` // 商品编号
Name string `json:"name"` // 商品名称
Price float64 `json:"price"` // 商品面值
Money float64 `json:"money"` // 商品面值
Type int `json:"type"` // 商品类型1卡密3人工代充
Day int `json:"day"` // 质保天数为0时无限制
Min int `json:"min"` // 最低下单数量
Max int `json:"max"` // 最高下单数量
Note string `json:"note"` // 注意事项
Desc string `json:"desc"` // 商品介绍
Describe string `json:"describe"` //
AccountName string `json:"accountName"` // 充值帐号名称:人工代充商品专用
AccountType int `json:"accountType"` // 充值帐号类型1、文本输入框4、下拉菜单5、多行文本6、数字类型7、累乘类型8、图片类型
AccountType1 int `json:"accountType1"` //
AccountDesc string `json:"accountDesc"` // 充值帐号提示
AccountContent string `json:"accountContent"` //充值帐号选项内容:单选、下拉菜单专用,多个选项用“,”号隔开
Count int `json:"count"` // 库存总数量
TagName string `json:"TagName"` // 标签名称
TagColor string `json:"TagColor"` // 标签颜色
Imgs []struct {
Img string `json:"img"` // 图片
} `json:"imgs"` // 图片地址列表
Discounts []struct {
Min int `json:"min"` // 大于min个
Max int `json:"max"` // 小于等于max个
Discount float64 `json:"discount"` // 折扣:单位%
} `json:"discounts"` // 折扣列表
Templates []struct {
Name string `json:"name"` //模版名称
Type int `json:"type"` //模版类型1、文本输入框4、下拉菜单5、多行文本6、数字类型7、累乘类型8、图片类型9、级联选择
Desc string `json:"desc"` //文本输入框提示
Content string `json:"content"` // 选项内容:单选、下拉菜单专用,多个选项用“,”号隔开
} `json:"templates"` // 充值模版列表
MainKey int `json:"mainKey"`
Status int `json:"status"` //销售状态1、销售2、暂停3、禁售
Multiple int `json:"multiple"` // 发货数量买1件时的实际发货数量
IsRepeat int `json:"isRepeat"` // 重复下单0、不允许1、允许
SkuType int `json:"skuType"` // 规格类型0、单规格1、多规格
Skus []struct {
Name string `json:"name"` // 规格分类名称
Data []interface{} `json:"data"` // 规格数组 name 规格名称
} `json:"skus"` // 商品规格数组(多规格专用)
SkuDetails []struct {
Names []struct {
Title string `json:"title"` // 规格分类名称
Value string `json:"value"` // 规格名称
} // 规格信息数组 title 规格分类名称 value
status int `json:"status"` // 规格上架状态0、已下架1、已上架
sku int `json:"sku"` // 规格编码
money int `json:"money"` // 规格购买价格
count int `json:"count"` // 规格库存数量
} `json:"skuDetails"` // 规格详情数组(多规格专用)
}
// CreateOrderByGoodsReq 商品下单
type CreateOrderByGoodsReq struct {
UserNo int `json:"userNo"` // 是 客户编号
Id int `json:"id"` // 是 商品ID
PayType int `json:"payType"` // 是 扣款方式0、余额下单1、骏卡兑换
Count int `json:"count"` // 是 购买数量
SafePrice float64 `json:"safePrice"` // 否 安全进价,祥见下方说明
Comment string `json:"comment"` // 否 购买备注
OuterNumber string `json:"outerNumber"` // 否 进货方订单号(采购单号)
NotifyUrl string `json:"notifyUrl"` // 否 异步通知地址
CardPartner string `json:"cardPartner"` // 否 骏卡商户号payType为1时必填
CardNumber string `json:"cardNumber"` // 否 骏卡卡号payType为1时必填
CardPwd string `json:"cardPwd"` // 否 骏卡密码payType为1时必填
CardFace float64 `json:"cardFace"` // 否 骏卡面额payType为1时必填
Account string `json:"account"` // 否 充值帐号
Name0 string `json:"name0"` // 否 充值模版的充值名称0
Val0 string `json:"val0"` // 否 充值模版的充值内容0
Name1 string `json:"name1"` // 否 充值模版的充值名称1
Val1 string `json:"val1"` // 否 充值模版的充值内容1
Name2 string `json:"name2"` // 否 充值模版的充值名称2
Val2 string `json:"val2"` // 否 充值模版的充值内容2
Name3 string `json:"name3"` // 否 充值模版的充值名称3
Val3 string `json:"val3"` // 否 充值模版的充值内容3
Name4 string `json:"name4"` // 否 充值模版的充值名称4
Val4 string `json:"val4"` // 否 充值模版的充值内容4
Name5 string `json:"name5"` // 否 充值模版的充值名称5
Val5 string `json:"val5"` // 否 充值模版的充值内容5
Name6 string `json:"name6"` // 否 充值模版的充值名称6
Val6 string `json:"val6"` // 否 充值模版的充值内容6
Name7 string `json:"name7"` // 否 充值模版的充值名称7
Val7 string `json:"val7"` // 否 充值模版的充值内容7
Name8 string `json:"name8"` // 否 充值模版的充值名称8
Val8 string `json:"val8"` // 否 充值模版的充值内容8
Sku string `json:"sku"` // 否 规格编码(多规格必传)
Sign string `json:"sign"` // 是 接口密钥+ userNo+id+count+payType
}

View File

@@ -0,0 +1,170 @@
package quick_recharge
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/md5"
"encoding/base64"
"encoding/hex"
"fmt"
"git.rosy.net.cn/baseapi/platformapi"
"git.rosy.net.cn/baseapi/utils"
"net/http"
"net/url"
"strings"
"unicode"
)
type API struct {
platformapi.APICookie
appKey string
userID string
client *http.Client
config *platformapi.APIConfig
}
func New(appKey, userId string, config ...*platformapi.APIConfig) *API {
curConfig := platformapi.DefAPIConfig
if len(config) > 0 {
curConfig = *config[0]
}
return &API{
appKey: appKey,
userID: userId,
client: &http.Client{Timeout: curConfig.ClientTimeout},
config: &curConfig,
}
}
// AccessInfo 发送请求
func (a *API) AccessInfo(baseUrl, url, requestMethods string, resultKey string, param map[string]interface{}) (retVal map[string]interface{}, err error) {
err = platformapi.AccessPlatformAPIWithRetry(a.client,
func() *http.Request {
var request *http.Request
if requestMethods == http.MethodPost {
fullURL := utils.GenerateGetURL(baseUrl, url, nil)
// 获取json结构体的参数体
request, _ = http.NewRequest(http.MethodPost, fullURL, strings.NewReader(utils.Map2URLValues(param).Encode()))
request.Header.Add("Content-Type", "application/x-www-form-urlencoded")
} else {
request, _ = http.NewRequest(http.MethodGet, utils.GenerateGetURL(baseUrl, url, param), nil)
}
return request
},
a.config,
func(response *http.Response, bodyStr string, jsonResult1 map[string]interface{}) (errLevel string, err error) {
if jsonResult1 == nil {
return platformapi.ErrLevelRecoverableErr, fmt.Errorf("mapData is nil")
}
retVal = jsonResult1
if jsonResult1["code"] == nil {
return platformapi.ErrLevelGeneralFail, fmt.Errorf("返回结果格式不正常")
}
code := int(utils.MustInterface2Int64(jsonResult1["code"]))
if code == ResponseCodeSuccess {
if resultKey == "" {
retVal = jsonResult1
} else {
retVal, _ = jsonResult1[resultKey].(map[string]interface{})
}
return platformapi.ErrLevelSuccess, nil
}
newErr := utils.NewErrorIntCode(jsonResult1["msg"].(string), code)
return platformapi.ErrLevelCodeIsNotOK, newErr
})
return retVal, err
}
// AESEncrypt 加密函数
func AESEncrypt(plaintext, key []byte) (string, error) {
block, err := aes.NewCipher(key)
if err != nil {
return "", err
}
blockSize := block.BlockSize()
paddedPlaintext := PKCS7Padding(plaintext, blockSize)
ciphertext := make([]byte, len(paddedPlaintext))
blockMode := cipher.NewCBCEncrypter(block, key[:blockSize])
blockMode.CryptBlocks(ciphertext, paddedPlaintext)
return base64.StdEncoding.EncodeToString(ciphertext), nil
}
// AESDecrypt 解密函数
func AESDecrypt(ciphertext, key []byte) (string, error) {
decodedCiphertext, err := base64.StdEncoding.DecodeString(string(ciphertext))
if err != nil {
return "", err
}
block, err := aes.NewCipher(key)
if err != nil {
return "", err
}
blockSize := block.BlockSize()
plaintext := make([]byte, len(decodedCiphertext))
blockMode := cipher.NewCBCDecrypter(block, key[:blockSize])
blockMode.CryptBlocks(plaintext, decodedCiphertext)
unpaddedPlaintext, err := PKCS7UnPadding(plaintext)
if err != nil {
return "", err
}
return string(unpaddedPlaintext), nil
}
// Md5Sign 签名
func (a *API) Md5Sign(signKey string, param ...string) string {
md5Data := signKey
for _, v := range param {
if ContainsChinese(v) {
md5Data += url.QueryEscape(v)
} else {
md5Data += v
}
}
lowerStr := strings.ToLower(md5Data)
harsher := md5.New()
harsher.Write([]byte(lowerStr))
hash := harsher.Sum(nil)
return hex.EncodeToString(hash)
}
// ContainsChinese 参数化是否包含中文
func ContainsChinese(s string) bool {
for _, v := range s {
if unicode.Is(unicode.Han, v) {
return true
}
}
return false
}
// PKCS7Padding 填充方法
func PKCS7Padding(ciphertext []byte, blockSize int) []byte {
padding := blockSize - len(ciphertext)%blockSize
padtext := bytes.Repeat([]byte{byte(padding)}, padding)
return append(ciphertext, padtext...)
}
// PKCS7UnPadding 移除填充
func PKCS7UnPadding(origData []byte) ([]byte, error) {
length := len(origData)
if length == 0 {
return nil, fmt.Errorf("invalid encrypted data")
}
unpadding := int(origData[length-1])
if unpadding > length || unpadding <= 0 {
return nil, fmt.Errorf("invalid encrypted data")
}
return origData[:(length - unpadding)], nil
}

View File

@@ -0,0 +1,54 @@
package quick_recharge
import (
"fmt"
"git.rosy.net.cn/baseapi"
"go.uber.org/zap"
"testing"
)
var (
api *API
sugarLogger *zap.SugaredLogger
)
func init() {
logger, _ := zap.NewDevelopment()
sugarLogger = logger.Sugar()
baseapi.Init(sugarLogger)
api = New(AppKey, UserID)
}
func TestAes(t *testing.T) {
key := []byte("5fb9600dd400b5e0853caed93ebbfb4e") // 256 位密钥,必须是 32 字节长
plaintext := []byte("mpwZ8Mf03eQm0xAu7+RWG8VM5RQpSIBrqUGilYts6p1c04uydMlsM5z85Azgt8HORVbJv+EReLAxoaHW2L8ZWeyG80mskqzjyKr8wuOiv/6upkYGi0lAVTSfrxuJaf")
// 加密
encryptedText, err := AESEncrypt(plaintext, key)
if err != nil {
fmt.Println("Encryption failed:", err)
return
}
fmt.Println("Encrypted Text:", encryptedText)
}
func TestAESDecrypt(t *testing.T) {
encryptedText := "mpwZ8Mf031teQm0xAu7+RWG8VM5RQpSIBrqUGilYts6p1c04uydMlsM5z85Azgt8HORVbJv+EReLAxoaHW2L8ZWeyG80mskqzjyKr8wuOiv/6upkYGi0lAVTSfrxuJaf"
key := "5fb9600dd400b5e0853caed93ebbfb4e"
// 解密
decryptedText, err := AESDecrypt([]byte(encryptedText), []byte(key))
if err != nil {
fmt.Println("Decryption failed:", err)
return
}
fmt.Println("Decrypted Text:", decryptedText)
}
func TestSign(t *testing.T) {
md5 := api.Md5Sign(AppKey, UserID)
fmt.Println(md5)
}

View File

@@ -0,0 +1,63 @@
package quick_recharge
import (
"encoding/json"
"fmt"
"git.rosy.net.cn/baseapi/utils"
"net/http"
)
// GetRechargeGoods 获取平台商品列表
func (a *API) GetRechargeGoods(param map[string]interface{}) (result []*RechargeGoodsRespData, err error) {
if param["type"] != nil {
param["sign"] = a.Md5Sign(AppKey, UserID, utils.Int64ToStr(utils.ForceInterface2Int64(param["type"])))
} else {
param["sign"] = a.Md5Sign(AppKey, UserID)
}
goods, err := a.AccessInfo(UrlLink, "api/getGoods.htm", http.MethodPost, "", param)
if err != nil {
return nil, err
}
var goodList *GetRechargeGoodsResp
goodsByte, err := json.Marshal(goods)
if err != nil {
return nil, err
}
if err = json.Unmarshal(goodsByte, &goodList); err != nil {
return nil, err
}
if goodList.Code != ResponseCodeSuccess {
return nil, fmt.Errorf("%s", goodList.Msg)
}
return goodList.Data, nil
}
// GetGoodsDetail 获取商品详情
func (a *API) GetGoodsDetail(goodsId int64) (detail []*GetGoodsDetailData, err error) {
param := map[string]interface{}{
"userNo": a.userID,
"id": goodsId,
}
param["sign"] = a.Md5Sign(AppKey, UserID, utils.Int64ToStr(goodsId))
data, err := a.AccessInfo(UrlLink, "api/getGood.htm", http.MethodPost, "", param)
if err != nil {
return nil, err
}
var result *GetGoodsDetailResp
goodsByte, err := json.Marshal(data)
if err != nil {
return nil, err
}
if err = json.Unmarshal(goodsByte, &result); err != nil {
return nil, err
}
if result.Code != ResponseCodeSuccess {
return nil, fmt.Errorf("%s", result.Msg)
}
return result.Data, nil
}

View File

@@ -0,0 +1,28 @@
package quick_recharge
import (
"testing"
)
func TestGetRechargeGoods(t *testing.T) {
param := map[string]interface{}{
"userNo": 100220,
"page": 1,
"type": 1,
}
goodsList, err := api.GetRechargeGoods(param)
if err != nil {
t.Log("err := ", err)
} else {
t.Log(goodsList)
}
}
func TestGetGoodsDetail(t *testing.T) {
goods, err := api.GetGoodsDetail(56254)
if err != nil {
t.Log("err := ", err)
} else {
t.Log(&goods)
}
}

View File

@@ -0,0 +1,42 @@
package quick_recharge
import (
"encoding/json"
"fmt"
"git.rosy.net.cn/baseapi/utils"
"net/http"
)
// CreateOrderByGoods 根据商品下单
func (a *API) CreateOrderByGoods(param *CreateOrderByGoodsReq) (result *CreateOrderByGoodsResp, err error) {
param.Sign = a.Md5Sign(AppKey, UserID, utils.Int2Str(param.Id), utils.Int2Str(param.Count), utils.Int2Str(param.PayType))
data, err := a.AccessInfo(UrlLink, "api/v2/addOrder", http.MethodPost, "", utils.Struct2Map(param, "", false))
if err != nil {
return nil, err
}
orderByte, err := json.Marshal(data)
if err != nil {
return nil, err
}
if err = json.Unmarshal(orderByte, &result); err != nil {
return nil, err
}
if result.Code != ResponseCodeSuccess {
return nil, fmt.Errorf("%s", err)
}
return result, nil
}
type CreateOrderByGoodsResp struct {
Code int `json:"code"`
Msg string `json:"msg"`
Id int `json:"id"` // 订单ID
Number string `json:"number"` // 订单编号
Cards []struct {
Number string `json:"number"` // 卡号密码
Pwd string `json:"pwd"`
} `json:"cards"`
}

View File

@@ -0,0 +1,21 @@
package quick_recharge
import (
"git.rosy.net.cn/baseapi/utils"
"testing"
)
func TestGetRechargeCreateOrder(t *testing.T) {
param := &CreateOrderByGoodsReq{
UserNo: utils.Str2Int(UserID),
Id: 56254,
PayType: 0,
Count: 1,
}
result, err := api.CreateOrderByGoods(param)
if err != nil {
t.Log(err)
} else {
t.Log(result)
}
}