各种修改
This commit is contained in:
parent
fa1b388c8d
commit
ac2a39742d
4572
csv/csv.go
4572
csv/csv.go
File diff suppressed because it is too large
Load Diff
@ -2,7 +2,6 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
)
|
||||
|
||||
//
|
||||
@ -848,53 +847,53 @@ import (
|
||||
// fmt.Println("\n测试完成!")
|
||||
//}
|
||||
|
||||
func main() {
|
||||
filename := "csv/taskLog.csv"
|
||||
handleID, err := GetManager().OpenCSVFile(filename, ',', true)
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
}
|
||||
fmt.Println(handleID)
|
||||
headers := []string{"ID", "序号", "姓名", "年龄"}
|
||||
err = GetManager().WriteHeader(handleID, headers)
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
}
|
||||
|
||||
//rowss, i, err := GetManager().AppendRowss(handleID, rows)
|
||||
//if err != nil {
|
||||
// fmt.Println(err.Error())
|
||||
//}
|
||||
//fmt.Println(rowss)
|
||||
//fmt.Println(i)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
results := make([][]int64, 100)
|
||||
|
||||
for i := 0; i < 100; i++ {
|
||||
rows := [][]string{
|
||||
{fmt.Sprintf("%d", i), fmt.Sprintf("%d", i), fmt.Sprintf("张三%d", i), fmt.Sprintf("3%d", i)},
|
||||
}
|
||||
wg.Add(1)
|
||||
go func(idx int) {
|
||||
defer wg.Done()
|
||||
rowsss, _ := GetManager().AppendRowsNum(handleID, rows)
|
||||
fmt.Println(rows, ":", rowsss)
|
||||
results[idx] = rowsss
|
||||
}(i)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
//rowsss, err := GetManager().AppendRowsss(handleID, rows)
|
||||
//if err != nil {
|
||||
// fmt.Println(err.Error())
|
||||
//}
|
||||
//fmt.Println(rowsss)
|
||||
|
||||
GetManager().CloseHandle(handleID)
|
||||
|
||||
}
|
||||
//func main() {
|
||||
// filename := "csv/taskLog.csv"
|
||||
// handleID, err := GetManager().OpenCSVFile(filename, ',', true)
|
||||
// if err != nil {
|
||||
// fmt.Println(err.Error())
|
||||
// }
|
||||
// fmt.Println(handleID)
|
||||
// headers := []string{"ID", "序号", "姓名", "年龄"}
|
||||
// err = GetManager().WriteHeader(handleID, headers)
|
||||
// if err != nil {
|
||||
// fmt.Println(err.Error())
|
||||
// }
|
||||
//
|
||||
// //rowss, i, err := GetManager().AppendRowss(handleID, rows)
|
||||
// //if err != nil {
|
||||
// // fmt.Println(err.Error())
|
||||
// //}
|
||||
// //fmt.Println(rowss)
|
||||
// //fmt.Println(i)
|
||||
//
|
||||
// var wg sync.WaitGroup
|
||||
// results := make([][]int64, 100)
|
||||
//
|
||||
// for i := 0; i < 100; i++ {
|
||||
// rows := [][]string{
|
||||
// {fmt.Sprintf("%d", i), fmt.Sprintf("%d", i), fmt.Sprintf("张三%d", i), fmt.Sprintf("3%d", i)},
|
||||
// }
|
||||
// wg.Add(1)
|
||||
// go func(idx int) {
|
||||
// defer wg.Done()
|
||||
// rowsss, _ := GetManager().AppendRowsNum(handleID, rows)
|
||||
// fmt.Println(rows, ":", rowsss)
|
||||
// results[idx] = rowsss
|
||||
// }(i)
|
||||
// }
|
||||
//
|
||||
// wg.Wait()
|
||||
//
|
||||
// //rowsss, err := GetManager().AppendRowsss(handleID, rows)
|
||||
// //if err != nil {
|
||||
// // fmt.Println(err.Error())
|
||||
// //}
|
||||
// //fmt.Println(rowsss)
|
||||
//
|
||||
// GetManager().CloseHandle(handleID)
|
||||
//
|
||||
//}
|
||||
|
||||
// WriteRowss 批量写入多行数据到CSV文件
|
||||
// 返回写入的起始行号和写入的行数,错误时返回(-1, -1, error)
|
||||
|
||||
2540
csv/newcsv.go
2540
csv/newcsv.go
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@ -1,125 +0,0 @@
|
||||
/* Code generated by cmd/cgo; DO NOT EDIT. */
|
||||
|
||||
/* package command-line-arguments */
|
||||
|
||||
|
||||
#line 1 "cgo-builtin-export-prolog"
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#ifndef GO_CGO_EXPORT_PROLOGUE_H
|
||||
#define GO_CGO_EXPORT_PROLOGUE_H
|
||||
|
||||
#ifndef GO_CGO_GOSTRING_TYPEDEF
|
||||
typedef struct { const char *p; ptrdiff_t n; } _GoString_;
|
||||
extern size_t _GoStringLen(_GoString_ s);
|
||||
extern const char *_GoStringPtr(_GoString_ s);
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
/* Start of preamble from import "C" comments. */
|
||||
|
||||
|
||||
#line 3 "expressDeliveryOrder.go"
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#line 1 "cgo-generated-wrapper"
|
||||
|
||||
|
||||
/* End of preamble from import "C" comments. */
|
||||
|
||||
|
||||
/* Start of boilerplate cgo prologue. */
|
||||
#line 1 "cgo-gcc-export-header-prolog"
|
||||
|
||||
#ifndef GO_CGO_PROLOGUE_H
|
||||
#define GO_CGO_PROLOGUE_H
|
||||
|
||||
typedef signed char GoInt8;
|
||||
typedef unsigned char GoUint8;
|
||||
typedef short GoInt16;
|
||||
typedef unsigned short GoUint16;
|
||||
typedef int GoInt32;
|
||||
typedef unsigned int GoUint32;
|
||||
typedef long long GoInt64;
|
||||
typedef unsigned long long GoUint64;
|
||||
typedef GoInt64 GoInt;
|
||||
typedef GoUint64 GoUint;
|
||||
typedef size_t GoUintptr;
|
||||
typedef float GoFloat32;
|
||||
typedef double GoFloat64;
|
||||
#ifdef _MSC_VER
|
||||
#if !defined(__cplusplus) || _MSVC_LANG <= 201402L
|
||||
#include <complex.h>
|
||||
typedef _Fcomplex GoComplex64;
|
||||
typedef _Dcomplex GoComplex128;
|
||||
#else
|
||||
#include <complex>
|
||||
typedef std::complex<float> GoComplex64;
|
||||
typedef std::complex<double> GoComplex128;
|
||||
#endif
|
||||
#else
|
||||
typedef float _Complex GoComplex64;
|
||||
typedef double _Complex GoComplex128;
|
||||
#endif
|
||||
|
||||
/*
|
||||
static assertion to make sure the file is being used on architecture
|
||||
at least with matching size of GoInt.
|
||||
*/
|
||||
typedef char _check_for_64_bit_pointer_matching_GoInt[sizeof(void*)==64/8 ? 1:-1];
|
||||
|
||||
#ifndef GO_CGO_GOSTRING_TYPEDEF
|
||||
typedef _GoString_ GoString;
|
||||
#endif
|
||||
typedef void *GoMap;
|
||||
typedef void *GoChan;
|
||||
typedef struct { void *t; void *v; } GoInterface;
|
||||
typedef struct { void *data; GoInt len; GoInt cap; } GoSlice;
|
||||
|
||||
#endif
|
||||
|
||||
/* End of boilerplate cgo prologue. */
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
|
||||
// ZtoOpenCreateOrder 中通快递--创建订单接口
|
||||
//
|
||||
extern __declspec(dllexport) char* ZtoOpenCreateOrder(char* requestJSON, char* appKey, char* appSecret);
|
||||
|
||||
// JtOrderAddOrder 极兔快递--创建订单接口
|
||||
//
|
||||
extern __declspec(dllexport) char* JtOrderAddOrder(char* requestJSON, char* apiAccount, char* privateKey);
|
||||
|
||||
// EmsAmpApiOpen 邮政快递--订单接入接口
|
||||
//
|
||||
extern __declspec(dllexport) char* EmsAmpApiOpen(char* requestJSON, char* secretKey);
|
||||
|
||||
// StoOmsExpressOrderCreate 申通快递--订单接入接口
|
||||
//
|
||||
extern __declspec(dllexport) char* StoOmsExpressOrderCreate(char* requestJSON, char* fromAppkey, char* secretKey, char* fromCode);
|
||||
|
||||
// YdCreateBmOrder 韵达快递--电子面单下单
|
||||
//
|
||||
extern __declspec(dllexport) char* YdCreateBmOrder(char* requestJSON, char* appKey, char* appSecret);
|
||||
|
||||
// YdBmGetPdfInfo 韵达快递--电子面单打印
|
||||
//
|
||||
extern __declspec(dllexport) char* YdBmGetPdfInfo(char* requestJSON, char* appKey, char* appSecret);
|
||||
|
||||
// IntegrationOrderCreate 整合所有快递--订单接口
|
||||
//
|
||||
extern __declspec(dllexport) char* IntegrationOrderCreate(char* orderType, char* requestJSON, char* key, char* secret, char* fromCode);
|
||||
|
||||
// FreeCString 释放C字符串内存
|
||||
//
|
||||
extern __declspec(dllexport) void FreeCString(char* str);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,20 +0,0 @@
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
func main() {
|
||||
|
||||
//ztoJSON := `{"partnerType":"2","orderType":"1","partnerOrderCode":"商家自主定义","accountInfo":{"accountId":"test","accountPassword":"","type":1,"customerId":"GPG1576724269"},"billCode":"","senderInfo":{"senderId":"","senderName":"张三","senderPhone":"010-22226789","senderMobile":"13900000000","senderProvince":"上海","senderCity":"上海市","senderDistrict":"青浦区","senderAddress":"华志路"},"receiveInfo":{"receiverName":"Jone Star","receiverPhone":"021-87654321","receiverMobile":"13500000000","receiverProvince":"上海","receiverCity":"上海市","receiverDistrict":"闵行区","receiverAddress":"申贵路1500号"},"orderVasList":[{"vasType":"COD","vasAmount":100000,"vasPrice":0,"vasDetail":"","accountNo":""}],"hallCode":"S2044","siteCode":"02100","siteName":"上海","summaryInfo":{"size":"","quantity":3,"price":30,"freight":20,"premium":10,"startTime":"2020-12-10 12:00:00","endTime":"2020-12-10 12:00:00"},"remark":"小吉下单","orderItems":[{"name":"","category":"","material":"","size":"","weight":0,"unitprice":0,"quantity":0,"remark":""}],"cabinet":{"address":"","specification":0,"code":""}}`
|
||||
//order, err := ztoOpenCreateOrder(ztoJSON, "2e2858ad0c0006150011b", "5f935e459b9fc50db8220714adcb2c2e")
|
||||
//if err != nil {
|
||||
// fmt.Printf(err.Error())
|
||||
//}
|
||||
//fmt.Println(order)
|
||||
|
||||
jtJSON := `{"customerCode":"J0086474299","digest":"qonqb4O1eNr6VCWS07Ieeg==","network":null,"txlogisticId":"TEST20220704210006","expressType":"EZ","orderType":"1","serviceType":"01","deliveryType":"06","payType":"PP_PM","sender":{"name":"小九","company":null,"postCode":null,"mailBox":null,"mobile":"15546168286","phone":"","countryCode":"CHN","prov":"上海","city":"上海市","area":"青浦区","town":null,"street":null,"address":"庆丰三路28号"},"receiver":{"name":"田丽","company":null,"postCode":null,"mailBox":null,"mobile":"13766245825","phone":"","countryCode":"CHN","prov":"上海","city":"上海市","area":"嘉定区","town":null,"street":null,"address":"站前西路永利酒店斜对面童装店"},"sendStartTime":"2022-07-04 09:00:00","sendEndTime":"2022-07-04 18:00:00","goodsType":"bm000006","length":0,"width":0,"height":0,"weight":"0.02","totalQuantity":0,"itemsValue":null,"priceCurrency":null,"offerFee":null,"remark":null,"items":[{"itemType":null,"itemName":"衣帽鞋服","chineseName":null,"englishName":null,"number":1,"itemValue":null,"priceCurrency":"RMB","desc":null,"itemUrl":null}],"customsInfo":null,"postSiteCode":null,"postSiteName":null,"postSiteAddress":null,"realName":null, "extendInfo": { "isFourLevelAddress": "1" }}`
|
||||
order, err := jtOrderAddOrder(jtJSON, "795641885325495168", "d8c262832bff4b8a9dd6e06147024861")
|
||||
if err != nil {
|
||||
fmt.Printf(err.Error())
|
||||
}
|
||||
fmt.Println(order)
|
||||
}
|
||||
86
expressDeliveryOrder/maintest.go
Normal file
86
expressDeliveryOrder/maintest.go
Normal file
@ -0,0 +1,86 @@
|
||||
package main
|
||||
|
||||
func main() {
|
||||
//apiName := "OMS_EXPRESS_ORDER_CREATE"
|
||||
//content := "{\"orderNo\":\"8885452262\",\"orderSource\":\"****\",\"billType\":\"00\",\"orderType\":\"01\",\"sender\":{\"name\":\"测试名称\",\"tel\":\"0558-45778586\",\"mobile\":\"18775487548\",\"postCode\":\"100001\",\"country\":\"中国\",\"province\":\"安徽\",\"city\":\"合肥\",\"area\":\"泸州\",\"town\":\"测试镇\",\"address\":\"XX街道XX小区XX楼888\"},\"receiver\":{\"name\":\"测试名称\",\"tel\":\"0556-45778586\",\"mobile\":\"15575487548\",\"postCode\":\"100001\",\"country\":\"中国\",\"province\":\"河北\",\"city\":\"湖州\",\"area\":\"江汉\",\"town\":\"收件镇\",\"address\":\"XX街道XX小区XX楼666\",\"safeNo\":\"13466666632-0011\"},\"cargo\":{\"battery\":\"10\",\"goodsType\":\"大件\",\"goodsName\":\"XX物\",\"goodsCount\":10,\"spaceX\":10,\"spaceY\":10,\"spaceZ\":10,\"weight\":10,\"goodsAmount\":\"100\",\"cargoItemList\":[{\"serialNumber\":\"8451234\",\"referenceNumber\":\"88838783634\",\"productId\":\"001\",\"name\":\"小商品\",\"qty\":10,\"unitPrice\":1,\"amount\":10,\"currency\":\"美元\",\"weight\":10,\"remark\":\"无\"}]},\"customer\":{\"siteCode\":\"666666\",\"customerName\":\"666666000001\",\"sitePwd\":\"***\",\"monthCustomerCode\":\"9000000\"},\"internationalAnnex\":{\"internationalProductType\":\"01\",\"customsDeclaration\":false,\"senderCountry\":\"中国\",\"receiverCountry\":\"俄罗斯\"},\"waybillNo\":\"59635456632\",\"assignAnnex\":{\"takeCompanyCode\":\"862456565466\",\"takeUserCode\":\"9000000007\"},\"codValue\":\"2000\",\"freightCollectValue\":\"20\",\"timelessType\":\"01\",\"productType\":\"01\",\"serviceTypeList\":[\"***\"],\"extendFieldMap\":{\"mapValue\":\"***\"},\"remark\":\"无备注\",\"expressDirection\":\"01\",\"createChannel\":\"01\",\"regionType\":\"01\",\"insuredAnnex\":{\"insuredValue\":\"6.66\",\"goodsValue\":\"6.66\"},\"expectValue\":\"10\",\"payModel\":\"1\"}"
|
||||
//stock, err := billCodeQueryUserStock(apiName, content)
|
||||
//if err != nil {
|
||||
// println(err.Error())
|
||||
// return
|
||||
//}
|
||||
//fmt.Println(string(stock))
|
||||
}
|
||||
|
||||
// 构建请求数据
|
||||
//func getStoHeader(apiName string, content string) map[string]string {
|
||||
// secretKey := "2kLMr75pURbVh3FnWzY6SCr7SM6qsoO3"
|
||||
// dataDigest := getStoSignature(content, secretKey)
|
||||
// var param = map[string]string{
|
||||
// "api_name": apiName,
|
||||
// "content": content,
|
||||
// "from_appkey": "CAKgwDceADOybmU",
|
||||
// "from_code": "CAKgwDceADOybmU",
|
||||
// "to_appkey": "sto_oms",
|
||||
// "to_code": "sto_oms",
|
||||
// "data_digest": dataDigest,
|
||||
// }
|
||||
// return param
|
||||
//}
|
||||
//
|
||||
//func requestSto(apiName string, content string) ([]byte, error) {
|
||||
// var url = "http://cloudinter-linkgatewaytest.sto.cn/gateway/link.do"
|
||||
// //url = "https://cloudinter-linkgateway.sto.cn/gateway/link.do"
|
||||
//
|
||||
// // 获取请求参数
|
||||
// params := getStoHeader(apiName, content)
|
||||
//
|
||||
// // 创建 multipart form-data 请求体
|
||||
// body := &bytes.Buffer{}
|
||||
// writer := multipart.NewWriter(body)
|
||||
//
|
||||
// // 添加所有参数到 form-data
|
||||
// for key, value := range params {
|
||||
// err := writer.WriteField(key, value)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// // 关闭 writer 以完成 multipart 数据
|
||||
// err := writer.Close()
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
//
|
||||
// // 创建 HTTP 请求
|
||||
// req, err := http.NewRequest("POST", url, body)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
//
|
||||
// // 设置 Content-Type 头,包含 boundary
|
||||
// req.Header.Set("Content-Type", writer.FormDataContentType())
|
||||
//
|
||||
// // 发送请求
|
||||
// client := &http.Client{}
|
||||
// resp, err := client.Do(req)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// defer resp.Body.Close()
|
||||
//
|
||||
// // 读取响应
|
||||
// responseBody, err := io.ReadAll(resp.Body)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
//
|
||||
// return responseBody, nil
|
||||
//}
|
||||
//
|
||||
//// 生成签名
|
||||
//func getStoSignature(content, secretKey string) string {
|
||||
// text := content + secretKey
|
||||
// hash := md5.Sum([]byte(text))
|
||||
// return base64.StdEncoding.EncodeToString(hash[:])
|
||||
//}
|
||||
31
go.mod
31
go.mod
@ -1,11 +1,12 @@
|
||||
module kfzgw-info
|
||||
|
||||
go 1.25
|
||||
go 1.26
|
||||
|
||||
require (
|
||||
gioui.org v0.9.0
|
||||
github.com/PuerkitoBio/goquery v1.10.3
|
||||
github.com/boombuler/barcode v1.1.0
|
||||
github.com/chromedp/chromedp v0.15.1
|
||||
github.com/disintegration/imaging v1.6.2
|
||||
github.com/elastic/go-elasticsearch/v8 v8.19.0
|
||||
github.com/fogleman/gg v1.3.0
|
||||
@ -15,11 +16,13 @@ require (
|
||||
github.com/makiuchi-d/gozxing v0.1.1
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646
|
||||
github.com/parnurzeal/gorequest v0.3.0
|
||||
github.com/redis/go-redis/v9 v9.18.0
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
|
||||
github.com/tjfoc/gmsm v1.4.1
|
||||
github.com/valyala/fasthttp v1.69.0
|
||||
github.com/xuri/excelize/v2 v2.10.0
|
||||
golang.org/x/image v0.26.0
|
||||
golang.org/x/sys v0.37.0
|
||||
golang.org/x/sys v0.42.0
|
||||
gopkg.in/ini.v1 v1.67.1
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
modernc.org/sqlite v1.44.3
|
||||
@ -28,25 +31,35 @@ require (
|
||||
require (
|
||||
filippo.io/edwards25519 v1.1.0 // indirect
|
||||
gioui.org/shader v1.0.8 // indirect
|
||||
github.com/andybalholm/brotli v1.2.0 // indirect
|
||||
github.com/andybalholm/cascadia v1.3.3 // indirect
|
||||
github.com/bytedance/sonic v1.14.0 // indirect
|
||||
github.com/bytedance/sonic/loader v0.3.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/chromedp/cdproto v0.0.0-20260321001828-e3e3800016bc // indirect
|
||||
github.com/chromedp/sysutil v1.1.0 // indirect
|
||||
github.com/cloudwego/base64x v0.1.6 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/elastic/elastic-transport-go/v8 v8.7.0 // indirect
|
||||
github.com/elazarl/goproxy v1.7.2 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
|
||||
github.com/gin-contrib/sse v1.1.0 // indirect
|
||||
github.com/go-json-experiment/json v0.0.0-20260214004413-d219187c3433 // indirect
|
||||
github.com/go-logr/logr v1.4.2 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.27.0 // indirect
|
||||
github.com/go-text/typesetting v0.3.0 // indirect
|
||||
github.com/gobwas/httphead v0.1.0 // indirect
|
||||
github.com/gobwas/pool v0.2.1 // indirect
|
||||
github.com/gobwas/ws v1.4.0 // indirect
|
||||
github.com/goccy/go-json v0.10.2 // indirect
|
||||
github.com/goccy/go-yaml v1.18.0 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/compress v1.18.2 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||
github.com/kr/pretty v0.3.1 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
@ -66,21 +79,23 @@ require (
|
||||
github.com/tiendc/go-deepcopy v1.7.1 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.3.0 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/xuri/efp v0.0.1 // indirect
|
||||
github.com/xuri/nfp v0.0.2-0.20250530014748-2ddeb826f9a9 // indirect
|
||||
go.opentelemetry.io/otel v1.28.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.28.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.28.0 // indirect
|
||||
go.uber.org/atomic v1.11.0 // indirect
|
||||
go.uber.org/mock v0.5.0 // indirect
|
||||
golang.org/x/arch v0.20.0 // indirect
|
||||
golang.org/x/crypto v0.43.0 // indirect
|
||||
golang.org/x/crypto v0.46.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect
|
||||
golang.org/x/exp/shiny v0.0.0-20250408133849-7e4ce0ab07d0 // indirect
|
||||
golang.org/x/mod v0.29.0 // indirect
|
||||
golang.org/x/net v0.46.0 // indirect
|
||||
golang.org/x/sync v0.17.0 // indirect
|
||||
golang.org/x/text v0.30.0 // indirect
|
||||
golang.org/x/tools v0.38.0 // indirect
|
||||
golang.org/x/mod v0.30.0 // indirect
|
||||
golang.org/x/net v0.48.0 // indirect
|
||||
golang.org/x/sync v0.19.0 // indirect
|
||||
golang.org/x/text v0.32.0 // indirect
|
||||
golang.org/x/tools v0.39.0 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||
google.golang.org/protobuf v1.36.9 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
|
||||
|
||||
70
go.sum
70
go.sum
@ -11,15 +11,29 @@ gioui.org/shader v1.0.8/go.mod h1:mWdiME581d/kV7/iEhLmUgUK5iZ09XR5XpduXzbePVM=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/PuerkitoBio/goquery v1.10.3 h1:pFYcNSqHxBD06Fpj/KsbStFRsgRATgnf3LeXiUkhzPo=
|
||||
github.com/PuerkitoBio/goquery v1.10.3/go.mod h1:tMUX0zDMHXYlAQk6p35XxQMqMweEKB7iK7iLNd4RH4Y=
|
||||
github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=
|
||||
github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
|
||||
github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kktS1LM=
|
||||
github.com/andybalholm/cascadia v1.3.3/go.mod h1:xNd9bqTn98Ln4DwST8/nG+H0yuB8Hmgu1YHNnWw0GeA=
|
||||
github.com/boombuler/barcode v1.1.0 h1:ChaYjBR63fr4LFyGn8E8nt7dBSt3MiU3zMOZqFvVkHo=
|
||||
github.com/boombuler/barcode v1.1.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
||||
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
||||
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
|
||||
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
|
||||
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
|
||||
github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ=
|
||||
github.com/bytedance/sonic v1.14.0/go.mod h1:WoEbx8WTcFJfzCe0hbmyTGrfjt8PzNEBdxlNUO24NhA=
|
||||
github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA=
|
||||
github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/chromedp/cdproto v0.0.0-20260321001828-e3e3800016bc h1:wkN/LMi5vc60pBRWx6qpbk/aEvq3/ZVNpnMvsw8PVVU=
|
||||
github.com/chromedp/cdproto v0.0.0-20260321001828-e3e3800016bc/go.mod h1:cbyjALe67vDvlvdiG9369P8w5U2w6IshwtyD2f2Tvag=
|
||||
github.com/chromedp/chromedp v0.15.1 h1:EJWiPm7BNqDqjYy6U0lTSL5wNH+iNt9GjC3a4gfjNyQ=
|
||||
github.com/chromedp/chromedp v0.15.1/go.mod h1:CdTHtUqD/dqaFw/cvFWtTydoEQS44wLBuwbMR9EkOY4=
|
||||
github.com/chromedp/sysutil v1.1.0 h1:PUFNv5EcprjqXZD9nJb9b/c9ibAbxiYo4exNWZyipwM=
|
||||
github.com/chromedp/sysutil v1.1.0/go.mod h1:WiThHUdltqCNKGc4gaU50XgYjwjYIhKWoHGPTUfWTJ8=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
|
||||
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
|
||||
@ -28,6 +42,8 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||
github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
|
||||
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
@ -49,6 +65,8 @@ github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w
|
||||
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
|
||||
github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk=
|
||||
github.com/gin-gonic/gin v1.11.0/go.mod h1:+iq/FyxlGzII0KHiBGjuNn4UNENUlKbGlNmc+W50Dls=
|
||||
github.com/go-json-experiment/json v0.0.0-20260214004413-d219187c3433 h1:vymEbVwYFP/L05h5TKQxvkXoKxNvTpjxYKdF1Nlwuao=
|
||||
github.com/go-json-experiment/json v0.0.0-20260214004413-d219187c3433/go.mod h1:tphK2c80bpPhMOI4v6bIc2xWywPfbqi1Z06+RcrMkDg=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
@ -68,6 +86,12 @@ github.com/go-text/typesetting v0.3.0 h1:OWCgYpp8njoxSRpwrdd1bQOxdjOXDj9Rqart9ML
|
||||
github.com/go-text/typesetting v0.3.0/go.mod h1:qjZLkhRgOEYMhU9eHBr3AR4sfnGJvOXNLt8yRAySFuY=
|
||||
github.com/go-text/typesetting-utils v0.0.0-20241103174707-87a29e9e6066 h1:qCuYC+94v2xrb1PoS4NIDe7DGYtLnU2wWiQe9a1B1c0=
|
||||
github.com/go-text/typesetting-utils v0.0.0-20241103174707-87a29e9e6066/go.mod h1:DDxDdQEnB70R8owOx3LVpEFvpMK9eeH1o2r0yZhFI9o=
|
||||
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
|
||||
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
|
||||
github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
|
||||
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
||||
github.com/gobwas/ws v1.4.0 h1:CTaoG1tojrh4ucGPcoJFiAQUAsEWekEWvLy7GsVNqGs=
|
||||
github.com/gobwas/ws v1.4.0/go.mod h1:G3gNqMNtPppf5XUz7O4shetPpcZ1VJ7zt18dlUeakrc=
|
||||
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
|
||||
@ -105,12 +129,16 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk=
|
||||
github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80 h1:6Yzfa6GP0rIo/kULo2bwGEkFvCePZ3qHDDTC3/J9Swo=
|
||||
github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs=
|
||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||
github.com/makiuchi-d/gozxing v0.1.1 h1:xxqijhoedi+/lZlhINteGbywIrewVdVv2wl9r5O9S1I=
|
||||
@ -127,6 +155,8 @@ github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOF
|
||||
github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
|
||||
github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde h1:x0TT0RDC7UhAVbbWWBzr41ElhJx5tXPWkIHA2HWPRuw=
|
||||
github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0=
|
||||
github.com/parnurzeal/gorequest v0.3.0 h1:SoFyqCDC9COr1xuS6VA8fC8RU7XyrJZN2ona1kEX7FI=
|
||||
github.com/parnurzeal/gorequest v0.3.0/go.mod h1:3Kh2QUMJoqw3icWAecsyzkpY7UzRfDhbRdTjtNwNiUE=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||
@ -141,6 +171,8 @@ github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
|
||||
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
|
||||
github.com/quic-go/quic-go v0.54.0 h1:6s1YB9QotYI6Ospeiguknbp2Znb/jZYjZLRXn9kMQBg=
|
||||
github.com/quic-go/quic-go v0.54.0/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY=
|
||||
github.com/redis/go-redis/v9 v9.18.0 h1:pMkxYPkEbMPwRdenAzUNyFNrDgHx9U+DrBabWNfSRQs=
|
||||
github.com/redis/go-redis/v9 v9.18.0/go.mod h1:k3ufPphLU5YXwNTUcCRXGxUoF1fqxnhFQmscfkCoDA0=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/richardlehane/mscfb v1.0.4 h1:WULscsljNPConisD5hR0+OyZjwK46Pfyr6mPu5ZawpM=
|
||||
@ -175,13 +207,21 @@ github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||
github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA=
|
||||
github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
|
||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasthttp v1.69.0 h1:fNLLESD2SooWeh2cidsuFtOcrEi4uB4m1mPrkJMZyVI=
|
||||
github.com/valyala/fasthttp v1.69.0/go.mod h1:4wA4PfAraPlAsJ5jMSqCE2ug5tqUPwKXxVj8oNECGcw=
|
||||
github.com/xuri/efp v0.0.1 h1:fws5Rv3myXyYni8uwj2qKjVaRP30PdjeYe2Y6FDsCL8=
|
||||
github.com/xuri/efp v0.0.1/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI=
|
||||
github.com/xuri/excelize/v2 v2.10.0 h1:8aKsP7JD39iKLc6dH5Tw3dgV3sPRh8uRVXu/fMstfW4=
|
||||
github.com/xuri/excelize/v2 v2.10.0/go.mod h1:SC5TzhQkaOsTWpANfm+7bJCldzcnU/jrhqkTi/iBHBU=
|
||||
github.com/xuri/nfp v0.0.2-0.20250530014748-2ddeb826f9a9 h1:+C0TIdyyYmzadGaL/HBLbf3WdLgC29pgyhTjAT/0nuE=
|
||||
github.com/xuri/nfp v0.0.2-0.20250530014748-2ddeb826f9a9/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ=
|
||||
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
|
||||
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0=
|
||||
github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA=
|
||||
go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo=
|
||||
go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4=
|
||||
go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q=
|
||||
@ -190,6 +230,8 @@ go.opentelemetry.io/otel/sdk v1.21.0 h1:FTt8qirL1EysG6sTQRZ5TokkU8d0ugCj8htOgThZ
|
||||
go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E=
|
||||
go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g=
|
||||
go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI=
|
||||
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
||||
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||
go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
|
||||
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
|
||||
golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c=
|
||||
@ -202,8 +244,8 @@ golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliY
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||
golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
|
||||
golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
|
||||
golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
|
||||
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 h1:mgKeJMpvi0yx/sU5GsxQ7p6s2wtOnGAHZWCHUM4KGzY=
|
||||
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546/go.mod h1:j/pmGrbnkbPtQfxEe5D0VQhZC6qKbfKifgD0oM7sR70=
|
||||
@ -220,8 +262,8 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=
|
||||
golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=
|
||||
golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk=
|
||||
golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@ -237,8 +279,8 @@ golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
||||
golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4=
|
||||
golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210=
|
||||
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
|
||||
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@ -249,8 +291,8 @@ golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
|
||||
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@ -266,8 +308,8 @@ golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
|
||||
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
|
||||
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
@ -286,8 +328,8 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
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/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
|
||||
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
@ -298,8 +340,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
|
||||
golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
|
||||
golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ=
|
||||
golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
|
||||
BIN
image/123.png
BIN
image/123.png
Binary file not shown.
|
Before Width: | Height: | Size: 544 KiB |
Binary file not shown.
@ -86,43 +86,17 @@ typedef struct { void *data; GoInt len; GoInt cap; } GoSlice;
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
|
||||
// =================== C 导出函数 =======================
|
||||
// 检测图片纯白占比
|
||||
//
|
||||
extern __declspec(dllexport) char* ProcessImage(char* jsonConfig);
|
||||
|
||||
// 根据原始图片生成新的白底图片
|
||||
//
|
||||
extern __declspec(dllexport) char* CreateWhiteBottomCenteredImage(char* jsonConfig, int width, int height);
|
||||
|
||||
// 根据高度生成等比例图片
|
||||
//
|
||||
extern __declspec(dllexport) char* ResizeToHeightQuality(char* jsonConfig, int targetHeight);
|
||||
|
||||
// 去掉白边并转PNG图片工具
|
||||
//
|
||||
extern __declspec(dllexport) char* RemoveWhiteBorderAndPNG(char* jsonConfig);
|
||||
|
||||
// ResizeWTToHeightQuality 图片缩放
|
||||
//
|
||||
extern __declspec(dllexport) char* ResizeWTToHeightQuality(char* jsonConfig, int dsWidth, int dsHeight);
|
||||
|
||||
// CropImage 图片裁切
|
||||
//
|
||||
extern __declspec(dllexport) char* CropImage(char* jsonConfig, int x, int y, int width, int height);
|
||||
|
||||
// CreateChineseTextImage 创建带中文字体的文本图片,支持超出部分显示...
|
||||
//
|
||||
extern __declspec(dllexport) char* CreateChineseTextImage(char* text, int width, int height, char* fontSize, char* outputPath);
|
||||
|
||||
// DrawChineseInfo 绘制书名,作者,出版社信息
|
||||
//
|
||||
extern __declspec(dllexport) char* DrawChineseInfo(char* filePath, char* title, char* author, char* publisher, char* outputPath);
|
||||
|
||||
// 导出函数:释放C字符串内存
|
||||
//
|
||||
extern __declspec(dllexport) void FreeCString(char* str);
|
||||
extern char* ProcessImage(char* jsonConfig);
|
||||
extern char* CreateWhiteBottomCenteredImage(char* jsonConfig, int width, int height);
|
||||
extern char* ResizeToHeightQuality(char* jsonConfig, int targetHeight);
|
||||
extern char* RemoveWhiteBorderAndPNG(char* jsonConfig);
|
||||
extern char* ResizeWTToHeightQuality(char* jsonConfig, int dsWidth, int dsHeight);
|
||||
extern char* CropImage(char* jsonConfig, int x, int y, int width, int height);
|
||||
extern char* CreateChineseTextImage(char* text, int width, int height, char* fontSize, char* outputPath);
|
||||
extern char* DrawChineseInfo(char* filePath, char* title, char* author, char* publisher, char* outputPath);
|
||||
extern char* GenerateBarcode(char* barcodeType, char* content, char* filename);
|
||||
extern char* AddWatermarkFromURLEx(char* jsonConfig);
|
||||
extern void FreeCString(char* str);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
609
image/image.go
609
image/image.go
@ -3,6 +3,8 @@ package main
|
||||
// #include <stdlib.h>
|
||||
import "C"
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/boombuler/barcode"
|
||||
@ -31,7 +33,10 @@ import (
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/valyala/fasthttp"
|
||||
)
|
||||
|
||||
// Config 配置结构体
|
||||
@ -250,11 +255,10 @@ func createWhiteBottomCenteredImage(config *Config, width, height int) (string,
|
||||
|
||||
// 设置背景颜色
|
||||
var bgColor color.Color
|
||||
|
||||
bgColor = color.RGBA{255, 255, 255, 255} // 白色
|
||||
bgColor = color.RGBA{R: 0, G: 0, B: 0, A: 0} // 白色
|
||||
|
||||
// 填充透明背景
|
||||
draw.Draw(dst, dst.Bounds(), &image.Uniform{bgColor}, image.Point{}, draw.Src)
|
||||
draw.Draw(dst, dst.Bounds(), &image.Uniform{C: bgColor}, image.Point{}, draw.Src)
|
||||
|
||||
// 计算居中位置
|
||||
srcBounds := img.Bounds()
|
||||
@ -305,7 +309,10 @@ func resizeToHeightQuality(config *Config, targetHeight int) (string, error) {
|
||||
// 保存图片到指定目录下
|
||||
filename := filepath.Base(config.FileName)
|
||||
destPath := filepath.Join(config.OutputDir, config.EqualHeightDir, filename)
|
||||
saveImage(destPath, imageNew, format)
|
||||
err = saveImage(destPath, imageNew, format)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("保存图片失败: %v", err)
|
||||
}
|
||||
|
||||
return destPath, nil
|
||||
}
|
||||
@ -1689,6 +1696,503 @@ func absDiff(a, b uint8) uint8 {
|
||||
return b - a
|
||||
}
|
||||
|
||||
// =========================== 添加水印 ===============================
|
||||
|
||||
// WatermarkConfig 水印配置结构体
|
||||
type WatermarkConfig struct {
|
||||
SourceImageURL string // 源图片URL地址
|
||||
WatermarkURL string // 水印图片URL地址
|
||||
WatermarkBase64 string // 水印图片base64编码字符串(新增,优先使用)
|
||||
Opacity float64 // 不透明度 (0.0-1.0)
|
||||
Position string // 位置: center, top-left, top-right, bottom-left, bottom-right, tile
|
||||
TileSpacing int // 平铺时的间距
|
||||
Scale float64 // 水印缩放比例 (0.0-1.0)
|
||||
Rotation float64 // 旋转角度 (度数)
|
||||
XOffset int // X轴偏移量AddWatermarkFromURLEx
|
||||
YOffset int // Y轴偏移量
|
||||
Timeout int // 下载超时时间(秒),默认30秒
|
||||
OutputFormat string // 输出格式: "jpeg", "png", "auto"(默认auto,根据源图片格式)
|
||||
JPEGQuality int // JPEG质量 (1-100),默认95
|
||||
TargetWidth int // 目标宽度(0表示不缩放)
|
||||
TargetHeight int // 目标高度(0表示不缩放)
|
||||
ResizeMode string // 缩放模式: "fit"(适应,保持比例,可能有黑边), "fill"(填充,裁剪), "stretch"(拉伸)
|
||||
}
|
||||
|
||||
// 将客户端声明为全局变量或缓存
|
||||
var httpClient = &fasthttp.Client{
|
||||
MaxConnsPerHost: 100,
|
||||
ReadTimeout: 30 * time.Second,
|
||||
WriteTimeout: 30 * time.Second,
|
||||
}
|
||||
|
||||
// loadImageFromURL 直接从URL加载图片
|
||||
func loadImageFromURL(url string, timeout int) (image.Image, string, error) {
|
||||
// 设置默认超时
|
||||
if timeout <= 0 {
|
||||
timeout = 120
|
||||
}
|
||||
|
||||
// 创建请求和响应对象
|
||||
req := fasthttp.AcquireRequest()
|
||||
resp := fasthttp.AcquireResponse()
|
||||
defer fasthttp.ReleaseRequest(req)
|
||||
defer fasthttp.ReleaseResponse(resp)
|
||||
|
||||
// 设置请求URL和方法
|
||||
req.SetRequestURI(url)
|
||||
req.Header.SetMethod("GET")
|
||||
|
||||
// 设置请求头
|
||||
req.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7")
|
||||
req.Header.Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6")
|
||||
req.Header.Set("Cache-Control", "max-age=0")
|
||||
req.Header.Set("If-Modified-Since", "Mon, 09 Mar 2026 07:43:59 GMT")
|
||||
req.Header.Set("Priority", "u=0, i")
|
||||
req.Header.Set("Sec-Ch-Ua", `"Not:A-Brand";v="99", "Microsoft Edge";v="145", "Chromium";v="145"`)
|
||||
req.Header.Set("Sec-Ch-Ua-Mobile", "?0")
|
||||
req.Header.Set("Sec-Ch-Ua-Platform", "Windows")
|
||||
req.Header.Set("Sec-Fetch-Dest", "document")
|
||||
req.Header.Set("Sec-Fetch-Mode", "navigate")
|
||||
req.Header.Set("Sec-Fetch-Site", "none")
|
||||
req.Header.Set("Sec-Fetch-User", "?1")
|
||||
req.Header.Set("Upgrade-Insecure-Requests", "1")
|
||||
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36 Edg/145.0.0.0")
|
||||
|
||||
// 发送请求
|
||||
err := httpClient.Do(req, resp)
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("请求失败: %v\n", err)
|
||||
}
|
||||
|
||||
// 复制body数据,避免引用问题
|
||||
body := make([]byte, len(resp.Body()))
|
||||
copy(body, resp.Body())
|
||||
|
||||
// 直接从响应体解码图片
|
||||
img, format, err := image.Decode(bytes.NewReader(body))
|
||||
// 主动释放body内存
|
||||
body = nil
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("解码图片失败: %v", err)
|
||||
}
|
||||
|
||||
return img, format, nil
|
||||
}
|
||||
|
||||
// 添加新的辅助函数:从base64字符串加载图片
|
||||
func loadImageFromBase64(base64Str string) (image.Image, string, error) {
|
||||
// 移除可能的 data:image/xxx;base64, 前缀
|
||||
base64Str = strings.TrimPrefix(base64Str, "data:image/jpeg;base64,")
|
||||
base64Str = strings.TrimPrefix(base64Str, "data:image/jpg;base64,")
|
||||
base64Str = strings.TrimPrefix(base64Str, "data:image/png;base64,")
|
||||
base64Str = strings.TrimPrefix(base64Str, "data:image/gif;base64,")
|
||||
base64Str = strings.TrimPrefix(base64Str, "data:image/webp;base64,")
|
||||
|
||||
// 解码base64
|
||||
imgData, err := base64.StdEncoding.DecodeString(base64Str)
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("解码base64失败: %v", err)
|
||||
}
|
||||
|
||||
// 检测图片格式
|
||||
format := detectImageFormat(imgData)
|
||||
|
||||
// 解码图片
|
||||
img, _, err := image.Decode(bytes.NewReader(imgData))
|
||||
// 主动释放数据内存
|
||||
imgData = nil
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("解码base64图片失败: %v", err)
|
||||
}
|
||||
|
||||
return img, format, nil
|
||||
}
|
||||
|
||||
// 检测图片格式的辅助函数
|
||||
func detectImageFormat(data []byte) string {
|
||||
if len(data) < 12 {
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
// PNG: 137 80 78 71 13 10 26 10
|
||||
if len(data) >= 8 && data[0] == 0x89 && data[1] == 0x50 && data[2] == 0x4E && data[3] == 0x47 {
|
||||
return "png"
|
||||
}
|
||||
|
||||
// JPEG: 255 216 255
|
||||
if len(data) >= 3 && data[0] == 0xFF && data[1] == 0xD8 && data[2] == 0xFF {
|
||||
return "jpg"
|
||||
}
|
||||
|
||||
// GIF: 71 73 70
|
||||
if len(data) >= 3 && data[0] == 0x47 && data[1] == 0x49 && data[2] == 0x46 {
|
||||
return "gif"
|
||||
}
|
||||
|
||||
// WEBP: 82 73 70 70
|
||||
if len(data) >= 12 && data[0] == 0x52 && data[1] == 0x49 && data[2] == 0x46 && data[3] == 0x46 {
|
||||
return "webp"
|
||||
}
|
||||
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
// 判断HTTP状态码是否值得重试
|
||||
func isRetryableStatus(statusCode int) bool {
|
||||
// 5xx服务器错误或429限流可以重试
|
||||
return statusCode >= 500 || statusCode == 429
|
||||
}
|
||||
|
||||
// AddWatermarkFromURL 从URL加载图片添加水印并返回字节集
|
||||
func AddWatermarkFromURL(config WatermarkConfig) ([]byte, string, error) {
|
||||
// 确保函数退出时释放内存
|
||||
defer runtime.GC() // 可选,在低内存场景下使用
|
||||
|
||||
// 加载源图片
|
||||
srcImg, srcFormat, err := loadImageFromURL(config.SourceImageURL, config.Timeout)
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("加载源图片失败: %v", err)
|
||||
}
|
||||
|
||||
// 等比缩放原图到 TargetWidth x TargetHeight 范围内
|
||||
if config.TargetWidth > 0 || config.TargetHeight > 0 {
|
||||
srcW := srcImg.Bounds().Dx()
|
||||
srcH := srcImg.Bounds().Dy()
|
||||
var newW, newH uint
|
||||
if config.TargetWidth > 0 && config.TargetHeight > 0 {
|
||||
scaleX := float64(config.TargetWidth) / float64(srcW)
|
||||
scaleY := float64(config.TargetHeight) / float64(srcH)
|
||||
scale := math.Min(scaleX, scaleY)
|
||||
newW = uint(float64(srcW) * scale)
|
||||
newH = uint(float64(srcH) * scale)
|
||||
} else if config.TargetWidth > 0 {
|
||||
newW = uint(config.TargetWidth)
|
||||
newH = uint(float64(srcH) * float64(config.TargetWidth) / float64(srcW))
|
||||
} else {
|
||||
newH = uint(config.TargetHeight)
|
||||
newW = uint(float64(srcW) * float64(config.TargetHeight) / float64(srcH))
|
||||
}
|
||||
srcImg = resize.Resize(newW, newH, srcImg, resize.Lanczos3)
|
||||
}
|
||||
|
||||
// 加载水印图片(支持URL或base64)
|
||||
var watermarkImg image.Image
|
||||
//var watermarkFormat string // 没用了
|
||||
|
||||
// 优先使用 base64
|
||||
if config.WatermarkBase64 != "" {
|
||||
watermarkImg, _, err = loadImageFromBase64(config.WatermarkBase64)
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("从base64加载水印图片失败: %v", err)
|
||||
}
|
||||
} else if config.WatermarkURL != "" {
|
||||
// 检查是否为base64格式的URL(以data:image开头)
|
||||
if strings.HasPrefix(config.WatermarkURL, "data:image/") {
|
||||
watermarkImg, _, err = loadImageFromBase64(config.WatermarkURL)
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("从base64 URL加载水印图片失败: %v", err)
|
||||
}
|
||||
} else {
|
||||
// 普通URL
|
||||
watermarkImg, _, err = loadImageFromURL(config.WatermarkURL, config.Timeout)
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("从URL加载水印图片失败: %v", err)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return nil, "", fmt.Errorf("必须提供水印图片(WatermarkURL或WatermarkBase64)")
|
||||
}
|
||||
|
||||
// 创建目标图片(RGBA格式以支持透明)
|
||||
// 画布尺寸 = 水印尺寸
|
||||
watermarkBounds := watermarkImg.Bounds()
|
||||
dst := image.NewRGBA(watermarkBounds)
|
||||
|
||||
// 先绘制源图片(居中)
|
||||
srcBounds := srcImg.Bounds()
|
||||
x := (watermarkBounds.Dx() - srcBounds.Dx()) / 2
|
||||
y := (watermarkBounds.Dy() - srcBounds.Dy()) / 2
|
||||
draw.Draw(dst, image.Rect(x, y, x+srcBounds.Dx(), y+srcBounds.Dy()), srcImg, image.Point{}, draw.Src)
|
||||
|
||||
// 处理水印
|
||||
err = applyWatermark(dst, watermarkImg, config)
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("应用水印失败: %v", err)
|
||||
}
|
||||
|
||||
// 按目标尺寸缩放
|
||||
finalImg := resizeOutputImage(dst, config)
|
||||
|
||||
// 确定输出格式
|
||||
outputFormat := config.OutputFormat
|
||||
if outputFormat == "" || outputFormat == "auto" {
|
||||
outputFormat = srcFormat
|
||||
}
|
||||
|
||||
// 将图片编码为字节集
|
||||
imgBytes, err := encodeImageToBytes(finalImg, outputFormat, config.JPEGQuality)
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("编码图片失败: %v", err)
|
||||
}
|
||||
|
||||
return imgBytes, outputFormat, nil
|
||||
}
|
||||
|
||||
// encodeImageToBytes 将图片编码为字节集
|
||||
func encodeImageToBytes(img image.Image, format string, quality int) ([]byte, error) {
|
||||
var buf bytes.Buffer
|
||||
|
||||
switch strings.ToLower(format) {
|
||||
case "jpeg", "jpg":
|
||||
// 设置默认质量
|
||||
if quality <= 0 || quality > 100 {
|
||||
quality = 95
|
||||
}
|
||||
err := jpeg.Encode(&buf, img, &jpeg.Options{Quality: quality})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("JPEG编码失败: %v", err)
|
||||
}
|
||||
case "png":
|
||||
err := png.Encode(&buf, img)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("PNG编码失败: %v", err)
|
||||
}
|
||||
default:
|
||||
// 默认使用PNG
|
||||
err := png.Encode(&buf, img)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("PNG编码失败: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// applyWatermark 应用水印到图片
|
||||
func applyWatermark(dst *image.RGBA, watermark image.Image, config WatermarkConfig) error {
|
||||
// 缩放水印
|
||||
watermark = scaleWatermark(watermark, config.Scale)
|
||||
|
||||
// 根据位置绘制水印
|
||||
switch config.Position {
|
||||
case "tile":
|
||||
drawTileWatermark(dst, watermark, config)
|
||||
default:
|
||||
drawSingleWatermark(dst, watermark, config)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// scaleWatermark 缩放水印
|
||||
func scaleWatermark(watermark image.Image, scale float64) image.Image {
|
||||
if scale <= 0 || scale >= 1 {
|
||||
return watermark
|
||||
}
|
||||
|
||||
bounds := watermark.Bounds()
|
||||
newWidth := uint(float64(bounds.Dx()) * scale)
|
||||
newHeight := uint(float64(bounds.Dy()) * scale)
|
||||
|
||||
if newWidth > 0 && newHeight > 0 {
|
||||
return resize.Resize(newWidth, newHeight, watermark, resize.Lanczos3)
|
||||
}
|
||||
return watermark
|
||||
}
|
||||
|
||||
// drawSingleWatermark 绘制单个水印
|
||||
func drawSingleWatermark(dst *image.RGBA, watermark image.Image, config WatermarkConfig) {
|
||||
bounds := dst.Bounds()
|
||||
watermarkBounds := watermark.Bounds()
|
||||
watermarkWidth := watermarkBounds.Dx()
|
||||
watermarkHeight := watermarkBounds.Dy()
|
||||
|
||||
// 计算位置
|
||||
var x, y int
|
||||
|
||||
switch config.Position {
|
||||
case "center":
|
||||
x = (bounds.Dx()-watermarkWidth)/2 + config.XOffset
|
||||
y = (bounds.Dy()-watermarkHeight)/2 + config.YOffset
|
||||
case "top-left":
|
||||
x = config.XOffset
|
||||
y = config.YOffset
|
||||
case "top-right":
|
||||
x = bounds.Dx() - watermarkWidth - config.XOffset
|
||||
y = config.YOffset
|
||||
case "bottom-left":
|
||||
x = config.XOffset
|
||||
y = bounds.Dy() - watermarkHeight - config.YOffset
|
||||
case "bottom-right":
|
||||
x = bounds.Dx() - watermarkWidth - config.XOffset
|
||||
y = bounds.Dy() - watermarkHeight - config.YOffset
|
||||
default: // 默认居中
|
||||
x = (bounds.Dx()-watermarkWidth)/2 + config.XOffset
|
||||
y = (bounds.Dy()-watermarkHeight)/2 + config.YOffset
|
||||
}
|
||||
|
||||
// 确保不超出边界
|
||||
x = max(0, min(x, bounds.Dx()-watermarkWidth))
|
||||
y = max(0, min(y, bounds.Dy()-watermarkHeight))
|
||||
|
||||
// 绘制水印
|
||||
drawWatermarkWithOpacity(dst, watermark, x, y, config.Opacity)
|
||||
}
|
||||
|
||||
// drawTileWatermark 平铺水印
|
||||
func drawTileWatermark(dst *image.RGBA, watermark image.Image, config WatermarkConfig) {
|
||||
bounds := dst.Bounds()
|
||||
watermarkBounds := watermark.Bounds()
|
||||
watermarkWidth := watermarkBounds.Dx()
|
||||
watermarkHeight := watermarkBounds.Dy()
|
||||
|
||||
spacing := config.TileSpacing
|
||||
if spacing < 0 {
|
||||
spacing = 0
|
||||
}
|
||||
|
||||
stepX := watermarkWidth + spacing
|
||||
stepY := watermarkHeight + spacing
|
||||
|
||||
// 计算起始偏移,使水印均匀分布
|
||||
startX := (bounds.Dx() % stepX) / 2
|
||||
startY := (bounds.Dy() % stepY) / 2
|
||||
|
||||
for y := startY; y < bounds.Dy(); y += stepY {
|
||||
for x := startX; x < bounds.Dx(); x += stepX {
|
||||
drawWatermarkWithOpacity(dst, watermark, x, y, config.Opacity)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// drawWatermarkWithOpacity 绘制带透明度的水印
|
||||
func drawWatermarkWithOpacity(dst *image.RGBA, watermark image.Image, x, y int, opacity float64) {
|
||||
if opacity < 0 {
|
||||
opacity = 0
|
||||
}
|
||||
if opacity > 1 {
|
||||
opacity = 1
|
||||
}
|
||||
|
||||
watermarkBounds := watermark.Bounds()
|
||||
for wy := 0; wy < watermarkBounds.Dy(); wy++ {
|
||||
for wx := 0; wx < watermarkBounds.Dx(); wx++ {
|
||||
// 计算目标位置
|
||||
dx := x + wx
|
||||
dy := y + wy
|
||||
|
||||
// 确保在目标图片范围内
|
||||
if dx < 0 || dx >= dst.Bounds().Dx() || dy < 0 || dy >= dst.Bounds().Dy() {
|
||||
continue
|
||||
}
|
||||
|
||||
// 获取水印像素
|
||||
wColor := watermark.At(wx, wy)
|
||||
wr, wg, wb, wa := wColor.RGBA()
|
||||
|
||||
// 如果有透明度,考虑水印本身的透明度
|
||||
if wa > 0 {
|
||||
// 转换为8位
|
||||
wr8 := uint8(wr >> 8)
|
||||
wg8 := uint8(wg >> 8)
|
||||
wb8 := uint8(wb >> 8)
|
||||
wa8 := uint8(wa >> 8)
|
||||
|
||||
// 获取目标像素
|
||||
dstColor := dst.At(dx, dy)
|
||||
dr, dg, db, _ := dstColor.RGBA()
|
||||
dr8 := uint8(dr >> 8)
|
||||
dg8 := uint8(dg >> 8)
|
||||
db8 := uint8(db >> 8)
|
||||
|
||||
// 混合颜色(考虑水印透明度和设置的不透明度)
|
||||
alpha := float64(wa8) / 255.0 * opacity
|
||||
|
||||
r := uint8(float64(dr8)*(1-alpha) + float64(wr8)*alpha)
|
||||
g := uint8(float64(dg8)*(1-alpha) + float64(wg8)*alpha)
|
||||
b := uint8(float64(db8)*(1-alpha) + float64(wb8)*alpha)
|
||||
|
||||
dst.Set(dx, dy, color.RGBA{r, g, b, 255})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// resizeOutputImage 根据配置缩放输出图片
|
||||
func resizeOutputImage(img image.Image, config WatermarkConfig) image.Image {
|
||||
if config.TargetWidth <= 0 && config.TargetHeight <= 0 {
|
||||
return img
|
||||
}
|
||||
|
||||
srcBounds := img.Bounds()
|
||||
srcW := srcBounds.Dx()
|
||||
srcH := srcBounds.Dy()
|
||||
|
||||
// 如果只设了一个维度,另一个按比例算
|
||||
targetW := config.TargetWidth
|
||||
targetH := config.TargetHeight
|
||||
if targetW <= 0 {
|
||||
targetW = int(float64(targetH) * float64(srcW) / float64(srcH))
|
||||
}
|
||||
if targetH <= 0 {
|
||||
targetH = int(float64(targetW) * float64(srcH) / float64(srcW))
|
||||
}
|
||||
|
||||
switch config.ResizeMode {
|
||||
case "fit":
|
||||
return resizeFit(img, srcW, srcH, targetW, targetH)
|
||||
case "fill":
|
||||
return resizeFill(img, srcW, srcH, targetW, targetH)
|
||||
case "stretch":
|
||||
fallthrough
|
||||
default:
|
||||
return resize.Resize(uint(targetW), uint(targetH), img, resize.Lanczos3)
|
||||
}
|
||||
}
|
||||
|
||||
// resizeFit 等比缩放适应,多出部分填白
|
||||
func resizeFit(img image.Image, srcW, srcH, targetW, targetH int) image.Image {
|
||||
scaleX := float64(targetW) / float64(srcW)
|
||||
scaleY := float64(targetH) / float64(srcH)
|
||||
scale := math.Min(scaleX, scaleY)
|
||||
|
||||
newW := int(float64(srcW) * scale)
|
||||
newH := int(float64(srcH) * scale)
|
||||
|
||||
scaled := resize.Resize(uint(newW), uint(newH), img, resize.Lanczos3)
|
||||
|
||||
// 居中放到白色画布上
|
||||
dst := image.NewRGBA(image.Rect(0, 0, targetW, targetH))
|
||||
draw.Draw(dst, dst.Bounds(), &image.Uniform{C: color.White}, image.Point{}, draw.Src)
|
||||
|
||||
x := (targetW - newW) / 2
|
||||
y := (targetH - newH) / 2
|
||||
draw.Draw(dst, image.Rect(x, y, x+newW, y+newH), scaled, image.Point{}, draw.Over)
|
||||
|
||||
return dst
|
||||
}
|
||||
|
||||
// resizeFill 等比缩放覆盖,溢出部分居中裁剪
|
||||
func resizeFill(img image.Image, srcW, srcH, targetW, targetH int) image.Image {
|
||||
scaleX := float64(targetW) / float64(srcW)
|
||||
scaleY := float64(targetH) / float64(srcH)
|
||||
scale := math.Max(scaleX, scaleY)
|
||||
|
||||
newW := int(float64(srcW) * scale)
|
||||
newH := int(float64(srcH) * scale)
|
||||
|
||||
scaled := resize.Resize(uint(newW), uint(newH), img, resize.Lanczos3)
|
||||
|
||||
// 居中裁剪
|
||||
cropX := (newW - targetW) / 2
|
||||
cropY := (newH - targetH) / 2
|
||||
|
||||
dst := image.NewRGBA(image.Rect(0, 0, targetW, targetH))
|
||||
draw.Draw(dst, dst.Bounds(), scaled, image.Point{X: cropX, Y: cropY}, draw.Src)
|
||||
|
||||
return dst
|
||||
}
|
||||
|
||||
// =================== C 导出函数 =======================
|
||||
// 检测图片纯白占比
|
||||
//
|
||||
@ -1847,14 +2351,61 @@ func GenerateBarcode(barcodeType, content, filename *C.char) *C.char {
|
||||
contentStr := C.GoString(content)
|
||||
filenameStr := C.GoString(filename)
|
||||
|
||||
filename, err := generateBarcode(barcodeTypeStr, contentStr, filenameStr)
|
||||
barcodeFilename, err := generateBarcode(barcodeTypeStr, contentStr, filenameStr)
|
||||
if err != nil {
|
||||
return C.CString(fmt.Sprintf("%v", err))
|
||||
}
|
||||
return C.CString(filename)
|
||||
return C.CString(barcodeFilename)
|
||||
}
|
||||
|
||||
// 导出函数:释放C字符串内存
|
||||
// AddWatermarkFromURLEx 从URL添加水印并返回字节集(C导出函数)
|
||||
//
|
||||
//export AddWatermarkFromURLEx
|
||||
func AddWatermarkFromURLEx(jsonConfig *C.char) *C.char {
|
||||
configStr := C.GoString(jsonConfig)
|
||||
|
||||
var config WatermarkConfig
|
||||
if err := json.Unmarshal([]byte(configStr), &config); err != nil {
|
||||
return C.CString(fmt.Sprintf("ERROR:解析水印配置失败: %v", err))
|
||||
}
|
||||
|
||||
imgBytes, format, err := AddWatermarkFromURL(config)
|
||||
if err != nil {
|
||||
return C.CString(fmt.Sprintf("ERROR:%v", err))
|
||||
}
|
||||
|
||||
// 构建带MIME前缀的Base64数据
|
||||
var base64Data string
|
||||
switch format {
|
||||
case "jpeg", "jpg":
|
||||
base64Data = "data:image/jpeg;base64," + base64.StdEncoding.EncodeToString(imgBytes)
|
||||
case "png":
|
||||
base64Data = "data:image/png;base64," + base64.StdEncoding.EncodeToString(imgBytes)
|
||||
case "gif":
|
||||
base64Data = "data:image/gif;base64," + base64.StdEncoding.EncodeToString(imgBytes)
|
||||
default:
|
||||
base64Data = "data:image/jpeg;base64," + base64.StdEncoding.EncodeToString(imgBytes)
|
||||
}
|
||||
|
||||
// 构建返回结果:格式 + 字节集大小 + 字节集数据
|
||||
// 使用Base64编码字节集以便在JSON中传输
|
||||
result := struct {
|
||||
Success bool `json:"success"`
|
||||
Format string `json:"format"`
|
||||
Data string `json:"data"` // Base64编码的图片数据
|
||||
Size int `json:"size"`
|
||||
}{
|
||||
Success: true,
|
||||
Format: format,
|
||||
Data: base64Data,
|
||||
Size: len(imgBytes),
|
||||
}
|
||||
|
||||
resultBytes, _ := json.Marshal(result)
|
||||
return C.CString(string(resultBytes))
|
||||
}
|
||||
|
||||
// FreeCString 导出函数:释放C字符串内存
|
||||
//
|
||||
//export FreeCString
|
||||
func FreeCString(str *C.char) {
|
||||
@ -1862,5 +2413,45 @@ func FreeCString(str *C.char) {
|
||||
}
|
||||
|
||||
// main 函数是必需的,即使为空
|
||||
//func main() {
|
||||
//}
|
||||
func main() {
|
||||
|
||||
//// 图片URL
|
||||
//surl := "https://img.pddpic.com/open-gw/2026-05-13/2f79386387d1b72f4081a7b661a9849a.png"
|
||||
//
|
||||
//// 1. 下载图片
|
||||
//resp, err := http.Get(surl)
|
||||
//if err != nil {
|
||||
// panic(err)
|
||||
//}
|
||||
//defer resp.Body.Close()
|
||||
//
|
||||
//// 2. 读取图片二进制数据
|
||||
//imgData, err := io.ReadAll(resp.Body)
|
||||
//if err != nil {
|
||||
// panic(err)
|
||||
//}
|
||||
//
|
||||
//// 3. 转换为Base64字符串
|
||||
//base64Str := base64.StdEncoding.EncodeToString(imgData)
|
||||
//
|
||||
//watermarkConfig := WatermarkConfig{
|
||||
// SourceImageURL: "http://booklibimg.kfzimg.com/data/book_lib_img_v2/isbn/1/b044/b044aae81122166798a30699e0f007d2_0_1_300_300.jpg",
|
||||
// WatermarkBase64: base64Str,
|
||||
// Position: "center",
|
||||
// Opacity: 1.0,
|
||||
// Scale: 1.0,
|
||||
// TileSpacing: 50,
|
||||
// Timeout: 30,
|
||||
// OutputFormat: "jpeg",
|
||||
// JPEGQuality: 95,
|
||||
// TargetWidth: 800,
|
||||
// TargetHeight: 800,
|
||||
// ResizeMode: "fit",
|
||||
//}
|
||||
//url, s, err := AddWatermarkFromURL(watermarkConfig)
|
||||
//if err != nil {
|
||||
// fmt.Println(err)
|
||||
//} else {
|
||||
// fmt.Println("url:", url, "s:", s)
|
||||
//}
|
||||
}
|
||||
|
||||
@ -1,5 +1,16 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"golang.org/x/image/draw"
|
||||
"image"
|
||||
"image/jpeg"
|
||||
"image/png"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
//func main() {
|
||||
// //// 测试长文本
|
||||
// //longText := "这是一段非常长的中文文本,需要测试自动换行功能。我们希望当文本超过图片宽度时,能够自动换行到下一行显示。这样就不需要手动添加换行符了。这段文本包含了中英文混合的内容,比如这里有一些English words mixed with中文。同时,我们也要测试标点符号的处理,例如逗号、句号、感叹号!问号?以及各种括号(包括圆括号、方括号[]、花括号{})等等。最后,我们还要测试一下当文本非常长,超过图片高度时的处理情况。" +
|
||||
@ -37,3 +48,262 @@ package main
|
||||
// fmt.Println(err)
|
||||
// }
|
||||
//}
|
||||
|
||||
// ScaleImage 缩放图片的主函数
|
||||
// imgPath: 原始图片路径
|
||||
// scaleWidth: 目标宽度(像素),如果为0则按比例自动计算
|
||||
// scaleHeight: 目标高度(像素),如果为0则按比例自动计算
|
||||
// quality: 图片质量(1-100),仅对JPEG有效
|
||||
// 返回:缩放后的图片保存路径和错误信息
|
||||
func ScaleImage(imgPath string, scaleWidth, scaleHeight int, quality int) (string, error) {
|
||||
// 检查文件是否存在
|
||||
if _, err := os.Stat(imgPath); os.IsNotExist(err) {
|
||||
return "", fmt.Errorf("图片文件不存在: %s", imgPath)
|
||||
}
|
||||
|
||||
// 打开原始图片
|
||||
file, err := os.Open(imgPath)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("打开图片失败: %v", err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
// 解码图片
|
||||
srcImg, format, err := image.Decode(file)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("解码图片失败: %v", err)
|
||||
}
|
||||
|
||||
// 获取原始尺寸
|
||||
srcBounds := srcImg.Bounds()
|
||||
srcWidth := srcBounds.Dx()
|
||||
srcHeight := srcBounds.Dy()
|
||||
|
||||
// 计算目标尺寸(保持宽高比)
|
||||
dstWidth, dstHeight := calculateTargetSize(srcWidth, srcHeight, scaleWidth, scaleHeight)
|
||||
|
||||
// 创建目标图片
|
||||
dstImg := image.NewRGBA(image.Rect(0, 0, dstWidth, dstHeight))
|
||||
|
||||
// 使用高质量的缩放算法
|
||||
draw.ApproxBiLinear.Scale(dstImg, dstImg.Bounds(), srcImg, srcBounds, draw.Over, nil)
|
||||
|
||||
// 生成保存路径
|
||||
outputPath := generateOutputPath(imgPath, dstWidth, dstHeight)
|
||||
|
||||
// 保存图片
|
||||
if err := SaveImage(outputPath, dstImg, format, quality); err != nil {
|
||||
return "", fmt.Errorf("保存图片失败: %v", err)
|
||||
}
|
||||
|
||||
return outputPath, nil
|
||||
}
|
||||
|
||||
// ScaleImageByWidth 按宽度缩放图片(高度自动适配)
|
||||
func ScaleImageByWidth(imgPath string, targetWidth int, quality int) (string, error) {
|
||||
return ScaleImage(imgPath, targetWidth, 0, quality)
|
||||
}
|
||||
|
||||
// ScaleImageByHeight 按高度缩放图片(宽度自动适配)
|
||||
func ScaleImageByHeight(imgPath string, targetHeight int, quality int) (string, error) {
|
||||
return ScaleImage(imgPath, 0, targetHeight, quality)
|
||||
}
|
||||
|
||||
// ScaleImageByPercent 按百分比缩放图片
|
||||
func ScaleImageByPercent(imgPath string, percent float64, quality int) (string, error) {
|
||||
if percent <= 0 {
|
||||
return "", fmt.Errorf("缩放百分比必须大于0")
|
||||
}
|
||||
|
||||
// 获取原始尺寸
|
||||
file, err := os.Open(imgPath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
srcImg, _, err := image.Decode(file)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
srcBounds := srcImg.Bounds()
|
||||
targetWidth := int(float64(srcBounds.Dx()) * percent)
|
||||
targetHeight := int(float64(srcBounds.Dy()) * percent)
|
||||
|
||||
return ScaleImage(imgPath, targetWidth, targetHeight, quality)
|
||||
}
|
||||
|
||||
// calculateTargetSize 计算目标尺寸,保持宽高比
|
||||
func calculateTargetSize(srcWidth, srcHeight, targetWidth, targetHeight int) (int, int) {
|
||||
// 如果目标尺寸都未指定,返回原尺寸
|
||||
if targetWidth == 0 && targetHeight == 0 {
|
||||
return srcWidth, srcHeight
|
||||
}
|
||||
|
||||
// 按宽度缩放
|
||||
if targetWidth != 0 && targetHeight == 0 {
|
||||
ratio := float64(targetWidth) / float64(srcWidth)
|
||||
return targetWidth, int(float64(srcHeight) * ratio)
|
||||
}
|
||||
|
||||
// 按高度缩放
|
||||
if targetHeight != 0 && targetWidth == 0 {
|
||||
ratio := float64(targetHeight) / float64(srcHeight)
|
||||
return int(float64(srcWidth) * ratio), targetHeight
|
||||
}
|
||||
|
||||
// 同时指定宽高,但保持原图宽高比,居中裁剪或留白
|
||||
// 这里采用完全缩放到指定尺寸(可能会变形)
|
||||
// 如需保持比例,可以取消下面的注释
|
||||
/*
|
||||
srcRatio := float64(srcWidth) / float64(srcHeight)
|
||||
dstRatio := float64(targetWidth) / float64(targetHeight)
|
||||
|
||||
if srcRatio > dstRatio {
|
||||
// 原图更宽,按高度缩放
|
||||
newHeight := targetHeight
|
||||
newWidth := int(float64(targetHeight) * srcRatio)
|
||||
return newWidth, newHeight
|
||||
} else {
|
||||
// 原图更高,按宽度缩放
|
||||
newWidth := targetWidth
|
||||
newHeight := int(float64(targetWidth) / srcRatio)
|
||||
return newWidth, newHeight
|
||||
}
|
||||
*/
|
||||
|
||||
return targetWidth, targetHeight
|
||||
}
|
||||
|
||||
// generateOutputPath 生成输出文件路径
|
||||
func generateOutputPath(srcPath string, width, height int) string {
|
||||
dir := filepath.Dir(srcPath)
|
||||
ext := filepath.Ext(srcPath)
|
||||
name := strings.TrimSuffix(filepath.Base(srcPath), ext)
|
||||
|
||||
outputName := fmt.Sprintf("%s_scaled_%dx%d%s", name, width, height, ext)
|
||||
return filepath.Join(dir, outputName)
|
||||
}
|
||||
|
||||
// saveImage 保存图片到文件
|
||||
func SaveImage(path string, img image.Image, format string, quality int) error {
|
||||
// 创建输出文件
|
||||
outFile, err := os.Create(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer outFile.Close()
|
||||
|
||||
// 根据原始格式保存
|
||||
switch strings.ToLower(format) {
|
||||
case "jpeg", "jpg":
|
||||
opts := &jpeg.Options{Quality: quality}
|
||||
return jpeg.Encode(outFile, img, opts)
|
||||
case "png":
|
||||
return png.Encode(outFile, img)
|
||||
default:
|
||||
// 默认保存为PNG格式
|
||||
return png.Encode(outFile, img)
|
||||
}
|
||||
}
|
||||
|
||||
// ScaleImageWithOptions 带更多选项的缩放函数
|
||||
type ScaleOptions struct {
|
||||
Width int // 目标宽度
|
||||
Height int // 目标高度
|
||||
Quality int // 图片质量 (1-100)
|
||||
OutputPath string // 自定义输出路径,为空则自动生成
|
||||
KeepRatio bool // 是否保持宽高比
|
||||
CropCenter bool // 是否居中裁剪(仅在KeepRatio为true且指定了宽高时有效)
|
||||
}
|
||||
|
||||
func ScaleImageWithOptions(imgPath string, opts ScaleOptions) (string, error) {
|
||||
// 参数验证
|
||||
if opts.Quality < 1 || opts.Quality > 100 {
|
||||
opts.Quality = 90 // 默认质量
|
||||
}
|
||||
|
||||
// 检查文件是否存在
|
||||
if _, err := os.Stat(imgPath); os.IsNotExist(err) {
|
||||
return "", fmt.Errorf("图片文件不存在: %s", imgPath)
|
||||
}
|
||||
|
||||
// 打开并解码图片
|
||||
file, err := os.Open(imgPath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
srcImg, format, err := image.Decode(file)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
srcBounds := srcImg.Bounds()
|
||||
srcWidth := srcBounds.Dx()
|
||||
srcHeight := srcBounds.Dy()
|
||||
|
||||
var dstWidth, dstHeight int
|
||||
|
||||
if opts.KeepRatio {
|
||||
// 保持宽高比
|
||||
dstWidth, dstHeight = calculateTargetSize(srcWidth, srcHeight, opts.Width, opts.Height)
|
||||
} else {
|
||||
// 不保持宽高比,直接使用指定尺寸
|
||||
dstWidth, dstHeight = opts.Width, opts.Height
|
||||
if dstWidth == 0 {
|
||||
dstWidth = srcWidth
|
||||
}
|
||||
if dstHeight == 0 {
|
||||
dstHeight = srcHeight
|
||||
}
|
||||
}
|
||||
|
||||
// 创建目标图片
|
||||
dstImg := image.NewRGBA(image.Rect(0, 0, dstWidth, dstHeight))
|
||||
|
||||
// 如果需要居中裁剪
|
||||
if opts.CropCenter && opts.KeepRatio && opts.Width > 0 && opts.Height > 0 {
|
||||
// 计算裁剪区域
|
||||
srcRatio := float64(srcWidth) / float64(srcHeight)
|
||||
dstRatio := float64(opts.Width) / float64(opts.Height)
|
||||
|
||||
var cropRect image.Rectangle
|
||||
if srcRatio > dstRatio {
|
||||
// 原图更宽,裁剪宽度
|
||||
cropWidth := int(float64(srcHeight) * dstRatio)
|
||||
cropX := (srcWidth - cropWidth) / 2
|
||||
cropRect = image.Rect(cropX, 0, cropX+cropWidth, srcHeight)
|
||||
} else {
|
||||
// 原图更高,裁剪高度
|
||||
cropHeight := int(float64(srcWidth) / dstRatio)
|
||||
cropY := (srcHeight - cropHeight) / 2
|
||||
cropRect = image.Rect(0, cropY, srcWidth, cropY+cropHeight)
|
||||
}
|
||||
|
||||
// 先裁剪,再缩放
|
||||
croppedImg := srcImg.(interface {
|
||||
SubImage(r image.Rectangle) image.Image
|
||||
}).SubImage(cropRect)
|
||||
|
||||
draw.ApproxBiLinear.Scale(dstImg, dstImg.Bounds(), croppedImg, croppedImg.Bounds(), draw.Over, nil)
|
||||
} else {
|
||||
// 直接缩放
|
||||
draw.ApproxBiLinear.Scale(dstImg, dstImg.Bounds(), srcImg, srcBounds, draw.Over, nil)
|
||||
}
|
||||
|
||||
// 确定输出路径
|
||||
outputPath := opts.OutputPath
|
||||
if outputPath == "" {
|
||||
outputPath = generateOutputPath(imgPath, dstWidth, dstHeight)
|
||||
}
|
||||
|
||||
// 保存图片
|
||||
if err := SaveImage(outputPath, dstImg, format, opts.Quality); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return outputPath, nil
|
||||
}
|
||||
|
||||
Binary file not shown.
@ -87,94 +87,47 @@ typedef struct { void *data; GoInt len; GoInt cap; } GoSlice;
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
|
||||
// OutLogin 孔网登录
|
||||
//
|
||||
extern __declspec(dllexport) char* OutLogin(char* username, char* password);
|
||||
|
||||
// OutGetUserMsg 获取孔网用户信息
|
||||
//
|
||||
extern __declspec(dllexport) char* OutGetUserMsg(char* token);
|
||||
|
||||
// OutGetGoodsTplMsg 获取商品模版--已登的店铺
|
||||
//
|
||||
extern __declspec(dllexport) char* OutGetGoodsTplMsg(char* token, char* proxy, char* itemId);
|
||||
|
||||
// OutGetGoodsListMsgFromSelfShop 获取商品列表-已登的店铺
|
||||
//
|
||||
extern __declspec(dllexport) char* OutGetGoodsListMsgFromSelfShop(char* token, char* proxy, char* itemSn, char* priceMin, char* priceMax, char* startCreateTime, char* endCreateTime, char* requestType, int isItemSnEqual, int page, int size);
|
||||
|
||||
// OutAddGoods 新增商品-已登的店铺
|
||||
//
|
||||
extern __declspec(dllexport) char* OutAddGoods(char* token, char* proxy, char* formData);
|
||||
|
||||
// OutAddGoodsAndFile 整合添加商品和获取孔网图片
|
||||
//
|
||||
extern __declspec(dllexport) char* OutAddGoodsAndFile(char* token, char* proxy, char* filePath, char* formData);
|
||||
|
||||
// OutDelGoodsFromSelfShop 删除商品-已登的店铺
|
||||
//
|
||||
extern __declspec(dllexport) char* OutDelGoodsFromSelfShop(char* token, char* proxy, char* itemId);
|
||||
|
||||
// OutGetImageFilterShopId 获取孔网商品图片和信息(官图和拍图)-带有店铺过滤
|
||||
//
|
||||
extern __declspec(dllexport) char* OutGetImageFilterShopId(char* token, char* proxy, char* isbn, int shopId, int isLiveImage, int isReturnMsg);
|
||||
|
||||
// OutGetImageByIsbn 获取孔网商品图片和信息(官图和拍图)
|
||||
//
|
||||
extern __declspec(dllexport) char* OutGetImageByIsbn(char* token, char* proxy, char* isbn, int isLiveImage, int isReturnMsg);
|
||||
|
||||
// OutGetGoodsListMsgByShopId 获取商品列表通过店铺ID
|
||||
//
|
||||
extern __declspec(dllexport) char* OutGetGoodsListMsgByShopId(int shopId, char* proxy, int retPrice, int isImage, char* sortType, char* sort, float priceMin, float priceMax, int pageNum, int returnNum);
|
||||
|
||||
// OutGetGoodsMsgByDetailUrl 获取商品信息通过商品详情链接
|
||||
//
|
||||
extern __declspec(dllexport) char* OutGetGoodsMsgByDetailUrl(char* detailUrl, char* proxy);
|
||||
|
||||
// OutGetTopGoodsListMsg 获取销量榜商品列表
|
||||
//
|
||||
extern __declspec(dllexport) char* OutGetTopGoodsListMsg(int catId, char* proxy);
|
||||
|
||||
// KongfzDeliveryMethodList 获取配送方式列表
|
||||
//
|
||||
extern __declspec(dllexport) char* KongfzDeliveryMethodList(int appId, char* appSecret, char* accessToken);
|
||||
|
||||
// KongfzOrderDeliver 订单发货
|
||||
//
|
||||
extern __declspec(dllexport) char* KongfzOrderDeliver(int appId, char* appSecret, char* accessToken, int orderId, char* shippingId, char* shippingCom, char* shipmentNum, char* userDefined, char* moreShipmentNum);
|
||||
|
||||
// KongfzOrderSynchronization 孔网订单同步
|
||||
//
|
||||
extern __declspec(dllexport) char* KongfzOrderSynchronization(int appId, char* appSecret, char* accessToken, char* shippingComName, int orderId, char* shippingId, char* shippingCom, char* shipmentNum, char* userDefined, char* moreShipmentNum);
|
||||
|
||||
// KongfzImageUpload 上传图片接口
|
||||
//
|
||||
extern __declspec(dllexport) char* KongfzImageUpload(int appId, char* appSecret, char* accessToken, char* filePath, char* savePath);
|
||||
|
||||
// KongfzShopItemAdd 添加店铺商品
|
||||
//
|
||||
extern __declspec(dllexport) char* KongfzShopItemAdd(int appId, char* appSecret, char* accessToken, char* shopItemAddJson);
|
||||
|
||||
// KongfzOrderList 查询订单列表
|
||||
//
|
||||
extern __declspec(dllexport) char* KongfzOrderList(int appId, char* appSecret, char* accessToken, char* orderListJson);
|
||||
|
||||
// KongfzOrderGet 查询单个订单
|
||||
//
|
||||
extern __declspec(dllexport) char* KongfzOrderGet(int appId, char* appSecret, char* accessToken, char* userType, int orderId);
|
||||
|
||||
// Initialize 初始化配置
|
||||
//
|
||||
extern __declspec(dllexport) char* Initialize(char* configJSON);
|
||||
|
||||
// FreeCString 释放C字符串内存
|
||||
//
|
||||
extern __declspec(dllexport) void FreeCString(char* str);
|
||||
|
||||
// 获取版本信息
|
||||
//
|
||||
extern __declspec(dllexport) char* GetVersion(void);
|
||||
extern char* OutKfzLogin(char* username, char* password);
|
||||
extern char* OutKfzGetUserInfo(char* token);
|
||||
extern char* OutGetKfzShippingTemplate(char* token);
|
||||
extern char* OutGetGoodsTplMsg(char* token, char* proxy, char* itemId);
|
||||
extern char* OutGetGoodsListMsgFromSelfShop(char* token, char* proxy, char* itemSn, char* mouldId, char* priceMin, char* priceMax, char* startCreateTime, char* endCreateTime, char* requestType, int isItemSnEqual, int page, int size);
|
||||
extern char* OutGetTplFields(char* token, char* proxy, char* itemId);
|
||||
extern char* OutAddGoods(char* token, char* proxy, char* formData);
|
||||
extern char* OutUpdateGoodsPrice(char* token, char* proxy, char* itemId, char* updateType, char* value);
|
||||
extern char* OutGetBaseSelectData(char* token);
|
||||
extern char* OutAddGoodsAndFile(char* token, char* proxy, char* filePath, char* formData);
|
||||
extern char* OutDelGoodsFromSelfShop(char* token, char* proxy, char* itemId);
|
||||
extern char* OutGetImageFilterShopId(char* token, char* proxy, char* isbn, int shopId, int isLiveImage, int isReturnMsg);
|
||||
extern char* OutGetImageByIsbn(char* token, char* proxy, char* isbn, int isLiveImage, int isReturnMsg);
|
||||
extern char* OutGetAllGoods(char* token, char* proxy, int queryIndex, char* isbn, char* quality, char* itemName, char* author, char* press);
|
||||
extern char* OutGetMidDetail(char* token, char* mid);
|
||||
extern char* OutGetGoodsListMsgByShopId(char* token, int shopId, char* proxy, int retPrice, int isImage, char* sortType, char* sort, float priceMin, float priceMax, int pageNum, int returnNum);
|
||||
extern char* OutGetHisGoodsList(char* token, char* shopId, char* proxy, char* retPrice, char* isImage, char* sortType, char* sort, char* priceMin, char* priceMax, int pageNum, int returnNum);
|
||||
extern char* OutGetGoodsMsgByDetailUrl(char* detailUrl, char* proxy);
|
||||
extern char* OutGetTopGoodsListMsg(int catId, char* proxy);
|
||||
extern char* KongfzDeliveryMethodList(int appId, char* appSecret, char* accessToken);
|
||||
extern char* KongfzOrderDeliver(int appId, char* appSecret, char* accessToken, int orderId, char* shippingId, char* shippingCom, char* shipmentNum, char* userDefined, char* moreShipmentNum);
|
||||
extern char* KongfzOrderSynchronization(int appId, char* appSecret, char* accessToken, char* shippingComName, int orderId, char* shippingId, char* shippingCom, char* shipmentNum, char* userDefined, char* moreShipmentNum);
|
||||
extern char* KongfzImageUpload(int appId, char* appSecret, char* accessToken, char* filePath, char* savePath);
|
||||
extern char* KongfzShopItemAdd(int appId, char* appSecret, char* accessToken, char* shopItemAddJson);
|
||||
extern char* KongfzOrderList(int appId, char* appSecret, char* accessToken, char* orderListJson);
|
||||
extern char* KongfzOrderGet(int appId, char* appSecret, char* accessToken, char* userType, int orderId);
|
||||
extern char* KongfzOrderRedeliver(int appId, char* appSecret, char* accessToken, char* requestJson);
|
||||
extern char* KongfzOrderFlagAdd(int appId, char* appSecret, char* accessToken, char* requestJson);
|
||||
extern char* KongfzOrderFlagDel(int appId, char* appSecret, char* accessToken, char* requestJson);
|
||||
extern char* KongfzShopCategoryNameList(int appId, char* appSecret, char* accessToken);
|
||||
extern char* KongfzCommonCategory(int appId, char* appSecret, char* accessToken);
|
||||
extern char* KongfzShopItemDelisting(int appId, char* appSecret, char* accessToken, char* requestJson);
|
||||
extern char* KongfzShopItemList(int appId, char* appSecret, char* accessToken, char* requestJson);
|
||||
extern char* KongfzShopItemDetail(int appId, char* appSecret, char* accessToken, char* requestJson);
|
||||
extern char* KongfzShopItemListing(int appId, char* appSecret, char* accessToken, char* requestJson);
|
||||
extern char* KongfzShopItemNumberUpdate(int appId, char* appSecret, char* accessToken, char* requestJson);
|
||||
extern char* KongfzShopItemPriceUpdate(int appId, char* appSecret, char* accessToken, char* requestJson);
|
||||
extern char* KongfzShopItemDelete(int appId, char* appSecret, char* accessToken, char* requestJson);
|
||||
extern char* Initialize(char* configJSON);
|
||||
extern void FreeCString(char* str);
|
||||
extern char* GetVersion(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
Binary file not shown.
2340
kongfz/kongfz.go
2340
kongfz/kongfz.go
File diff suppressed because it is too large
Load Diff
185
kongfz/kongfz.h
185
kongfz/kongfz.h
@ -1,185 +0,0 @@
|
||||
/* Code generated by cmd/cgo; DO NOT EDIT. */
|
||||
|
||||
/* package command-line-arguments */
|
||||
|
||||
|
||||
#line 1 "cgo-builtin-export-prolog"
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#ifndef GO_CGO_EXPORT_PROLOGUE_H
|
||||
#define GO_CGO_EXPORT_PROLOGUE_H
|
||||
|
||||
#ifndef GO_CGO_GOSTRING_TYPEDEF
|
||||
typedef struct { const char *p; ptrdiff_t n; } _GoString_;
|
||||
extern size_t _GoStringLen(_GoString_ s);
|
||||
extern const char *_GoStringPtr(_GoString_ s);
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
/* Start of preamble from import "C" comments. */
|
||||
|
||||
|
||||
#line 3 "kongfz.go"
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#line 1 "cgo-generated-wrapper"
|
||||
|
||||
|
||||
/* End of preamble from import "C" comments. */
|
||||
|
||||
|
||||
/* Start of boilerplate cgo prologue. */
|
||||
#line 1 "cgo-gcc-export-header-prolog"
|
||||
|
||||
#ifndef GO_CGO_PROLOGUE_H
|
||||
#define GO_CGO_PROLOGUE_H
|
||||
|
||||
typedef signed char GoInt8;
|
||||
typedef unsigned char GoUint8;
|
||||
typedef short GoInt16;
|
||||
typedef unsigned short GoUint16;
|
||||
typedef int GoInt32;
|
||||
typedef unsigned int GoUint32;
|
||||
typedef long long GoInt64;
|
||||
typedef unsigned long long GoUint64;
|
||||
typedef GoInt64 GoInt;
|
||||
typedef GoUint64 GoUint;
|
||||
typedef size_t GoUintptr;
|
||||
typedef float GoFloat32;
|
||||
typedef double GoFloat64;
|
||||
#ifdef _MSC_VER
|
||||
#if !defined(__cplusplus) || _MSVC_LANG <= 201402L
|
||||
#include <complex.h>
|
||||
typedef _Fcomplex GoComplex64;
|
||||
typedef _Dcomplex GoComplex128;
|
||||
#else
|
||||
#include <complex>
|
||||
typedef std::complex<float> GoComplex64;
|
||||
typedef std::complex<double> GoComplex128;
|
||||
#endif
|
||||
#else
|
||||
typedef float _Complex GoComplex64;
|
||||
typedef double _Complex GoComplex128;
|
||||
#endif
|
||||
|
||||
/*
|
||||
static assertion to make sure the file is being used on architecture
|
||||
at least with matching size of GoInt.
|
||||
*/
|
||||
typedef char _check_for_64_bit_pointer_matching_GoInt[sizeof(void*)==64/8 ? 1:-1];
|
||||
|
||||
#ifndef GO_CGO_GOSTRING_TYPEDEF
|
||||
typedef _GoString_ GoString;
|
||||
#endif
|
||||
typedef void *GoMap;
|
||||
typedef void *GoChan;
|
||||
typedef struct { void *t; void *v; } GoInterface;
|
||||
typedef struct { void *data; GoInt len; GoInt cap; } GoSlice;
|
||||
|
||||
#endif
|
||||
|
||||
/* End of boilerplate cgo prologue. */
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
|
||||
// OutLogin 孔网登录
|
||||
//
|
||||
extern __declspec(dllexport) char* OutLogin(char* username, char* password);
|
||||
|
||||
// OutGetUserMsg 获取孔网用户信息
|
||||
//
|
||||
extern __declspec(dllexport) char* OutGetUserMsg(char* token);
|
||||
|
||||
// OutGetGoodsTplMsg 获取商品模版--已登的店铺
|
||||
//
|
||||
extern __declspec(dllexport) char* OutGetGoodsTplMsg(char* token, char* proxy, char* itemId);
|
||||
|
||||
// OutGetGoodsListMsgFromSelfShop 获取商品列表-已登的店铺
|
||||
//
|
||||
extern __declspec(dllexport) char* OutGetGoodsListMsgFromSelfShop(char* token, char* proxy, char* itemSn, char* priceMin, char* priceMax, char* startCreateTime, char* endCreateTime, char* requestType, int isItemSnEqual, int page, int size);
|
||||
|
||||
// OutAddGoods 新增商品-已登的店铺
|
||||
//
|
||||
extern __declspec(dllexport) char* OutAddGoods(char* token, char* proxy, char* formData);
|
||||
|
||||
// OutAddGoodsAndFile 整合添加商品和获取孔网图片
|
||||
//
|
||||
extern __declspec(dllexport) char* OutAddGoodsAndFile(char* token, char* proxy, char* filePath, char* formData);
|
||||
|
||||
// OutDelGoodsFromSelfShop 删除商品-已登的店铺
|
||||
//
|
||||
extern __declspec(dllexport) char* OutDelGoodsFromSelfShop(char* token, char* proxy, char* itemId);
|
||||
|
||||
// OutGetImageFilterShopId 获取孔网商品图片和信息(官图和拍图)-带有店铺过滤
|
||||
//
|
||||
extern __declspec(dllexport) char* OutGetImageFilterShopId(char* token, char* proxy, char* isbn, int shopId, int isLiveImage, int isReturnMsg);
|
||||
|
||||
// OutGetImageByIsbn 获取孔网商品图片和信息(官图和拍图)
|
||||
//
|
||||
extern __declspec(dllexport) char* OutGetImageByIsbn(char* token, char* proxy, char* isbn, int isLiveImage, int isReturnMsg);
|
||||
|
||||
// OutGetMidDetail 爬取孔网 https://item.kongfz.com/book/42291389.html 网址商品信息
|
||||
//
|
||||
extern __declspec(dllexport) char* OutGetMidDetail(char* token, char* mid);
|
||||
|
||||
// OutGetGoodsListMsgByShopId 获取商品列表通过店铺ID
|
||||
//
|
||||
extern __declspec(dllexport) char* OutGetGoodsListMsgByShopId(char* token, int shopId, char* proxy, int retPrice, int isImage, char* sortType, char* sort, float priceMin, float priceMax, int pageNum, int returnNum);
|
||||
|
||||
// OutGetGoodsMsgByDetailUrl 获取商品信息通过商品详情链接
|
||||
//
|
||||
extern __declspec(dllexport) char* OutGetGoodsMsgByDetailUrl(char* detailUrl, char* proxy);
|
||||
|
||||
// OutGetTopGoodsListMsg 获取销量榜商品列表
|
||||
//
|
||||
extern __declspec(dllexport) char* OutGetTopGoodsListMsg(int catId, char* proxy);
|
||||
|
||||
// KongfzDeliveryMethodList 获取配送方式列表
|
||||
//
|
||||
extern __declspec(dllexport) char* KongfzDeliveryMethodList(int appId, char* appSecret, char* accessToken);
|
||||
|
||||
// KongfzOrderDeliver 订单发货
|
||||
//
|
||||
extern __declspec(dllexport) char* KongfzOrderDeliver(int appId, char* appSecret, char* accessToken, int orderId, char* shippingId, char* shippingCom, char* shipmentNum, char* userDefined, char* moreShipmentNum);
|
||||
|
||||
// KongfzOrderSynchronization 孔网订单同步
|
||||
//
|
||||
extern __declspec(dllexport) char* KongfzOrderSynchronization(int appId, char* appSecret, char* accessToken, char* shippingComName, int orderId, char* shippingId, char* shippingCom, char* shipmentNum, char* userDefined, char* moreShipmentNum);
|
||||
|
||||
// KongfzImageUpload 上传图片接口
|
||||
//
|
||||
extern __declspec(dllexport) char* KongfzImageUpload(int appId, char* appSecret, char* accessToken, char* filePath, char* savePath);
|
||||
|
||||
// KongfzShopItemAdd 添加店铺商品
|
||||
//
|
||||
extern __declspec(dllexport) char* KongfzShopItemAdd(int appId, char* appSecret, char* accessToken, char* shopItemAddJson);
|
||||
|
||||
// KongfzOrderList 查询订单列表
|
||||
//
|
||||
extern __declspec(dllexport) char* KongfzOrderList(int appId, char* appSecret, char* accessToken, char* orderListJson);
|
||||
|
||||
// KongfzOrderGet 查询单个订单
|
||||
//
|
||||
extern __declspec(dllexport) char* KongfzOrderGet(int appId, char* appSecret, char* accessToken, char* userType, int orderId);
|
||||
|
||||
// Initialize 初始化配置
|
||||
//
|
||||
extern __declspec(dllexport) char* Initialize(char* configJSON);
|
||||
|
||||
// FreeCString 释放C字符串内存
|
||||
//
|
||||
extern __declspec(dllexport) void FreeCString(char* str);
|
||||
|
||||
// 获取版本信息
|
||||
//
|
||||
extern __declspec(dllexport) char* GetVersion(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@ -1,106 +1,46 @@
|
||||
package main
|
||||
|
||||
//func main() {
|
||||
// //upload, err := kongfzImageUpload(576, "256e10220c5b307f5172b1a49c11467a6cfa8038bbe2a7feccc42231852324f8",
|
||||
// // "306849d7541661989453102bc0e1a4f000c4469ab084beddc7b10a90ca55f6bb",
|
||||
// // "D:/work/image/9787511220660.jpg",
|
||||
// // "D:\\work\\project\\kfzgw-info\\kongfz")
|
||||
// //if err != nil {
|
||||
// // fmt.Println(err)
|
||||
// //}
|
||||
// //fmt.Println(upload)
|
||||
// // fields, err := outGetTplFields("2aaf1b863a530d4dc0a41539292587f5b35e05da", "", "9811034490")
|
||||
// // if err != nil {
|
||||
// // fmt.Println(err)
|
||||
// // }
|
||||
// // marshal, _ := json.Marshal(fields)
|
||||
// // fmt.Println("数据:", string(marshal))
|
||||
// // formData := `{"author":"朱自清、逯亮 著","bearShipping":"buyer","binding":"2","booklibId":"","catId":"32002004001000000","deliverTimeGroup":"1","images[0][imgDesc]":"","images[0][imgType]":"0","images[0][imgUrl]":"sw/kfz-cos/kfzimg/24175337/7174f9932b38d071_s.jpg","images[0][isMain]":"1","images[1][imgDesc]":"","images[1][imgType]":"0","images[1][imgUrl]":"sw/kfz-cos/kfzimg/24175337/271a68c88dad67f4_s.jpg","images[1][isMain]":"0","importantDesc":"","isDeliverTimeDefault":"48h","isOnSale":"1","isbn":"9787204155293","itemDesc":"","itemId":"9890668175","itemName":"朱自清散文集/新课标课外阅读经典文学名著","itemSn":"24-9","language":"","material":"","mouldId":"975623","number":"3","oriPrice":"0.01","pageNum":"169","pageSize":"16开","pageType":"edit","paper":"6","press":"内蒙古人民出版社","price":"759.00","pubDate":"2018-10","quality":"95","qualityDesc":"","sizeHeight":"0.00","sizeLength":"0.00","sizeWidth":"0.00","tpl":"13","weight":"1.00","weightPiece":"0.00","wordNum":"230","yearsGroup":"1"}
|
||||
// //`
|
||||
// // goods, err := outUpdateGoods("ea9f46cd9b1c8a93f143993a363b08a835640080", "", formData)
|
||||
// // if err != nil {
|
||||
// // fmt.Println(err.Error())
|
||||
// // }
|
||||
// // marshal, _ := json.Marshal(goods)
|
||||
// // fmt.Println(string(marshal))
|
||||
//
|
||||
// //var q OrderQueryParams
|
||||
// //q.UserType = "buyer"
|
||||
// //marshal, err := json.Marshal(q)
|
||||
// //if err != nil {
|
||||
// // fmt.Println(err)
|
||||
// //}
|
||||
// //fmt.Println(string(marshal))
|
||||
// //
|
||||
// //list, err := kongfzOrderList(576, "256e10220c5b307f5172b1a49c11467a6cfa8038bbe2a7feccc42231852324f8",
|
||||
// // "306849d7541661989453102bc0e1a4f000c4469ab084beddc7b10a90ca55f6bb", string(marshal))
|
||||
// //if err != nil {
|
||||
// // fmt.Println(err)
|
||||
// //}
|
||||
// //fmt.Println(list)
|
||||
// //
|
||||
// //key, err := outKfzimgKey("de810e9e702de07c50601ef27bb51c93878c920a", "")
|
||||
// //if err != nil {
|
||||
// // fmt.Println(err)
|
||||
// //}
|
||||
// //fmt.Println(key)
|
||||
// //
|
||||
// //upload, err := outKfzimgUpload("8b7f613a6f9de5dbaff8f6c419e1c1de8495a993", "",
|
||||
// // "D:/work/image/9787115460622.jpg")
|
||||
// //if err != nil {
|
||||
// // fmt.Println(err)
|
||||
// //}
|
||||
// //fmt.Println(upload)
|
||||
//
|
||||
// //uploadd, err := outKfzimgUploadd("de810e9e702de07c50601ef27bb51c93878c920a", "",
|
||||
// // "D:/work/image/9787511220660.jpg")
|
||||
// //if err != nil {
|
||||
// // fmt.Println(err)
|
||||
// //}
|
||||
// //fmt.Println(uploadd)
|
||||
//
|
||||
// appId := 576
|
||||
// appSecret := "256e10220c5b307f5172b1a49c11467a6cfa8038bbe2a7feccc42231852324f8"
|
||||
// accessToken := "7c5ef2e492f82457898dd9a1bc2eb725df3a67b282721c9c52bfa24e460c4d92"
|
||||
// shippingComName := "韵达快递"
|
||||
// orderId := 275723461
|
||||
// shippingId := ""
|
||||
// shippingCom := ""
|
||||
// shipmentNum := "312944151258647"
|
||||
// userDefined := ""
|
||||
// moreShipmentNum := ""
|
||||
//
|
||||
// synchronization, err := kongfzOrderSynchronization(appId, appSecret, accessToken, shippingComName, orderId, shippingId, shippingCom, shipmentNum, userDefined, moreShipmentNum)
|
||||
// info, err := outUpdateGoodsPrice("ea9f46cd9b1c8a93f143993a363b08a835640080", "", "9890668175", "price", "859.00")
|
||||
// if err != nil {
|
||||
// fmt.Println(err)
|
||||
// fmt.Println(err.Error())
|
||||
// }
|
||||
// fmt.Println(synchronization)
|
||||
//
|
||||
// //data := ItemInfo{
|
||||
// // ItemName: "至关重要的关系",
|
||||
// // ISBN: "9787550213869",
|
||||
// // Author: "[美]里德·霍夫曼、本·卡斯诺瓦 著;钱峰 译",
|
||||
// // Press: "北京联合出版公司",
|
||||
// // YearsGroup: "1",
|
||||
// // PubDate: "2013-04",
|
||||
// // PubDateYear: "2013",
|
||||
// // PubDateMonth: "4",
|
||||
// // Edition: "1",
|
||||
// // Binding: "2", // 2可能表示平装
|
||||
// // Quality: "95",
|
||||
// // Price: "1500",
|
||||
// // Number: "2",
|
||||
// // ItemSn: "a1",
|
||||
// // MouldId: "909963",
|
||||
// // DeliverTime: "48h",
|
||||
// // IsDeliverTimeDefault: "48h",
|
||||
// // CatId: "43000000000000000",
|
||||
// // PageType: "add",
|
||||
// // OldCatId: "0000000000000000000",
|
||||
// // Tpl: "13",
|
||||
// // BearShipping: "buyer",
|
||||
// // WeightPiece: "1",
|
||||
// // IsOnSale: "1",
|
||||
// // Weight: "0.5",
|
||||
// // IsUseMould: "1",
|
||||
// // DeliverTimeGroup: "1",
|
||||
// //}
|
||||
// //marshal, err := json.Marshal(data)
|
||||
// //if err != nil {
|
||||
// // fmt.Println(err)
|
||||
// //}
|
||||
// //
|
||||
// //file, err := outAddGoodsAndFile("7feb7d192911b5440a4c83d81decd568e3cb4833", "", "D:/work/image/9787115460622.jpg", string(marshal))
|
||||
// //if err != nil {
|
||||
// // fmt.Println(err)
|
||||
// //}
|
||||
// //fmt.Println(string(file))
|
||||
// var apiResponse APIResponse
|
||||
// if err != nil {
|
||||
// apiResponse = APIResponse{
|
||||
// Success: false,
|
||||
// Message: err.Error(),
|
||||
// }
|
||||
// } else {
|
||||
// apiResponse = APIResponse{
|
||||
// Success: true,
|
||||
// Data: info["result"],
|
||||
// }
|
||||
// }
|
||||
// marshal, _ := json.Marshal(apiResponse)
|
||||
// fmt.Println(string(marshal))
|
||||
|
||||
//goods, err := outGetAllGoods("1acf1d5ce6f99fde2ae31f093e4201f9c9004e3f", "", 3, "", "100", "三国演义", "罗贯中", "中国画报出版社")
|
||||
//if err != nil {
|
||||
//fmt.Println(err)
|
||||
//}
|
||||
//marshal, _ := json.Marshal(goods)
|
||||
//fmt.Println(string(marshal))
|
||||
//}
|
||||
|
||||
type ItemInfo struct {
|
||||
|
||||
4262
kongfz/kongfzLao.go
Normal file
4262
kongfz/kongfzLao.go
Normal file
File diff suppressed because it is too large
Load Diff
1
listener/ceshi.go
Normal file
1
listener/ceshi.go
Normal file
@ -0,0 +1 @@
|
||||
package main
|
||||
@ -1,71 +1,351 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/cookiejar"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/chromedp/chromedp"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// 定义路由
|
||||
http.HandleFunc("/", homeHandler)
|
||||
http.HandleFunc("/api/data", dataHandler)
|
||||
http.HandleFunc("/ws", websocketHandler)
|
||||
//func main() {
|
||||
// username := "15140030829"
|
||||
// password := "long616"
|
||||
// downloadDir := "./downloads"
|
||||
// os.MkdirAll(downloadDir, 0755)
|
||||
//
|
||||
// // 1. API 登录获取 Cookie
|
||||
// fmt.Println("正在登录...")
|
||||
// cookieJar, err := loginAndGetCookies(username, password)
|
||||
// if err != nil {
|
||||
// fmt.Printf("登录失败: %v\n", err)
|
||||
// return
|
||||
// }
|
||||
// fmt.Println("✓ 登录成功")
|
||||
//
|
||||
// // 2. 使用 chromedp 的远程浏览器(自动下载)
|
||||
// targetURL := "https://seller.kongfz.com/management/item/unsold/?tab=onSale"
|
||||
// var downloadPath string
|
||||
//
|
||||
// // 创建 chromedp 上下文
|
||||
// ctx, cancel := createChromeContext()
|
||||
// defer cancel()
|
||||
//
|
||||
// // 设置超时
|
||||
// ctx, cancel = context.WithTimeout(ctx, 5*time.Minute)
|
||||
// defer cancel()
|
||||
//
|
||||
// // 执行操作
|
||||
// err = chromedp.Run(ctx,
|
||||
// // 注入 Cookie
|
||||
// chromedp.ActionFunc(func(ctx context.Context) error {
|
||||
// fmt.Println("正在注入 Session Cookie...")
|
||||
// return injectCookies(ctx, cookieJar)
|
||||
// }),
|
||||
//
|
||||
// // 导航到目标页面
|
||||
// chromedp.Navigate(targetURL),
|
||||
// chromedp.Sleep(5*time.Second),
|
||||
//
|
||||
// // 等待页面加载完成
|
||||
// chromedp.WaitVisible("body", chromedp.ByQuery),
|
||||
// chromedp.Sleep(2*time.Second),
|
||||
//
|
||||
// // 点击下载按钮
|
||||
// chromedp.ActionFunc(func(ctx context.Context) error {
|
||||
// fmt.Println("查找并点击下载按钮...")
|
||||
// return clickDownloadButton(ctx)
|
||||
// }),
|
||||
//
|
||||
// // 等待下载完成
|
||||
// chromedp.ActionFunc(func(ctx context.Context) error {
|
||||
// fmt.Println("等待文件下载...")
|
||||
// path, err := waitForDownload(downloadDir, 90*time.Second)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// downloadPath = path
|
||||
// return nil
|
||||
// }),
|
||||
// )
|
||||
//
|
||||
// if err != nil {
|
||||
// fmt.Printf("错误: %v\n", err)
|
||||
// //// 打印更多错误信息
|
||||
// //if chromedpErr, ok := err.(*chromedp.Err); ok {
|
||||
// // fmt.Printf("Chromedp 错误详情: %v\n", chromedpErr)
|
||||
// //}
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// fmt.Printf("✓ 文件已保存: %s\n", downloadPath)
|
||||
//}
|
||||
|
||||
// 中间件示例
|
||||
wrappedHandler := loggingMiddleware(http.DefaultServeMux)
|
||||
// createChromeContext 创建 Chrome 上下文
|
||||
func createChromeContext() (context.Context, context.CancelFunc) {
|
||||
// 设置 Chrome 选项
|
||||
opts := append(chromedp.DefaultExecAllocatorOptions[:],
|
||||
chromedp.Flag("disable-blink-features", "AutomationControlled"),
|
||||
chromedp.UserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"),
|
||||
chromedp.Flag("headless", false), // 设置为 false 可以看到浏览器窗口
|
||||
chromedp.Flag("no-sandbox", true),
|
||||
chromedp.Flag("disable-dev-shm-usage", true),
|
||||
chromedp.Flag("disable-gpu", true),
|
||||
chromedp.Flag("disable-web-security", false),
|
||||
chromedp.Flag("disable-extensions", true),
|
||||
chromedp.Flag("disable-plugins", true),
|
||||
// 设置下载目录
|
||||
chromedp.Flag("disable-default-apps", true),
|
||||
chromedp.Flag("disable-background-networking", true),
|
||||
// 指定下载路径(如果需要)
|
||||
// chromedp.Flag("download.default_directory", downloadDir),
|
||||
)
|
||||
|
||||
// 配置服务器
|
||||
server := &http.Server{
|
||||
Addr: ":8080",
|
||||
Handler: wrappedHandler,
|
||||
ReadTimeout: 10 * time.Second,
|
||||
WriteTimeout: 10 * time.Second,
|
||||
IdleTimeout: 60 * time.Second,
|
||||
allocCtx, cancel := chromedp.NewExecAllocator(context.Background(), opts...)
|
||||
ctx, _ := chromedp.NewContext(allocCtx)
|
||||
|
||||
return ctx, cancel
|
||||
}
|
||||
|
||||
// loginAndGetCookies 登录 API
|
||||
func loginAndGetCookies(username, password string) (*cookiejar.Jar, error) {
|
||||
loginURL := "https://login.kongfz.com/Pc/Login/account"
|
||||
|
||||
reqBody := map[string]interface{}{
|
||||
"loginName": username,
|
||||
"loginPass": password,
|
||||
"captchaCode": "",
|
||||
"autoLogin": 0,
|
||||
"newUsername": "",
|
||||
"returnUrl": "https://seller.kongfz.com/management/item/unsold/?tab=onSale",
|
||||
"captchaId": "",
|
||||
}
|
||||
|
||||
fmt.Println("HTTP Server listening on :8080")
|
||||
jsonData, _ := json.Marshal(reqBody)
|
||||
|
||||
// 启动服务器
|
||||
if err := server.ListenAndServe(); err != nil {
|
||||
log.Fatal("Server error:", err)
|
||||
jar, _ := cookiejar.New(nil)
|
||||
client := &http.Client{
|
||||
Jar: jar,
|
||||
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
||||
return nil // 允许重定向
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func homeHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path != "/" {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
req, _ := http.NewRequest("POST", loginURL, bytes.NewBuffer(jsonData))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36")
|
||||
req.Header.Set("Accept", "application/json, text/javascript, */*; q=0.01")
|
||||
req.Header.Set("Referer", "https://login.kongfz.com/")
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("请求失败: %v", err)
|
||||
}
|
||||
fmt.Fprintf(w, "Welcome to HTTP Server!\n")
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
func dataHandler(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.Method {
|
||||
case "GET":
|
||||
fmt.Fprintf(w, `{"status": "ok", "message": "GET request received"}`)
|
||||
case "POST":
|
||||
fmt.Fprintf(w, `{"status": "ok", "message": "POST request received"}`)
|
||||
default:
|
||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("读取响应失败: %v", err)
|
||||
}
|
||||
|
||||
fmt.Printf("登录响应: %s\n", string(body))
|
||||
|
||||
var result map[string]interface{}
|
||||
if err := json.Unmarshal(body, &result); err != nil {
|
||||
return nil, fmt.Errorf("解析响应失败: %v", err)
|
||||
}
|
||||
|
||||
if code, ok := result["code"]; ok {
|
||||
switch v := code.(type) {
|
||||
case float64:
|
||||
if v != 0 {
|
||||
return nil, fmt.Errorf("登录失败: %v", result["msg"])
|
||||
}
|
||||
case int:
|
||||
if v != 0 {
|
||||
return nil, fmt.Errorf("登录失败: %v", result["msg"])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 获取并打印所有 Cookie
|
||||
cookies := jar.Cookies(&url.URL{Scheme: "https", Host: "kongfz.com"})
|
||||
fmt.Printf("获取到 %d 个 Cookie\n", len(cookies))
|
||||
for _, c := range cookies {
|
||||
fmt.Printf("Cookie: %s=%s\n", c.Name, c.Value[:min(20, len(c.Value))])
|
||||
}
|
||||
|
||||
return jar, nil
|
||||
}
|
||||
|
||||
func websocketHandler(w http.ResponseWriter, r *http.Request) {
|
||||
// WebSocket处理逻辑
|
||||
fmt.Fprintf(w, "WebSocket endpoint")
|
||||
func injectCookies(ctx context.Context, jar *cookiejar.Jar) error {
|
||||
// 获取所有 Cookie
|
||||
cookies := jar.Cookies(&url.URL{Scheme: "https", Host: "kongfz.com"})
|
||||
|
||||
// 先设置 document.cookie
|
||||
for _, cookie := range cookies {
|
||||
// 构建 Cookie 字符串
|
||||
cookieStr := fmt.Sprintf("%s=%s; path=/; domain=.kongfz.com", cookie.Name, cookie.Value)
|
||||
if cookie.Domain != "" {
|
||||
cookieStr += fmt.Sprintf("; domain=%s", cookie.Domain)
|
||||
}
|
||||
if cookie.Path != "" {
|
||||
cookieStr += fmt.Sprintf("; path=%s", cookie.Path)
|
||||
}
|
||||
|
||||
jsCode := fmt.Sprintf(`document.cookie = '%s';`, cookieStr)
|
||||
err := chromedp.Evaluate(jsCode, nil).Do(ctx)
|
||||
if err != nil {
|
||||
fmt.Printf("设置 cookie %s 失败: %v\n", cookie.Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
// 验证 Cookie 是否设置成功
|
||||
var cookiesCount int
|
||||
err := chromedp.Evaluate(`document.cookie.length`, &cookiesCount).Do(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("验证 Cookie 失败: %v", err)
|
||||
}
|
||||
fmt.Printf("Cookie 注入完成,当前 document.cookie 长度: %d\n", cookiesCount)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 中间件:记录请求日志
|
||||
func loggingMiddleware(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
start := time.Now()
|
||||
func clickDownloadButton(ctx context.Context) error {
|
||||
// 多种方式尝试点击下载按钮
|
||||
|
||||
// 调用下一个处理器
|
||||
next.ServeHTTP(w, r)
|
||||
// 方法1: 通过文本查找
|
||||
var clicked bool
|
||||
err := chromedp.Evaluate(`
|
||||
(function() {
|
||||
// 禁用确认对话框
|
||||
window.confirm = function() { return true; };
|
||||
window.alert = function() { return true; };
|
||||
|
||||
// 记录日志
|
||||
log.Printf("%s %s %s %v", r.Method, r.URL.Path, r.RemoteAddr, time.Since(start))
|
||||
})
|
||||
// 查找包含"下载查询结果"的按钮
|
||||
var buttons = Array.from(document.querySelectorAll('button, a, span, div'));
|
||||
var downloadBtn = buttons.find(el => el.innerText && el.innerText.includes('下载查询结果'));
|
||||
if (downloadBtn) {
|
||||
downloadBtn.click();
|
||||
return true;
|
||||
}
|
||||
|
||||
// 尝试通过类名查找
|
||||
var btnByClass = document.querySelector('.download-btn, .export-btn, [class*="download"], [class*="export"]');
|
||||
if (btnByClass) {
|
||||
btnByClass.click();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
})()
|
||||
`, &clicked).Do(ctx)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("执行点击脚本失败: %v", err)
|
||||
}
|
||||
|
||||
if !clicked {
|
||||
// 截图以便调试
|
||||
var buf []byte
|
||||
err = chromedp.CaptureScreenshot(&buf).Do(ctx)
|
||||
if err == nil {
|
||||
os.WriteFile("debug_screenshot.png", buf, 0644)
|
||||
fmt.Println("已保存调试截图: debug_screenshot.png")
|
||||
}
|
||||
|
||||
// 获取页面标题和URL
|
||||
var title, url string
|
||||
chromedp.Title(&title).Do(ctx)
|
||||
chromedp.Location(&url).Do(ctx)
|
||||
fmt.Printf("当前页面: %s, URL: %s\n", title, url)
|
||||
|
||||
return fmt.Errorf("未找到下载按钮")
|
||||
}
|
||||
|
||||
fmt.Println("✓ 已点击下载按钮")
|
||||
time.Sleep(3 * time.Second)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getCookiesFromJar(jar *cookiejar.Jar, domain string) []*http.Cookie {
|
||||
parsedURL, _ := url.Parse(fmt.Sprintf("https://%s/", domain))
|
||||
return jar.Cookies(parsedURL)
|
||||
}
|
||||
|
||||
func waitForDownload(dir string, timeout time.Duration) (string, error) {
|
||||
deadline := time.Now().Add(timeout)
|
||||
lastCount := 0
|
||||
|
||||
for time.Now().Before(deadline) {
|
||||
entries, err := os.ReadDir(dir)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
downloading := false
|
||||
var excels []string
|
||||
|
||||
for _, entry := range entries {
|
||||
name := entry.Name()
|
||||
if strings.HasSuffix(name, ".crdownload") ||
|
||||
strings.HasSuffix(name, ".tmp") ||
|
||||
strings.HasSuffix(name, ".download") {
|
||||
downloading = true
|
||||
fmt.Printf("下载中: %s\n", name)
|
||||
} else if strings.HasSuffix(name, ".xls") ||
|
||||
strings.HasSuffix(name, ".xlsx") ||
|
||||
strings.HasSuffix(name, ".csv") {
|
||||
excels = append(excels, filepath.Join(dir, name))
|
||||
}
|
||||
}
|
||||
|
||||
if !downloading && len(excels) > 0 {
|
||||
// 找到最新的文件
|
||||
var newest string
|
||||
var newestTime time.Time
|
||||
for _, f := range excels {
|
||||
info, err := os.Stat(f)
|
||||
if err == nil {
|
||||
if info.ModTime().After(newestTime) {
|
||||
newestTime = info.ModTime()
|
||||
newest = f
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 确保文件写入完成
|
||||
time.Sleep(2 * time.Second)
|
||||
if newest != "" {
|
||||
fmt.Printf("下载完成: %s\n", newest)
|
||||
return newest, nil
|
||||
}
|
||||
}
|
||||
|
||||
if len(excels) != lastCount {
|
||||
fmt.Printf("发现 %d 个 Excel 文件\n", len(excels))
|
||||
lastCount = len(excels)
|
||||
}
|
||||
|
||||
time.Sleep(2 * time.Second)
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("下载超时 (%v)", timeout)
|
||||
}
|
||||
|
||||
func min(a, b int) int {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
772
md/pdd.md
772
md/pdd.md
@ -623,6 +623,778 @@ dll.PddGoodsQuantityUpdate(clientId, clientSecret, accessToken, request)
|
||||
}
|
||||
```
|
||||
|
||||
## 12. 商品图片上传接口(base64方式)--PddGoodsImageUpload
|
||||
### 请求信息
|
||||
```gotemplate
|
||||
dll.PddGoodsImageUpload(clientId, clientSecret, accessToken, fileBase)
|
||||
```
|
||||
### 请求参数
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--|--|--|----------|
|
||||
| clientId | string | 是 | 拼多多开放平台ClientID |
|
||||
| clientSecret | string | 是 | 拼多多开放平台ClientSecret |
|
||||
| accessToken | string | 是 | 授权令牌 |
|
||||
| fileBase | string | 是 | 图片base64编码字符串 |
|
||||
### 响应示例
|
||||
```json
|
||||
{
|
||||
"goods_image_upload_response": {
|
||||
"image_url": "http://oms-imageimg.pinduoduo.com/upload/2025/01/20/e9a8c1b6e1a84f1d8d7c3a8b9e2f5c7d.jpg",
|
||||
"request_id": "17666480184871655"
|
||||
}
|
||||
}
|
||||
```
|
||||
### 错误响应示例
|
||||
```json
|
||||
{
|
||||
"error_response": {
|
||||
"error_msg": "公共参数错误:type",
|
||||
"sub_msg": "",
|
||||
"sub_code": null,
|
||||
"error_code": 10001,
|
||||
"request_id": "15440104776643887"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 13. 订单基础信息列表查询接口(根据成交时间)--PddOrderBasicListGet
|
||||
### 请求信息
|
||||
```gotemplate
|
||||
dll.PddOrderBasicListGet(clientId, clientSecret, accessToken, orderBasicListGetJSONStr)
|
||||
```
|
||||
### 请求参数
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--|--|--|----------|
|
||||
| clientId | string | 是 | 拼多多开放平台ClientID |
|
||||
| clientSecret | string | 是 | 拼多多开放平台ClientSecret |
|
||||
| accessToken | string | 是 | 授权令牌 |
|
||||
| orderBasicListGetJSONStr | string | 是 | 订单列表查询JSON字符串 |
|
||||
#### 请求JSON结构示例
|
||||
```json
|
||||
{
|
||||
"order_status": 1,
|
||||
"refund_status": 1,
|
||||
"start_confirm_at": 1700000000,
|
||||
"end_confirm_at": 1700100000,
|
||||
"page": 1,
|
||||
"page_size": 50
|
||||
}
|
||||
```
|
||||
### 响应示例
|
||||
```json
|
||||
{
|
||||
"order_basic_list_get_response": {
|
||||
"order_list": [
|
||||
{
|
||||
"order_sn": "250101-123456789",
|
||||
"goods_id": 123456789,
|
||||
"goods_name": "测试商品",
|
||||
"goods_quantity": 1,
|
||||
"order_amount": 8900,
|
||||
"order_status": 1,
|
||||
"confirm_time": 1700000000
|
||||
}
|
||||
],
|
||||
"total_count": 100,
|
||||
"request_id": "17666480184871656"
|
||||
}
|
||||
}
|
||||
```
|
||||
### 错误响应示例
|
||||
```json
|
||||
{
|
||||
"error_response": {
|
||||
"error_msg": "公共参数错误:type",
|
||||
"sub_msg": "",
|
||||
"sub_code": null,
|
||||
"error_code": 10001,
|
||||
"request_id": "15440104776643887"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 14. 获取商品提交的商品详情--PddGoodsCommitDetailGet
|
||||
### 请求信息
|
||||
```gotemplate
|
||||
dll.PddGoodsCommitDetailGet(clientId, clientSecret, accessToken, goodsCommitId, goodsId)
|
||||
```
|
||||
### 请求参数
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--|--|--|----------|
|
||||
| clientId | string | 是 | 拼多多开放平台ClientID |
|
||||
| clientSecret | string | 是 | 拼多多开放平台ClientSecret |
|
||||
| accessToken | string | 是 | 授权令牌 |
|
||||
| goodsCommitId | string | 是 | 商品提交ID |
|
||||
| goodsId | string | 是 | 商品ID |
|
||||
### 响应示例
|
||||
```json
|
||||
{
|
||||
"goods_commit_detail_get_response": {
|
||||
"goods_id": 123456789,
|
||||
"goods_name": "测试商品",
|
||||
"cat_id": 20111,
|
||||
"goods_desc": "商品描述",
|
||||
"image_url": "http://oms-imageimg.pinduoduo.com/upload/xxx.jpg",
|
||||
"sku_list": [],
|
||||
"request_id": "17666480184871657"
|
||||
}
|
||||
}
|
||||
```
|
||||
### 错误响应示例
|
||||
```json
|
||||
{
|
||||
"error_response": {
|
||||
"error_msg": "公共参数错误:type",
|
||||
"sub_msg": "",
|
||||
"sub_code": null,
|
||||
"error_code": 10001,
|
||||
"request_id": "15440104776643887"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 15. 获取拼多多系统时间--PddTimeGet
|
||||
### 请求信息
|
||||
```gotemplate
|
||||
dll.PddTimeGet(clientId, clientSecret, accessToken)
|
||||
```
|
||||
### 请求参数
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--|--|--|----------|
|
||||
| clientId | string | 是 | 拼多多开放平台ClientID |
|
||||
| clientSecret | string | 是 | 拼多多开放平台ClientSecret |
|
||||
| accessToken | string | 是 | 授权令牌 |
|
||||
### 响应示例
|
||||
```json
|
||||
{
|
||||
"time_get_response": {
|
||||
"time": 1700000000000,
|
||||
"request_id": "17666480184871658"
|
||||
}
|
||||
}
|
||||
```
|
||||
### 错误响应示例
|
||||
```json
|
||||
{
|
||||
"error_response": {
|
||||
"error_msg": "公共参数错误:type",
|
||||
"sub_msg": "",
|
||||
"sub_code": null,
|
||||
"error_code": 10001,
|
||||
"request_id": "15440104776643887"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 16. 查询面单服务订购及面单使用情况--PddWaybillSearch
|
||||
### 请求信息
|
||||
```gotemplate
|
||||
dll.PddWaybillSearch(clientId, clientSecret, accessToken, requestJson)
|
||||
```
|
||||
### 请求参数
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--|--|--|----------|
|
||||
| clientId | string | 是 | 拼多多开放平台ClientID |
|
||||
| clientSecret | string | 是 | 拼多多开放平台ClientSecret |
|
||||
| accessToken | string | 是 | 授权令牌 |
|
||||
| requestJson | string | 是 | 查询参数JSON字符串 |
|
||||
#### 请求JSON结构示例
|
||||
```json
|
||||
{
|
||||
"wp_code": "STO"
|
||||
}
|
||||
```
|
||||
### 响应示例
|
||||
```json
|
||||
{
|
||||
"waybill_search_response": {
|
||||
"wp_code": "STO",
|
||||
"total_quantity": 1000,
|
||||
"used_quantity": 100,
|
||||
"created_at": 1700000000,
|
||||
"request_id": "17666480184871659"
|
||||
}
|
||||
}
|
||||
```
|
||||
### 错误响应示例
|
||||
```json
|
||||
{
|
||||
"error_response": {
|
||||
"error_msg": "公共参数错误:type",
|
||||
"sub_msg": "",
|
||||
"sub_code": null,
|
||||
"error_code": 10001,
|
||||
"request_id": "15440104776643887"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 17. 电子面单取号--PddFdsWaybillGet
|
||||
### 请求信息
|
||||
```gotemplate
|
||||
dll.PddFdsWaybillGet(clientId, clientSecret, accessToken, requestJson)
|
||||
```
|
||||
### 请求参数
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--|--|--|----------|
|
||||
| clientId | string | 是 | 拼多多开放平台ClientID |
|
||||
| clientSecret | string | 是 | 拼多多开放平台ClientSecret |
|
||||
| accessToken | string | 是 | 授权令牌 |
|
||||
| requestJson | string | 是 | 取号请求JSON字符串 |
|
||||
#### 请求JSON结构示例
|
||||
```json
|
||||
{
|
||||
"wp_code": "STO",
|
||||
"order_sn": "250101-123456789",
|
||||
"sender": {
|
||||
"name": "张三",
|
||||
"mobile": "13800000000",
|
||||
"province": "广东省",
|
||||
"city": "深圳市",
|
||||
"town": "南山区",
|
||||
"address": "xx街道xx号"
|
||||
},
|
||||
"receiver": {
|
||||
"name": "李四",
|
||||
"mobile": "13900000000",
|
||||
"province": "广东省",
|
||||
"city": "广州市",
|
||||
"town": "天河区",
|
||||
"address": "xx街道xx号"
|
||||
}
|
||||
}
|
||||
```
|
||||
### 响应示例
|
||||
```json
|
||||
{
|
||||
"fds_waybill_get_response": {
|
||||
"waybill_code": "1234567890",
|
||||
"print_data": "{\"content\": \"打印数据\"}",
|
||||
"request_id": "17666480184871660"
|
||||
}
|
||||
}
|
||||
```
|
||||
### 错误响应示例
|
||||
```json
|
||||
{
|
||||
"error_response": {
|
||||
"error_msg": "公共参数错误:type",
|
||||
"sub_msg": "",
|
||||
"sub_code": null,
|
||||
"error_code": 10001,
|
||||
"request_id": "15440104776643887"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 18. 商家取消获取的电子面单号--PddWaybillCancel
|
||||
### 请求信息
|
||||
```gotemplate
|
||||
dll.PddWaybillCancel(clientId, clientSecret, accessToken, requestJson)
|
||||
```
|
||||
### 请求参数
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--|--|--|----------|
|
||||
| clientId | string | 是 | 拼多多开放平台ClientID |
|
||||
| clientSecret | string | 是 | 拼多多开放平台ClientSecret |
|
||||
| accessToken | string | 是 | 授权令牌 |
|
||||
| requestJson | string | 是 | 取消面单请求JSON字符串 |
|
||||
#### 请求JSON结构示例
|
||||
```json
|
||||
{
|
||||
"wp_code": "STO",
|
||||
"waybill_code": "1234567890"
|
||||
}
|
||||
```
|
||||
### 响应示例
|
||||
```json
|
||||
{
|
||||
"waybill_cancel_response": {
|
||||
"is_success": true,
|
||||
"request_id": "17666480184871661"
|
||||
}
|
||||
}
|
||||
```
|
||||
### 错误响应示例
|
||||
```json
|
||||
{
|
||||
"error_response": {
|
||||
"error_msg": "公共参数错误:type",
|
||||
"sub_msg": "",
|
||||
"sub_code": null,
|
||||
"error_code": 10001,
|
||||
"request_id": "15440104776643887"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 19. 商品列表接口--PddGoodsListGet
|
||||
### 请求信息
|
||||
```gotemplate
|
||||
dll.PddGoodsListGet(clientId, clientSecret, accessToken, requestJson)
|
||||
```
|
||||
### 请求参数
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--|--|--|----------|
|
||||
| clientId | string | 是 | 拼多多开放平台ClientID |
|
||||
| clientSecret | string | 是 | 拼多多开放平台ClientSecret |
|
||||
| accessToken | string | 是 | 授权令牌 |
|
||||
| requestJson | string | 是 | 商品列表查询JSON字符串 |
|
||||
#### 请求JSON结构示例
|
||||
```json
|
||||
{
|
||||
"is_onsale": 1,
|
||||
"page": 1,
|
||||
"page_size": 50
|
||||
}
|
||||
```
|
||||
### 响应示例
|
||||
```json
|
||||
{
|
||||
"goods_list_get_response": {
|
||||
"goods_list": [
|
||||
{
|
||||
"goods_id": 123456789,
|
||||
"goods_name": "测试商品",
|
||||
"goods_sn": "G202501200001",
|
||||
"goods_status": 1,
|
||||
"goods_type": 1
|
||||
}
|
||||
],
|
||||
"total_count": 100,
|
||||
"request_id": "17666480184871662"
|
||||
}
|
||||
}
|
||||
```
|
||||
### 错误响应示例
|
||||
```json
|
||||
{
|
||||
"error_response": {
|
||||
"error_msg": "公共参数错误:type",
|
||||
"sub_msg": "",
|
||||
"sub_code": null,
|
||||
"error_code": 10001,
|
||||
"request_id": "15440104776643887"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 20. 电子面单云打印接口--PddWaybillGet
|
||||
### 请求信息
|
||||
```gotemplate
|
||||
dll.PddWaybillGet(clientId, clientSecret, accessToken, requestJson)
|
||||
```
|
||||
### 请求参数
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--|--|--|----------|
|
||||
| clientId | string | 是 | 拼多多开放平台ClientID |
|
||||
| clientSecret | string | 是 | 拼多多开放平台ClientSecret |
|
||||
| accessToken | string | 是 | 授权令牌 |
|
||||
| requestJson | string | 是 | 云打印请求JSON字符串 |
|
||||
#### 请求JSON结构示例
|
||||
```json
|
||||
{
|
||||
"wp_code": "STO",
|
||||
"waybill_code": "1234567890"
|
||||
}
|
||||
```
|
||||
### 响应示例
|
||||
```json
|
||||
{
|
||||
"waybill_get_response": {
|
||||
"print_data": "{\"content\": \"打印数据\"}",
|
||||
"request_id": "17666480184871663"
|
||||
}
|
||||
}
|
||||
```
|
||||
### 错误响应示例
|
||||
```json
|
||||
{
|
||||
"error_response": {
|
||||
"error_msg": "公共参数错误:type",
|
||||
"sub_msg": "",
|
||||
"sub_code": null,
|
||||
"error_code": 10001,
|
||||
"request_id": "15440104776643887"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 21. 通过面单号查询面单信息--PddWaybillQueryByWaybillcode
|
||||
### 请求信息
|
||||
```gotemplate
|
||||
dll.PddWaybillQueryByWaybillcode(clientId, clientSecret, accessToken, requestJson)
|
||||
```
|
||||
### 请求参数
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--|--|--|----------|
|
||||
| clientId | string | 是 | 拼多多开放平台ClientID |
|
||||
| clientSecret | string | 是 | 拼多多开放平台ClientSecret |
|
||||
| accessToken | string | 是 | 授权令牌 |
|
||||
| requestJson | string | 是 | 查询面单信息JSON字符串 |
|
||||
#### 请求JSON结构示例
|
||||
```json
|
||||
{
|
||||
"wp_code": "STO",
|
||||
"waybill_code": "1234567890"
|
||||
}
|
||||
```
|
||||
### 响应示例
|
||||
```json
|
||||
{
|
||||
"waybill_query_by_waybillcode_response": {
|
||||
"wp_code": "STO",
|
||||
"waybill_code": "1234567890",
|
||||
"order_sn": "250101-123456789",
|
||||
"print_data": "{\"content\": \"打印数据\"}",
|
||||
"request_id": "17666480184871664"
|
||||
}
|
||||
}
|
||||
```
|
||||
### 错误响应示例
|
||||
```json
|
||||
{
|
||||
"error_response": {
|
||||
"error_msg": "公共参数错误:type",
|
||||
"sub_msg": "",
|
||||
"sub_code": null,
|
||||
"error_code": 10001,
|
||||
"request_id": "15440104776643887"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 22. 云打印--PddCloudPrint
|
||||
### 请求信息
|
||||
```gotemplate
|
||||
dll.PddCloudPrint(clientId, clientSecret, accessToken, requestJson)
|
||||
```
|
||||
### 请求参数
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--|--|--|----------|
|
||||
| clientId | string | 是 | 拼多多开放平台ClientID |
|
||||
| clientSecret | string | 是 | 拼多多开放平台ClientSecret |
|
||||
| accessToken | string | 是 | 授权令牌 |
|
||||
| requestJson | string | 是 | 云打印请求JSON字符串 |
|
||||
#### 请求JSON结构示例
|
||||
```json
|
||||
{
|
||||
"print_data": "{\"content\": \"打印数据\"}",
|
||||
"printer_id": "printer_001"
|
||||
}
|
||||
```
|
||||
### 响应示例
|
||||
```json
|
||||
{
|
||||
"cloud_print_response": {
|
||||
"is_success": true,
|
||||
"request_id": "17666480184871665"
|
||||
}
|
||||
}
|
||||
```
|
||||
### 错误响应示例
|
||||
```json
|
||||
{
|
||||
"error_response": {
|
||||
"error_msg": "公共参数错误:type",
|
||||
"sub_msg": "",
|
||||
"sub_code": null,
|
||||
"error_code": 10001,
|
||||
"request_id": "15440104776643887"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 23. 云打印任务查询--PddCloudPrintTaskQuery
|
||||
### 请求信息
|
||||
```gotemplate
|
||||
dll.PddCloudPrintTaskQuery(clientId, clientSecret, accessToken, requestJson)
|
||||
```
|
||||
### 请求参数
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--|--|--|----------|
|
||||
| clientId | string | 是 | 拼多多开放平台ClientID |
|
||||
| clientSecret | string | 是 | 拼多多开放平台ClientSecret |
|
||||
| accessToken | string | 是 | 授权令牌 |
|
||||
| requestJson | string | 是 | 云打印任务查询JSON字符串 |
|
||||
#### 请求JSON结构示例
|
||||
```json
|
||||
{
|
||||
"task_id": "task_001"
|
||||
}
|
||||
```
|
||||
### 响应示例
|
||||
```json
|
||||
{
|
||||
"cloud_print_task_query_response": {
|
||||
"task_id": "task_001",
|
||||
"task_status": 2,
|
||||
"request_id": "17666480184871666"
|
||||
}
|
||||
}
|
||||
```
|
||||
### 错误响应示例
|
||||
```json
|
||||
{
|
||||
"error_response": {
|
||||
"error_msg": "公共参数错误:type",
|
||||
"sub_msg": "",
|
||||
"sub_code": null,
|
||||
"error_code": 10001,
|
||||
"request_id": "15440104776643887"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 24. 云打印验证码--PddCloudPrintVerifyCode
|
||||
### 请求信息
|
||||
```gotemplate
|
||||
dll.PddCloudPrintVerifyCode(clientId, clientSecret, accessToken, requestJson)
|
||||
```
|
||||
### 请求参数
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--|--|--|----------|
|
||||
| clientId | string | 是 | 拼多多开放平台ClientID |
|
||||
| clientSecret | string | 是 | 拼多多开放平台ClientSecret |
|
||||
| accessToken | string | 是 | 授权令牌 |
|
||||
| requestJson | string | 是 | 云打印验证码请求JSON字符串 |
|
||||
#### 请求JSON结构示例
|
||||
```json
|
||||
{
|
||||
"printer_id": "printer_001",
|
||||
"verify_code": "123456"
|
||||
}
|
||||
```
|
||||
### 响应示例
|
||||
```json
|
||||
{
|
||||
"cloud_print_verify_code_response": {
|
||||
"is_success": true,
|
||||
"request_id": "17666480184871667"
|
||||
}
|
||||
}
|
||||
```
|
||||
### 错误响应示例
|
||||
```json
|
||||
{
|
||||
"error_response": {
|
||||
"error_msg": "公共参数错误:type",
|
||||
"sub_msg": "",
|
||||
"sub_code": null,
|
||||
"error_code": 10001,
|
||||
"request_id": "15440104776643887"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 25. 云打印机绑定--PddCloudPrinterBind
|
||||
### 请求信息
|
||||
```gotemplate
|
||||
dll.PddCloudPrinterBind(clientId, clientSecret, accessToken, requestJson)
|
||||
```
|
||||
### 请求参数
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--|--|--|----------|
|
||||
| clientId | string | 是 | 拼多多开放平台ClientID |
|
||||
| clientSecret | string | 是 | 拼多多开放平台ClientSecret |
|
||||
| accessToken | string | 是 | 授权令牌 |
|
||||
| requestJson | string | 是 | 云打印机绑定JSON字符串 |
|
||||
#### 请求JSON结构示例
|
||||
```json
|
||||
{
|
||||
"printer_id": "printer_001",
|
||||
"printer_name": "前台打印机"
|
||||
}
|
||||
```
|
||||
### 响应示例
|
||||
```json
|
||||
{
|
||||
"cloud_printer_bind_response": {
|
||||
"is_success": true,
|
||||
"printer_id": "printer_001",
|
||||
"request_id": "17666480184871668"
|
||||
}
|
||||
}
|
||||
```
|
||||
### 错误响应示例
|
||||
```json
|
||||
{
|
||||
"error_response": {
|
||||
"error_msg": "公共参数错误:type",
|
||||
"sub_msg": "",
|
||||
"sub_code": null,
|
||||
"error_code": 10001,
|
||||
"request_id": "15440104776643887"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 26. 云打印机设置--PddCloudPrinterSetting
|
||||
### 请求信息
|
||||
```gotemplate
|
||||
dll.PddCloudPrinterSetting(clientId, clientSecret, accessToken, requestJson)
|
||||
```
|
||||
### 请求参数
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--|--|--|----------|
|
||||
| clientId | string | 是 | 拼多多开放平台ClientID |
|
||||
| clientSecret | string | 是 | 拼多多开放平台ClientSecret |
|
||||
| accessToken | string | 是 | 授权令牌 |
|
||||
| requestJson | string | 是 | 云打印机设置JSON字符串 |
|
||||
#### 请求JSON结构示例
|
||||
```json
|
||||
{
|
||||
"printer_id": "printer_001",
|
||||
"auto_print": 1,
|
||||
"print_count": 1
|
||||
}
|
||||
```
|
||||
### 响应示例
|
||||
```json
|
||||
{
|
||||
"cloud_printer_setting_response": {
|
||||
"is_success": true,
|
||||
"request_id": "17666480184871669"
|
||||
}
|
||||
}
|
||||
```
|
||||
### 错误响应示例
|
||||
```json
|
||||
{
|
||||
"error_response": {
|
||||
"error_msg": "公共参数错误:type",
|
||||
"sub_msg": "",
|
||||
"sub_code": null,
|
||||
"error_code": 10001,
|
||||
"request_id": "15440104776643887"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 27. 云打印机状态查询--PddCloudPrinterStatusQuery
|
||||
### 请求信息
|
||||
```gotemplate
|
||||
dll.PddCloudPrinterStatusQuery(clientId, clientSecret, accessToken, requestJson)
|
||||
```
|
||||
### 请求参数
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--|--|--|----------|
|
||||
| clientId | string | 是 | 拼多多开放平台ClientID |
|
||||
| clientSecret | string | 是 | 拼多多开放平台ClientSecret |
|
||||
| accessToken | string | 是 | 授权令牌 |
|
||||
| requestJson | string | 是 | 云打印机状态查询JSON字符串 |
|
||||
#### 请求JSON结构示例
|
||||
```json
|
||||
{
|
||||
"printer_id": "printer_001"
|
||||
}
|
||||
```
|
||||
### 响应示例
|
||||
```json
|
||||
{
|
||||
"cloud_printer_status_query_response": {
|
||||
"printer_id": "printer_001",
|
||||
"printer_status": 1,
|
||||
"request_id": "17666480184871670"
|
||||
}
|
||||
}
|
||||
```
|
||||
### 错误响应示例
|
||||
```json
|
||||
{
|
||||
"error_response": {
|
||||
"error_msg": "公共参数错误:type",
|
||||
"sub_msg": "",
|
||||
"sub_code": null,
|
||||
"error_code": 10001,
|
||||
"request_id": "15440104776643887"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 28. 商品上架状态设置--PddGoodsSaleStatusSet
|
||||
### 请求信息
|
||||
```gotemplate
|
||||
dll.PddGoodsSaleStatusSet(clientId, clientSecret, accessToken, requestJson)
|
||||
```
|
||||
### 请求参数
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--|--|--|----------|
|
||||
| clientId | string | 是 | 拼多多开放平台ClientID |
|
||||
| clientSecret | string | 是 | 拼多多开放平台ClientSecret |
|
||||
| accessToken | string | 是 | 授权令牌 |
|
||||
| requestJson | string | 是 | 商品上架状态设置JSON字符串 |
|
||||
#### 请求JSON结构示例
|
||||
```json
|
||||
{
|
||||
"goods_id": 123456789,
|
||||
"is_onsale": 1
|
||||
}
|
||||
```
|
||||
### 响应示例
|
||||
```json
|
||||
{
|
||||
"goods_sale_status_set_response": {
|
||||
"is_success": true,
|
||||
"goods_id": 123456789,
|
||||
"request_id": "17666480184871671"
|
||||
}
|
||||
}
|
||||
```
|
||||
### 错误响应示例
|
||||
```json
|
||||
{
|
||||
"error_response": {
|
||||
"error_msg": "公共参数错误:type",
|
||||
"sub_msg": "",
|
||||
"sub_code": null,
|
||||
"error_code": 10001,
|
||||
"request_id": "15440104776643887"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 29. 删除商品接口--PddDeleteGoodsCommit
|
||||
### 请求信息
|
||||
```gotemplate
|
||||
dll.PddDeleteGoodsCommit(clientId, clientSecret, accessToken, requestJson)
|
||||
```
|
||||
### 请求参数
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--|--|--|----------|
|
||||
| clientId | string | 是 | 拼多多开放平台ClientID |
|
||||
| clientSecret | string | 是 | 拼多多开放平台ClientSecret |
|
||||
| accessToken | string | 是 | 授权令牌 |
|
||||
| requestJson | string | 是 | 删除商品请求JSON字符串 |
|
||||
#### 请求JSON结构示例
|
||||
```json
|
||||
{
|
||||
"goods_id": 123456789
|
||||
}
|
||||
```
|
||||
### 响应示例
|
||||
```json
|
||||
{
|
||||
"delete_goods_commit_response": {
|
||||
"is_success": true,
|
||||
"goods_id": 123456789,
|
||||
"request_id": "17666480184871672"
|
||||
}
|
||||
}
|
||||
```
|
||||
### 错误响应示例
|
||||
```json
|
||||
{
|
||||
"error_response": {
|
||||
"error_msg": "公共参数错误:type",
|
||||
"sub_msg": "",
|
||||
"sub_code": null,
|
||||
"error_code": 10001,
|
||||
"request_id": "15440104776643887"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 获取商品信息接口 -- OutPddAuthGetCommitDetailt
|
||||
### 请求信息
|
||||
```gotemplate
|
||||
|
||||
2231
newWalkingWithBooks.go
Normal file
2231
newWalkingWithBooks.go
Normal file
File diff suppressed because it is too large
Load Diff
BIN
pdd/dll/pdd.dll
BIN
pdd/dll/pdd.dll
Binary file not shown.
112
pdd/dll/pdd.h
112
pdd/dll/pdd.h
@ -87,82 +87,42 @@ typedef struct { void *data; GoInt len; GoInt cap; } GoSlice;
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
|
||||
// PddGoodsOuterCatMappingGet 类目预测
|
||||
//
|
||||
extern __declspec(dllexport) char* PddGoodsOuterCatMappingGet(char* clientId, char* clientSecret, char* accessToken, char* outerCatId, char* outerCatName, char* outerGoodsName);
|
||||
|
||||
// PddLogisticsCompaniesGet 快递公司查看
|
||||
//
|
||||
extern __declspec(dllexport) char* PddLogisticsCompaniesGet(char* clientId, char* clientSecret);
|
||||
|
||||
// PddErpOrderSync erp打单信息同步
|
||||
//
|
||||
extern __declspec(dllexport) char* PddErpOrderSync(char* clientId, char* clientSecret, char* accessToken, char* logisticsId, char* orderSn, char* orderState, char* waybillNo);
|
||||
|
||||
// PddOrderSynchronization 拼多多订单同步
|
||||
//
|
||||
extern __declspec(dllexport) char* PddOrderSynchronization(char* clientId, char* clientSecret, char* accessToken, char* logisticsCompany, char* logisticsOnlineSendJson);
|
||||
|
||||
// PddGoodsImgUpload 商品图片上传接口
|
||||
//
|
||||
extern __declspec(dllexport) char* PddGoodsImgUpload(char* clientId, char* clientSecret, char* accessToken, char* filePath);
|
||||
|
||||
// PddGoodsAdd 商品新增接口
|
||||
//
|
||||
extern __declspec(dllexport) char* PddGoodsAdd(char* clientId, char* clientSecret, char* accessToken, char* goodsAddJson);
|
||||
|
||||
// SelfPddGoodsAdd 联合拼多多图片上传的商品新增
|
||||
//
|
||||
extern __declspec(dllexport) char* SelfPddGoodsAdd(char* clientId, char* clientSecret, char* accessToken, char* filePath, char* goodsAddJson);
|
||||
|
||||
// PddOpenDecryptMaskBatch 批量数据解密脱敏接口
|
||||
//
|
||||
extern __declspec(dllexport) char* PddOpenDecryptMaskBatch(char* clientId, char* clientSecret, char* accessToken, char* reqJson);
|
||||
|
||||
// PddGoodsSpecIdGet 生成商家自定义的规格
|
||||
//
|
||||
extern __declspec(dllexport) char* PddGoodsSpecIdGet(char* clientId, char* clientSecret, char* accessToken, char* parentSpecId, char* specName);
|
||||
|
||||
// PddGoodsSkuPriceUpdate 修改商品sku价格
|
||||
//
|
||||
extern __declspec(dllexport) char* PddGoodsSkuPriceUpdate(char* clientId, char* clientSecret, char* accessToken, char* request);
|
||||
|
||||
// PddGoodsQuantityUpdate 商品库存更新接口
|
||||
//
|
||||
extern __declspec(dllexport) char* PddGoodsQuantityUpdate(char* clientId, char* clientSecret, char* accessToken, char* request);
|
||||
|
||||
// PddGoodsImageUpload 商品图片上传接口
|
||||
//
|
||||
extern __declspec(dllexport) char* PddGoodsImageUpload(char* clientId, char* clientSecret, char* accessToken, char* fileBase);
|
||||
|
||||
// OutPddAuthGetCommitDetailt 获取商品信息
|
||||
//
|
||||
extern __declspec(dllexport) char* OutPddAuthGetCommitDetailt(char* goodsCommitId, char* goodsId, char* accessToken);
|
||||
|
||||
// OutPddAuthGetGoodsDetail 获取商品信息
|
||||
//
|
||||
extern __declspec(dllexport) char* OutPddAuthGetGoodsDetail(char* goodsId, char* accessToken);
|
||||
|
||||
// OutPddAuthSetSpec 生成自定义规格
|
||||
//
|
||||
extern __declspec(dllexport) char* OutPddAuthSetSpec(int specTypeId, char* specName, char* accessToken);
|
||||
|
||||
// OutPddAuthGetCats 获取商品信息
|
||||
//
|
||||
extern __declspec(dllexport) char* OutPddAuthGetCats(void);
|
||||
|
||||
// OutPddAuthUpdatePrice 修改价格
|
||||
//
|
||||
extern __declspec(dllexport) char* OutPddAuthUpdatePrice(char* jsonData);
|
||||
|
||||
// OutPddAuthUpdateStock 修改库存
|
||||
//
|
||||
extern __declspec(dllexport) char* OutPddAuthUpdateStock(char* jsonData);
|
||||
|
||||
// FreeCString 释放C字符串内存
|
||||
//
|
||||
extern __declspec(dllexport) void FreeCString(char* str);
|
||||
extern char* PddGoodsOuterCatMappingGet(char* clientId, char* clientSecret, char* accessToken, char* outerCatId, char* outerCatName, char* outerGoodsName);
|
||||
extern char* PddLogisticsCompaniesGet(char* clientId, char* clientSecret);
|
||||
extern char* PddErpOrderSync(char* clientId, char* clientSecret, char* accessToken, char* logisticsId, char* orderSn, char* orderState, char* waybillNo);
|
||||
extern char* PddOrderSynchronization(char* clientId, char* clientSecret, char* accessToken, char* logisticsCompany, char* logisticsOnlineSendJson);
|
||||
extern char* PddGoodsImgUpload(char* clientId, char* clientSecret, char* accessToken, char* filePath);
|
||||
extern char* PddGoodsAdd(char* clientId, char* clientSecret, char* accessToken, char* goodsAddJson);
|
||||
extern char* SelfPddGoodsAdd(char* clientId, char* clientSecret, char* accessToken, char* filePath, char* goodsAddJson);
|
||||
extern char* PddOpenDecryptMaskBatch(char* clientId, char* clientSecret, char* accessToken, char* reqJson);
|
||||
extern char* PddGoodsSpecIdGet(char* clientId, char* clientSecret, char* accessToken, char* parentSpecId, char* specName);
|
||||
extern char* PddGoodsSkuPriceUpdate(char* clientId, char* clientSecret, char* accessToken, char* requestJson);
|
||||
extern char* PddGoodsQuantityUpdate(char* clientId, char* clientSecret, char* accessToken, char* request);
|
||||
extern char* PddGoodsImageUpload(char* clientId, char* clientSecret, char* accessToken, char* fileBase);
|
||||
extern char* PddOrderBasicListGet(char* clientId, char* clientSecret, char* accessToken, char* orderBasicListGetJSON);
|
||||
extern char* PddGoodsCommitDetailGet(char* clientId, char* clientSecret, char* accessToken, char* goodsCommitId, char* goodsId);
|
||||
extern char* PddTimeGet(char* clientId, char* clientSecret, char* accessToken);
|
||||
extern char* PddWaybillSearch(char* clientId, char* clientSecret, char* accessToken, char* requestJson);
|
||||
extern char* PddFdsWaybillGet(char* clientId, char* clientSecret, char* accessToken, char* requestJson);
|
||||
extern char* PddWaybillCancel(char* clientId, char* clientSecret, char* accessToken, char* requestJson);
|
||||
extern char* PddGoodsListGet(char* clientId, char* clientSecret, char* accessToken, char* requestJson);
|
||||
extern char* PddWaybillGet(char* clientId, char* clientSecret, char* accessToken, char* requestJson);
|
||||
extern char* PddWaybillQueryByWaybillcode(char* clientId, char* clientSecret, char* accessToken, char* requestJson);
|
||||
extern char* PddCloudPrint(char* clientId, char* clientSecret, char* accessToken, char* requestJson);
|
||||
extern char* PddCloudPrintTaskQuery(char* clientId, char* clientSecret, char* accessToken, char* requestJson);
|
||||
extern char* PddCloudPrintVerifyCode(char* clientId, char* clientSecret, char* accessToken, char* requestJson);
|
||||
extern char* PddCloudPrinterBind(char* clientId, char* clientSecret, char* accessToken, char* requestJson);
|
||||
extern char* PddCloudPrinterSetting(char* clientId, char* clientSecret, char* accessToken, char* requestJson);
|
||||
extern char* PddCloudPrinterStatusQuery(char* clientId, char* clientSecret, char* accessToken, char* requestJson);
|
||||
extern char* PddGoodsSaleStatusSet(char* clientId, char* clientSecret, char* accessToken, char* requestJson);
|
||||
extern char* PddDeleteGoodsCommit(char* clientId, char* clientSecret, char* accessToken, char* requestJson);
|
||||
extern char* OutPddAuthGetCommitDetailt(char* goodsCommitId, char* goodsId, char* accessToken);
|
||||
extern char* OutPddAuthGetGoodsDetail(char* goodsId, char* accessToken);
|
||||
extern char* OutPddAuthSetSpec(int specTypeId, char* specName, char* accessToken);
|
||||
extern char* OutPddAuthGetCats(void);
|
||||
extern char* OutPddAuthUpdatePrice(char* jsonData);
|
||||
extern char* OutPddAuthUpdateStock(char* jsonData);
|
||||
extern void FreeCString(char* str);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
1731
pdd/pdd.go
1731
pdd/pdd.go
File diff suppressed because it is too large
Load Diff
@ -266,12 +266,32 @@ type DataList struct {
|
||||
}
|
||||
|
||||
//func main() {
|
||||
// jsonStr := `{"goods_name":"未厌居习作未厌居习作9787543421523叶圣陶(1894.10~1988.2)","carousel_gallery":["https://img.pddpic.com/open-gw/2025-11-30/30eb451b-1fea-4f23-be25-b97454bf677a.jpeg","https://img.pddpic.com/open-gw/2025-11-30/30eb451b-1fea-4f23-be25-b97454bf677a.jpeg","https://img.pddpic.com/open-gw/2025-11-30/30eb451b-1fea-4f23-be25-b97454bf677a.jpeg","https://img.pddpic.com/open-gw/2025-11-30/30eb451b-1fea-4f23-be25-b97454bf677a.jpeg","https://img.pddpic.com/open-gw/2025-11-30/30eb451b-1fea-4f23-be25-b97454bf677a.jpeg","https://img.pddpic.com/open-gw/2025-11-30/30eb451b-1fea-4f23-be25-b97454bf677a.jpeg"],"cat_id":0,"goods_type":1,"market_price":515,"detail_gallery":[],"out_goods_id":"9787543421523","sku_list":[{"is_onsale":0,"limit_quantity":999,"multi_price":415,"price":515,"sku_properties":[{"punit":"","ref_pid":0,"value":"","vid":0,"spec_id_list":"1","thumb_url":"https://img.pddpic.com/open-gw/2025-11-30/30eb451b-1fea-4f23-be25-b97454bf677a.jpeg","weight":250}],"quantity":999}],"is_folt":false,"is_pre_sale":false,"is_refundable":false,"second_hand":true,"cost_template_id":0,"country_id":0,"shipment_limit_second":172800}`
|
||||
// add, err := pddGoodsAdd("203c5a7ba8bd4b8488d5e26f93052642",
|
||||
// //jsonStr := `{"goods_name":"未厌居习作未厌居习作9787543421523叶圣陶(1894.10~1988.2)","carousel_gallery":["https://img.pddpic.com/open-gw/2025-11-30/30eb451b-1fea-4f23-be25-b97454bf677a.jpeg","https://img.pddpic.com/open-gw/2025-11-30/30eb451b-1fea-4f23-be25-b97454bf677a.jpeg","https://img.pddpic.com/open-gw/2025-11-30/30eb451b-1fea-4f23-be25-b97454bf677a.jpeg","https://img.pddpic.com/open-gw/2025-11-30/30eb451b-1fea-4f23-be25-b97454bf677a.jpeg","https://img.pddpic.com/open-gw/2025-11-30/30eb451b-1fea-4f23-be25-b97454bf677a.jpeg","https://img.pddpic.com/open-gw/2025-11-30/30eb451b-1fea-4f23-be25-b97454bf677a.jpeg"],"cat_id":0,"goods_type":1,"market_price":515,"detail_gallery":[],"out_goods_id":"9787543421523","sku_list":[{"is_onsale":0,"limit_quantity":999,"multi_price":415,"price":515,"sku_properties":[{"punit":"","ref_pid":0,"value":"","vid":0,"spec_id_list":"1","thumb_url":"https://img.pddpic.com/open-gw/2025-11-30/30eb451b-1fea-4f23-be25-b97454bf677a.jpeg","weight":250}],"quantity":999}],"is_folt":false,"is_pre_sale":false,"is_refundable":false,"second_hand":true,"cost_template_id":0,"country_id":0,"shipment_limit_second":172800}`
|
||||
// add, err := pddTimeGet("203c5a7ba8bd4b8488d5e26f93052642",
|
||||
// "892ffaa86e12b7a3d8d2942b669d9aa520ad8179",
|
||||
// "1177d0c36419417eba692a3fea88f611d42f0665", jsonStr)
|
||||
// "1177d0c36419417eba692a3fea88f611d42f0665")
|
||||
// if err != nil {
|
||||
// fmt.Println(err.Error())
|
||||
// }
|
||||
// fmt.Println(add)
|
||||
//}
|
||||
|
||||
//func main() {
|
||||
// 替换为你的图片路径
|
||||
//imagePath := "D:\\isbn_images\\9771005867004.jpg"
|
||||
|
||||
//url := "http://103.236.68.64:19000/public-img/bbb.jpg"
|
||||
|
||||
// http://36.212.7.35:19000/public-img/e5ef25ae85853f3e_s.jpg
|
||||
|
||||
//fPath := "D:\\isbn_images\\9780064408561.jpg"
|
||||
//
|
||||
//baseUrl := "http://36.212.7.35:19000/public-img/" + filepath.Base(fPath)
|
||||
////picSpaceResult, err := utils.UploadImageToSpace(imgResult.PicPath, baseUrl)
|
||||
//
|
||||
//space, err := UploadImageToSpace(fPath, baseUrl)
|
||||
//if err != nil {
|
||||
// fmt.Println(err)
|
||||
//}
|
||||
//fmt.Println(space)
|
||||
//}
|
||||
|
||||
Binary file not shown.
@ -1,129 +0,0 @@
|
||||
/* Code generated by cmd/cgo; DO NOT EDIT. */
|
||||
|
||||
/* package command-line-arguments */
|
||||
|
||||
|
||||
#line 1 "cgo-builtin-export-prolog"
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#ifndef GO_CGO_EXPORT_PROLOGUE_H
|
||||
#define GO_CGO_EXPORT_PROLOGUE_H
|
||||
|
||||
#ifndef GO_CGO_GOSTRING_TYPEDEF
|
||||
typedef struct { const char *p; ptrdiff_t n; } _GoString_;
|
||||
extern size_t _GoStringLen(_GoString_ s);
|
||||
extern const char *_GoStringPtr(_GoString_ s);
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
/* Start of preamble from import "C" comments. */
|
||||
|
||||
|
||||
#line 3 "proxy.go"
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#line 1 "cgo-generated-wrapper"
|
||||
|
||||
|
||||
/* End of preamble from import "C" comments. */
|
||||
|
||||
|
||||
/* Start of boilerplate cgo prologue. */
|
||||
#line 1 "cgo-gcc-export-header-prolog"
|
||||
|
||||
#ifndef GO_CGO_PROLOGUE_H
|
||||
#define GO_CGO_PROLOGUE_H
|
||||
|
||||
typedef signed char GoInt8;
|
||||
typedef unsigned char GoUint8;
|
||||
typedef short GoInt16;
|
||||
typedef unsigned short GoUint16;
|
||||
typedef int GoInt32;
|
||||
typedef unsigned int GoUint32;
|
||||
typedef long long GoInt64;
|
||||
typedef unsigned long long GoUint64;
|
||||
typedef GoInt64 GoInt;
|
||||
typedef GoUint64 GoUint;
|
||||
typedef size_t GoUintptr;
|
||||
typedef float GoFloat32;
|
||||
typedef double GoFloat64;
|
||||
#ifdef _MSC_VER
|
||||
#if !defined(__cplusplus) || _MSVC_LANG <= 201402L
|
||||
#include <complex.h>
|
||||
typedef _Fcomplex GoComplex64;
|
||||
typedef _Dcomplex GoComplex128;
|
||||
#else
|
||||
#include <complex>
|
||||
typedef std::complex<float> GoComplex64;
|
||||
typedef std::complex<double> GoComplex128;
|
||||
#endif
|
||||
#else
|
||||
typedef float _Complex GoComplex64;
|
||||
typedef double _Complex GoComplex128;
|
||||
#endif
|
||||
|
||||
/*
|
||||
static assertion to make sure the file is being used on architecture
|
||||
at least with matching size of GoInt.
|
||||
*/
|
||||
typedef char _check_for_64_bit_pointer_matching_GoInt[sizeof(void*)==64/8 ? 1:-1];
|
||||
|
||||
#ifndef GO_CGO_GOSTRING_TYPEDEF
|
||||
typedef _GoString_ GoString;
|
||||
#endif
|
||||
typedef void *GoMap;
|
||||
typedef void *GoChan;
|
||||
typedef struct { void *t; void *v; } GoInterface;
|
||||
typedef struct { void *data; GoInt len; GoInt cap; } GoSlice;
|
||||
|
||||
#endif
|
||||
|
||||
/* End of boilerplate cgo prologue. */
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
|
||||
// GetProxyHealth 导出函数:获取代理健康状态(用于调试)
|
||||
//
|
||||
extern __declspec(dllexport) char* GetProxyHealth(void);
|
||||
|
||||
// ProxyTypeManager 导出函数:代理类型管理器
|
||||
//
|
||||
extern __declspec(dllexport) char* ProxyTypeManager(char* proxyType, char* username, char* password, char* machineCode);
|
||||
|
||||
// GetMachineCode 导出函数:查询机器码
|
||||
//
|
||||
extern __declspec(dllexport) char* GetMachineCode(char* tailCardSecret);
|
||||
|
||||
// RechargeCard 导出函数:充值卡密
|
||||
//
|
||||
extern __declspec(dllexport) char* RechargeCard(char* tailCardSecret, char* machineCode);
|
||||
|
||||
// GetProxies 导出函数:获取代理服务器列表
|
||||
//
|
||||
extern __declspec(dllexport) char* GetProxies(char* machineCode);
|
||||
|
||||
// CheckTailCardSecretExpired 导出函数:检查卡密是否过期
|
||||
//
|
||||
extern __declspec(dllexport) char* CheckTailCardSecretExpired(char* tailCardSecret);
|
||||
|
||||
// InitProxyManager 导出函数:初始化代理管理器
|
||||
//
|
||||
extern __declspec(dllexport) char* InitProxyManager(char* serversJson, char* username, char* password, char* tailCardSecret, char* proxyType);
|
||||
|
||||
// FreeCString 导出函数:释放C字符串内存
|
||||
//
|
||||
extern __declspec(dllexport) void FreeCString(char* str);
|
||||
|
||||
// GetVersion 获取版本
|
||||
//
|
||||
extern __declspec(dllexport) char* GetVersion(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
BIN
proxy/proxy.dll
BIN
proxy/proxy.dll
Binary file not shown.
761
proxy/proxy.go
761
proxy/proxy.go
@ -8,538 +8,168 @@ import (
|
||||
"crypto/md5"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/parnurzeal/gorequest"
|
||||
"log"
|
||||
"math/rand"
|
||||
"net/url"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/parnurzeal/gorequest"
|
||||
)
|
||||
|
||||
// 代理类型常量
|
||||
const (
|
||||
CalfElephantProxyType = "CALF_ELEPHANT_PROXY" // 小象代理类型
|
||||
TailProxyType = "TAIL_PROXY" // 尾巴代理类型
|
||||
TailProxyType = "TAIL_PROXY" // 尾巴代理类型
|
||||
)
|
||||
|
||||
// 小象代理服务器列表
|
||||
// 互斥锁,用于保护全局随机数生成器的并发访问
|
||||
var (
|
||||
servers = []string{
|
||||
"http-dynamic.xiaoxiangdaili.com",
|
||||
"http-dynamic-S02.xiaoxiangdaili.com",
|
||||
"http-dynamic-S03.xiaoxiangdaili.com",
|
||||
"http-dynamic-S04.xiaoxiangdaili.com",
|
||||
}
|
||||
// 互斥锁,用于保护全局随机数生成器的并发访问
|
||||
randMutex sync.Mutex
|
||||
globalRand *rand.Rand // 全局随机数生成器
|
||||
// 代理健康状态管理
|
||||
// 代理健康状态映射表,key为代理主机名,value为健康状态
|
||||
proxyHealthMaps = make(map[string]*ProxyHealth)
|
||||
proxyHealthMutex sync.RWMutex
|
||||
globalRand *rand.Rand
|
||||
)
|
||||
|
||||
// ProxyManager 代理管理器结构体,用于管理代理配置信息
|
||||
type ProxyManager struct {
|
||||
servers []string `json:"servers"` // 代理服务器列表
|
||||
username string `json:"username"` // 代理账号
|
||||
password string `json:"password"` // 代理密码
|
||||
tailCardSecret string `json:"tail_card_secret"` // 尾巴代理卡密
|
||||
proxyType string `json:"proxy_type"` // 代理类型 CALF_ELEPHANT_PROXY/TAIL_PROXY
|
||||
}
|
||||
|
||||
// ProxyHealth 代理健康状态结构体,用于跟踪代理的健康状况
|
||||
type ProxyHealth struct {
|
||||
SuccessCount int // 成功次数
|
||||
FailCount int // 失败次数
|
||||
LastCheck time.Time // 最后检查时间
|
||||
ResponseTime time.Duration // 响应时间
|
||||
IsHealthy bool // 是否健康
|
||||
}
|
||||
|
||||
// 初始化函数,在程序启动时自动执行
|
||||
// 初始化函数
|
||||
func init() {
|
||||
// 创建全局的随机数生成器,使用当前时间作为种子
|
||||
globalRand = rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
}
|
||||
|
||||
/*
|
||||
* 获取代理URL,代理类型管理器,根据代理类型构建不同的代理URL
|
||||
* param proxyType[string] 代理类型
|
||||
* param username[string] 代理用户名
|
||||
* param password[string] 代理密码
|
||||
* param machineCode[string] 机器码
|
||||
* return 代理服务器IP,错误信息
|
||||
*/
|
||||
func proxyTypeManager(proxyType, username, password, machineCode string) (string, error) {
|
||||
// 根据代理类型获取代理URL
|
||||
func getProxyByType(proxyType, machineCode string) (string, error) {
|
||||
switch proxyType {
|
||||
case CalfElephantProxyType:
|
||||
// 小象代理:使用用户名和密码构建代理URL
|
||||
return buildCalfElephantProxyURL(username, password)
|
||||
case TailProxyType:
|
||||
// 尾巴代理:使用机器码构建代理URL
|
||||
return buildTailProxyURL(machineCode)
|
||||
return getTailProxyURL(machineCode)
|
||||
default:
|
||||
// 不支持的代理类型,返回错误
|
||||
return "", fmt.Errorf("不支持的代理类型: %s", proxyType)
|
||||
}
|
||||
}
|
||||
|
||||
func proxyTypeManagerNew(proxyType, username, password, machineCode string) (string, error) {
|
||||
// 获取尾巴代理URL
|
||||
func getTailProxyURL(machineCode string) (string, error) {
|
||||
// 获取代理列表
|
||||
proxies, err := getProxies(machineCode)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// 检查是否获取到有效代理
|
||||
if len(proxies) == 0 {
|
||||
return "", fmt.Errorf("未获取到有效代理")
|
||||
}
|
||||
// 获取代理
|
||||
|
||||
// 随机选择一个代理
|
||||
proxy := randomElement(proxies)
|
||||
proxy = fmt.Sprintf("http://%s", proxy)
|
||||
return proxy, nil
|
||||
}
|
||||
proxyURL := fmt.Sprintf("http://%s", proxy)
|
||||
|
||||
/*
|
||||
* 构建小象代理URL
|
||||
* param username[string] 代理用户名
|
||||
* param password[string] 代理密码
|
||||
* return 代理服务器IP,错误信息
|
||||
*/
|
||||
func buildCalfElephantProxyURL(username, password string) (string, error) {
|
||||
// 随机选择一个代理服务器
|
||||
server := randomServer()
|
||||
// 构建代理URL格式:http://用户名:密码@服务器:端口
|
||||
proxyURL := fmt.Sprintf("http://%s:%s@%s:%d",
|
||||
url.QueryEscape(username), // URL编码用户名,防止特殊字符问题
|
||||
url.QueryEscape(password), // URL编码密码,防止特殊字符问题
|
||||
server, // 服务器地址
|
||||
10030) // 固定端口号
|
||||
|
||||
// 检测代理可用性
|
||||
if err := checkProxyHealth(proxyURL); err != nil {
|
||||
// 代理检测失败,记录警告日志
|
||||
log.Printf("[WARN] 代理 %s 检测失败: %v", server, err)
|
||||
// 尝试下一个代理服务器
|
||||
return tryNextCalfElephantProxy(username, password, server)
|
||||
}
|
||||
// 代理检测成功,记录信息日志
|
||||
log.Printf("[INFO] 使用小象代理: %s", server)
|
||||
return proxyURL, nil
|
||||
}
|
||||
|
||||
/*
|
||||
* 尝试下一个小象代理服务器
|
||||
* param username[string] 代理用户名
|
||||
* param password[string] 代理密码
|
||||
* param failedServer[string] 代理服务器
|
||||
* return 代理服务器IP,错误信息
|
||||
*/
|
||||
func tryNextCalfElephantProxy(username, password, failedServer string) (string, error) {
|
||||
// 创建服务器副本并排除失败的服务器
|
||||
availableServers := make([]string, 0)
|
||||
for _, server := range servers {
|
||||
if server != failedServer {
|
||||
availableServers = append(availableServers, server)
|
||||
}
|
||||
}
|
||||
// 如果没有可用的服务器,返回错误
|
||||
if len(availableServers) == 0 {
|
||||
return "", fmt.Errorf("所有小象代理服务器都不可用")
|
||||
}
|
||||
|
||||
// 随机尝试可用服务器
|
||||
for _, server := range shuffleServers(availableServers) {
|
||||
// 构建代理URL
|
||||
proxyURL := fmt.Sprintf("http://%s:%s@%s:%d",
|
||||
url.QueryEscape(username),
|
||||
url.QueryEscape(password),
|
||||
server,
|
||||
10030)
|
||||
// 检测代理可用性
|
||||
if err := checkProxyHealth(proxyURL); err == nil {
|
||||
log.Printf("[INFO] 切换到可用代理: %s", server)
|
||||
return proxyURL, nil
|
||||
}
|
||||
// 代理不可用,记录警告日志
|
||||
log.Printf("[WARN] 代理 %s 检测失败", server)
|
||||
}
|
||||
// 所有服务器都检测失败,返回错误
|
||||
return "", fmt.Errorf("所有可用的小象代理服务器都检测失败")
|
||||
}
|
||||
|
||||
/*
|
||||
* 构建内置代理URL
|
||||
* param machineCode[string] 机器码
|
||||
* return 代理服务器IP,错误信息
|
||||
*/
|
||||
func buildTailProxyURL(machineCode string) (string, error) {
|
||||
// 获取代理列表
|
||||
proxies, err := getProxies(machineCode)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
// 检查是否获取到有效代理
|
||||
if len(proxies) == 0 {
|
||||
return "", fmt.Errorf("未获取到有效代理")
|
||||
}
|
||||
|
||||
// 过滤并选择健康的代理
|
||||
healthyProxies := filterHealthyProxies(proxies)
|
||||
if len(healthyProxies) > 0 {
|
||||
// 从健康代理中随机选择一个
|
||||
proxyURL := "http://" + randomElement(healthyProxies)
|
||||
log.Printf("[INFO] 使用健康尾巴代理: %s", proxyURL)
|
||||
return proxyURL, nil
|
||||
}
|
||||
|
||||
// 如果没有健康代理,检测所有代理
|
||||
log.Printf("[INFO] 未找到健康代理,开始检测代理可用性...")
|
||||
return findWorkingTailProxy(proxies)
|
||||
}
|
||||
|
||||
/*
|
||||
* 过滤健康代理,返回当前健康的代理列表
|
||||
* param proxies[[]string] 内置代理服务器数组
|
||||
* return 内置代理服务器数组,错误信息
|
||||
*/
|
||||
func filterHealthyProxies(proxies []string) []string {
|
||||
// 获取读锁,允许多个goroutine同时读取
|
||||
proxyHealthMutex.RLock()
|
||||
defer proxyHealthMutex.RUnlock() // 确保函数结束时释放锁
|
||||
|
||||
var healthy []string
|
||||
for _, proxy := range proxies {
|
||||
if health, exists := proxyHealthMaps[proxy]; exists && health.IsHealthy {
|
||||
// 检查是否在最近检查过(5分钟内),避免使用过时的健康状态
|
||||
if time.Since(health.LastCheck) < 5*time.Minute {
|
||||
healthy = append(healthy, proxy)
|
||||
}
|
||||
}
|
||||
}
|
||||
return healthy
|
||||
}
|
||||
|
||||
/*
|
||||
* 查找可用的尾巴代理
|
||||
* param proxies[[]string] 内置代理服务器数组
|
||||
* return 可用的代理服务器IP,错误信息
|
||||
*/
|
||||
func findWorkingTailProxy(proxies []string) (string, error) {
|
||||
// 打乱代理顺序
|
||||
shuffledProxies := shuffleServers(proxies)
|
||||
|
||||
// 并发检测代理
|
||||
type proxyResult struct {
|
||||
proxy string
|
||||
err error
|
||||
}
|
||||
// 创建带缓冲的通道,用于收集检测结果
|
||||
ch := make(chan proxyResult, len(shuffledProxies))
|
||||
var wg sync.WaitGroup // 等待组,用于等待所有检测goroutine完成
|
||||
|
||||
// 限制并发数,避免同时检测太多代理导致网络拥塞
|
||||
sem := make(chan struct{}, 5)
|
||||
// 并发检测每个代理
|
||||
for _, proxy := range shuffledProxies {
|
||||
wg.Add(1) // 增加等待组计数
|
||||
go func(p string) {
|
||||
defer wg.Done() // 函数结束时减少等待组计数
|
||||
sem <- struct{}{} // 获取信号量,限制并发数
|
||||
defer func() { <-sem }() // 释放信号量
|
||||
|
||||
proxyURL := "http://" + p
|
||||
err := checkProxyHealth(proxyURL)
|
||||
// 将检测结果发送到通道
|
||||
ch <- proxyResult{proxy: p, err: err}
|
||||
}(proxy)
|
||||
}
|
||||
// 等待所有检测goroutine完成
|
||||
wg.Wait()
|
||||
close(ch) // 关闭通道,表示不会再有数据发送
|
||||
|
||||
// 收集结果,查找可用的代理
|
||||
var workingProxies []string
|
||||
for result := range ch {
|
||||
if result.err == nil {
|
||||
// 代理可用,添加到工作代理列表
|
||||
workingProxies = append(workingProxies, result.proxy)
|
||||
// 更新健康状态
|
||||
updateProxyHealth(result.proxy, true, 0)
|
||||
} else {
|
||||
// 代理不可用,更新健康状态
|
||||
updateProxyHealth(result.proxy, false, 0)
|
||||
}
|
||||
}
|
||||
// 如果有可用的代理,随机选择一个
|
||||
if len(workingProxies) > 0 {
|
||||
selected := randomElement(workingProxies)
|
||||
log.Printf("[INFO] 找到可用代理: %s (共 %d 个可用)", selected, len(workingProxies))
|
||||
return "http://" + selected, nil
|
||||
}
|
||||
// 所有代理都不可用,返回错误
|
||||
return "", fmt.Errorf("所有尾巴代理都不可用")
|
||||
}
|
||||
|
||||
/*
|
||||
* 检测代理健康状态,通过访问测试网站验证代理可用性
|
||||
* param proxyURL[string] 代理服务器
|
||||
* return 错误信息
|
||||
*/
|
||||
func checkProxyHealth(proxyURL string) error {
|
||||
start := time.Now() // 记录开始时间,用于计算响应时间
|
||||
// 创建HTTP请求,设置代理和超时
|
||||
req := gorequest.New().Proxy(proxyURL).Timeout(10 * time.Second)
|
||||
// 发送GET请求到测试网站
|
||||
resp, _, errs := req.Get("https://shop.kongfz.com/").End()
|
||||
|
||||
responseTime := time.Since(start) // 计算响应时间
|
||||
// 检查请求错误
|
||||
if len(errs) > 0 {
|
||||
updateProxyHealth(proxyURL, false, responseTime)
|
||||
return fmt.Errorf("代理连接失败: %v", errs)
|
||||
}
|
||||
// 检查HTTP状态码
|
||||
if resp.StatusCode != 200 {
|
||||
updateProxyHealth(proxyURL, false, responseTime)
|
||||
return fmt.Errorf("代理响应状态码错误: %d", resp.StatusCode)
|
||||
}
|
||||
// 代理检测成功
|
||||
updateProxyHealth(proxyURL, true, responseTime)
|
||||
log.Printf("[DEBUG] 代理 %s 检测成功, 响应时间: %v", getProxyHost(proxyURL), responseTime)
|
||||
return nil
|
||||
}
|
||||
|
||||
/*
|
||||
* 更新代理健康状态
|
||||
* param proxyURL[string] 代理服务器
|
||||
* param success[bool] 是否成功
|
||||
* param responseTime[time.Duration] 响应时间
|
||||
*/
|
||||
func updateProxyHealth(proxyURL string, success bool, responseTime time.Duration) {
|
||||
// 获取写锁,确保更新操作的互斥性
|
||||
proxyHealthMutex.Lock()
|
||||
defer proxyHealthMutex.Unlock() // 确保函数结束时释放锁
|
||||
// 获取代理主机名作为键
|
||||
host := getProxyHost(proxyURL)
|
||||
// 如果代理健康状态不存在,创建新的
|
||||
if _, exists := proxyHealthMaps[host]; !exists {
|
||||
proxyHealthMaps[host] = &ProxyHealth{}
|
||||
}
|
||||
|
||||
health := proxyHealthMaps[host]
|
||||
health.LastCheck = time.Now() // 更新最后检查时间
|
||||
|
||||
if success {
|
||||
// 成功的情况
|
||||
health.SuccessCount++ // 增加成功次数
|
||||
health.FailCount = 0 // 重置失败次数
|
||||
health.ResponseTime = responseTime // 记录响应时间
|
||||
health.IsHealthy = true // 标记为健康
|
||||
} else {
|
||||
// 失败的情况
|
||||
health.FailCount++ // 增加失败次数
|
||||
health.SuccessCount = 0 // 重置成功次数
|
||||
// 连续失败3次标记为不健康
|
||||
if health.FailCount >= 3 {
|
||||
health.IsHealthy = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* 获取代理主机名
|
||||
* param proxyURL[string] 代理服务器
|
||||
* return 代理服务器IP
|
||||
*/
|
||||
func getProxyHost(proxyURL string) string {
|
||||
// 去除协议前缀
|
||||
if strings.HasPrefix(proxyURL, "http://") {
|
||||
proxyURL = proxyURL[7:]
|
||||
}
|
||||
// 去除认证信息(用户名:密码@部分)
|
||||
if atIndex := strings.Index(proxyURL, "@"); atIndex != -1 {
|
||||
proxyURL = proxyURL[atIndex+1:]
|
||||
}
|
||||
// 去除端口
|
||||
if colonIndex := strings.Index(proxyURL, ":"); colonIndex != -1 {
|
||||
proxyURL = proxyURL[:colonIndex]
|
||||
}
|
||||
return proxyURL
|
||||
}
|
||||
|
||||
// 随机代理服务器
|
||||
func randomServer() string {
|
||||
return randomElement(servers)
|
||||
}
|
||||
|
||||
// 线程安全的随机元素选择
|
||||
func randomElement(slice []string) string {
|
||||
randMutex.Lock() // 获取互斥锁
|
||||
defer randMutex.Unlock() // 确保函数结束时释放锁
|
||||
return slice[globalRand.Intn(len(slice))] // 随机选择一个元素
|
||||
}
|
||||
|
||||
// 打乱服务器顺序
|
||||
func shuffleServers(servers []string) []string {
|
||||
randMutex.Lock()
|
||||
defer randMutex.Unlock()
|
||||
// 创建服务器副本,避免修改原始切片
|
||||
shuffled := make([]string, len(servers))
|
||||
copy(shuffled, servers)
|
||||
// 使用Fisher-Yates算法打乱顺序
|
||||
globalRand.Shuffle(len(shuffled), func(i, j int) {
|
||||
shuffled[i], shuffled[j] = shuffled[j], shuffled[i]
|
||||
})
|
||||
return shuffled
|
||||
return slice[globalRand.Intn(len(slice))]
|
||||
}
|
||||
|
||||
/*
|
||||
* 检查卡密是否过期
|
||||
* param tailCardSecret[string] 卡密
|
||||
* return 是否过期,错误信息
|
||||
*/
|
||||
func checkTailCardSecretExpired(tailCardSecret string) (bool, error) {
|
||||
// 获取机器码信息
|
||||
code, err := getMachineCode(tailCardSecret)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("请求错误: %v", err)
|
||||
}
|
||||
// 解析过期时间字符串
|
||||
targetTime, err := time.Parse("2006-01-02 15:04:05", code.Data.IpExpTime)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("时间格式错误: %v", err)
|
||||
}
|
||||
currentTime := time.Now()
|
||||
// 卡密日期在当前日期之后表示有效
|
||||
if targetTime.After(currentTime) {
|
||||
return true, nil
|
||||
} else {
|
||||
// 卡密已过期
|
||||
return false, fmt.Errorf("卡密已经过期!过期时间: %v", targetTime)
|
||||
}
|
||||
// MachineCodeResponse 查询机器码响应结构体
|
||||
type MachineCodeResponse struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
Data MachineCodeData `json:"data"`
|
||||
}
|
||||
|
||||
// 定义响应结构体
|
||||
type getMachineCodeResp struct {
|
||||
Code int `json:"code"` // 状态码
|
||||
Message string `json:"message"` // 消息
|
||||
Data struct {
|
||||
MachineCode string `json:"machine_code"` // 机器码
|
||||
IpExpTime string `json:"ip_exp_time"` // IP过期时间
|
||||
IpThread int `json:"ip_thread"` // IP线程数
|
||||
IpCardCode string `json:"ip_card_code"` // IP卡密
|
||||
} `json:"data"`
|
||||
// MachineCodeData 机器码数据
|
||||
type MachineCodeData struct {
|
||||
MachineCode string `json:"machine_code"` // 机器码
|
||||
IpExpTime string `json:"ip_exp_time"` // 使用时间
|
||||
IpThread int `json:"ip_thread"` // 线程数
|
||||
IpCardCode string `json:"ip_card_code"` // 卡密
|
||||
}
|
||||
|
||||
/*
|
||||
* 查询机器码
|
||||
* param tailCardSecret[string] 卡密
|
||||
* return 响应结构体,错误信息
|
||||
*/
|
||||
func getMachineCode(tailCardSecret string) (*getMachineCodeResp, error) {
|
||||
url := "http://114.66.2.223:7842/api/proxies/ip_show"
|
||||
// 构建请求数据
|
||||
// 查询机器码
|
||||
func getMachineCode(tailCardSecret string) (*MachineCodeData, error) {
|
||||
url := "http://36.134.51.135:7842/api/proxies/ip_show"
|
||||
data := map[string]interface{}{
|
||||
"ip_card_code": tailCardSecret,
|
||||
"agent_id": 9999,
|
||||
}
|
||||
// 发送POST请求
|
||||
|
||||
_, body, errs := gorequest.New().Post(url).Send(data).End()
|
||||
if len(errs) > 0 {
|
||||
return nil, fmt.Errorf("查询机器码失败: %v", errs)
|
||||
}
|
||||
// 解析响应
|
||||
var resp getMachineCodeResp
|
||||
|
||||
var resp MachineCodeResponse
|
||||
if err := json.Unmarshal([]byte(body), &resp); err != nil {
|
||||
return nil, fmt.Errorf("解析响应失败: %v", err)
|
||||
}
|
||||
// 处理不同的响应码
|
||||
if resp.Code == 201 {
|
||||
// 需要充值卡密
|
||||
|
||||
switch resp.Code {
|
||||
case 201:
|
||||
machineCode, err := rechargeCard(tailCardSecret, resp.Data.MachineCode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp.Data.MachineCode = machineCode
|
||||
return &resp, nil
|
||||
} else if resp.Code == 200 {
|
||||
// 成功获取机器码
|
||||
return &resp, nil
|
||||
} else {
|
||||
// 其他错误
|
||||
return &resp.Data, nil
|
||||
case 200:
|
||||
return &resp.Data, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("查询机器码失败: %s", resp.Message)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* 充值卡密
|
||||
* param tailCardSecret[string] 卡密
|
||||
* param machineCode[string] 机器码
|
||||
* return 机器码,错误信息
|
||||
*/
|
||||
// 充值卡密
|
||||
func rechargeCard(tailCardSecret, machineCode string) (string, error) {
|
||||
url := "http://114.66.2.223:7842/api/proxies/ip_recharge"
|
||||
// 构建请求数据
|
||||
url := "http://36.134.51.135:7842/api/proxies/ip_recharge"
|
||||
data := map[string]interface{}{
|
||||
"machine_code": machineCode,
|
||||
"ip_card_code": tailCardSecret,
|
||||
"agent_id": 9999,
|
||||
}
|
||||
// 发送POST请求
|
||||
|
||||
_, body, errs := gorequest.New().Post(url).Send(data).End()
|
||||
if len(errs) > 0 {
|
||||
return "", fmt.Errorf("充值卡密失败: %v", errs)
|
||||
}
|
||||
// 解析响应
|
||||
|
||||
var resp struct {
|
||||
Code int `json:"code"` // 状态码
|
||||
Message string `json:"message"` // 返回消息
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
Data struct {
|
||||
IpExpTime string `json:"ip_exp_time"` // 过期时间
|
||||
IpThread int `json:"ip_thread"` // 线程
|
||||
MachineCode string `json:"machine_code"` // 机器码
|
||||
IpExpTime string `json:"ip_exp_time"`
|
||||
IpThread int `json:"ip_thread"`
|
||||
MachineCode string `json:"machine_code"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
if err := json.Unmarshal([]byte(body), &resp); err != nil {
|
||||
return "", fmt.Errorf("解析响应失败: %v", err)
|
||||
}
|
||||
|
||||
// 检查响应状态
|
||||
if resp.Code != 200 {
|
||||
return "", fmt.Errorf("充值卡密失败: %s", resp.Message)
|
||||
}
|
||||
|
||||
return resp.Data.MachineCode, nil
|
||||
}
|
||||
|
||||
/*
|
||||
* 获取代理服务器列表
|
||||
* param machineCode[string] 机器码
|
||||
* return 代理服务器组,错误信息
|
||||
*/
|
||||
// 获取代理服务器列表
|
||||
func getProxies(machineCode string) ([]string, error) {
|
||||
log.Printf("[INFO] 开始获取代理列表: %s", machineCode)
|
||||
// 生成签名
|
||||
sign := fmt.Sprintf("%x", md5.Sum([]byte(fmt.Sprintf("9999%s9999", machineCode))))
|
||||
|
||||
// 构建请求URL
|
||||
GetProxiesUrl := fmt.Sprintf("http://114.66.2.223:7842/api/proxies/get_proxy?machine_code=%s&sign=%s&agent_id=9999",
|
||||
getProxiesUrl := fmt.Sprintf("http://36.134.51.135:7842/api/proxies/get_proxy?machine_code=%s&sign=%s&agent_id=9999",
|
||||
machineCode, sign)
|
||||
|
||||
// 发送GET请求
|
||||
req := gorequest.New().Get(GetProxiesUrl).Timeout(20 * time.Second)
|
||||
req := gorequest.New().Get(getProxiesUrl).Timeout(20 * time.Second)
|
||||
_, body, errs := req.End()
|
||||
if len(errs) > 0 {
|
||||
return nil, fmt.Errorf("获取代理失败: %v", errs)
|
||||
}
|
||||
|
||||
// 检查是否为JSON错误响应(响应以{开头,以}结尾)
|
||||
if strings.HasPrefix(strings.TrimSpace(body), "{") && strings.HasSuffix(strings.TrimSpace(body), "}") {
|
||||
// 尝试解析为JSON错误响应
|
||||
// 检查是否为JSON错误响应
|
||||
trimmedBody := strings.TrimSpace(body)
|
||||
if strings.HasPrefix(trimmedBody, "{") && strings.HasSuffix(trimmedBody, "}") {
|
||||
var errorResp struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
@ -549,190 +179,139 @@ func getProxies(machineCode string) ([]string, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// 解析响应(每行一个代理地址)
|
||||
lines := strings.Split(strings.TrimSpace(body), "\n")
|
||||
// 解析响应
|
||||
lines := strings.Split(trimmedBody, "\n")
|
||||
var proxies []string
|
||||
for _, line := range lines {
|
||||
line = strings.TrimSpace(line)
|
||||
// 过滤空行和JSON格式的行
|
||||
if line != "" && !strings.HasPrefix(line, "{") {
|
||||
proxies = append(proxies, line)
|
||||
}
|
||||
}
|
||||
// 检查是否获取到有效代理
|
||||
|
||||
if len(proxies) == 0 {
|
||||
return nil, fmt.Errorf("未获取到有效代理")
|
||||
}
|
||||
|
||||
log.Printf("[INFO] 获取到 %d 个代理", len(proxies))
|
||||
return proxies, nil
|
||||
}
|
||||
|
||||
// 初始化代理信息
|
||||
func initProxy() {
|
||||
|
||||
}
|
||||
|
||||
// =================== C 导出函数 =======================
|
||||
|
||||
// GetProxyHealth 导出函数:获取代理健康状态(用于调试)
|
||||
//
|
||||
//export GetProxyHealth
|
||||
func GetProxyHealth() *C.char {
|
||||
proxyHealthMutex.RLock()
|
||||
defer proxyHealthMutex.RUnlock()
|
||||
// 构建健康信息映射
|
||||
healthInfo := make(map[string]interface{})
|
||||
for proxy, health := range proxyHealthMaps {
|
||||
healthInfo[proxy] = map[string]interface{}{
|
||||
"success_count": health.SuccessCount,
|
||||
"fail_count": health.FailCount,
|
||||
"last_check": health.LastCheck.Format(time.RFC3339),
|
||||
"response_time": health.ResponseTime.String(),
|
||||
"is_healthy": health.IsHealthy,
|
||||
}
|
||||
}
|
||||
// 序列化为JSON
|
||||
jsonData, err := json.Marshal(healthInfo)
|
||||
// 检查卡密是否过期
|
||||
func checkTailCardSecretExpired(tailCardSecret string) (bool, error) {
|
||||
resp, err := getMachineCode(tailCardSecret)
|
||||
if err != nil {
|
||||
return C.CString(fmt.Sprintf(`{"error": "序列化健康信息失败: %v"}`, err))
|
||||
return false, fmt.Errorf("请求错误: %v", err)
|
||||
}
|
||||
|
||||
return C.CString(string(jsonData))
|
||||
}
|
||||
|
||||
// ProxyTypeManager 导出函数:代理类型管理器
|
||||
//
|
||||
//export ProxyTypeManager
|
||||
func ProxyTypeManager(proxyType, username, password, machineCode *C.char) *C.char {
|
||||
// C字符串转换为Go字符串
|
||||
goProxyType := C.GoString(proxyType)
|
||||
goUsername := C.GoString(username)
|
||||
goPassword := C.GoString(password)
|
||||
goMachineCode := C.GoString(machineCode)
|
||||
|
||||
log.Printf("[DEBUG] 代理类型管理器调用: type=%s", goProxyType)
|
||||
// 调用代理类型管理器
|
||||
proxyURL, err := proxyTypeManager(goProxyType, goUsername, goPassword, goMachineCode)
|
||||
targetTime, err := time.Parse("2006-01-02 15:04:05", resp.IpExpTime)
|
||||
if err != nil {
|
||||
errorMsg := fmt.Sprintf("ERROR: %v", err)
|
||||
log.Printf("[ERROR] 代理类型管理器错误: %v", err)
|
||||
return C.CString(errorMsg)
|
||||
return false, fmt.Errorf("时间格式错误: %v", err)
|
||||
}
|
||||
|
||||
log.Printf("[DEBUG] 代理类型管理器返回: %s", proxyURL)
|
||||
return C.CString(proxyURL)
|
||||
if targetTime.After(time.Now()) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return false, fmt.Errorf("卡密已经过期!过期时间: %v", targetTime)
|
||||
}
|
||||
|
||||
// ProxyTypeManagerNew 导出函数:代理类型管理器
|
||||
//
|
||||
//export ProxyTypeManagerNew
|
||||
func ProxyTypeManagerNew(proxyType, username, password, machineCode *C.char) *C.char {
|
||||
// C字符串转换为Go字符串
|
||||
goProxyType := C.GoString(proxyType)
|
||||
goUsername := C.GoString(username)
|
||||
goPassword := C.GoString(password)
|
||||
goMachineCode := C.GoString(machineCode)
|
||||
// =================== C导出函数 =======================
|
||||
|
||||
// 调用代理类型管理器
|
||||
proxyURL, err := proxyTypeManagerNew(goProxyType, goUsername, goPassword, goMachineCode)
|
||||
// ResponseAPI 统一响应数据结构
|
||||
type ResponseAPI struct {
|
||||
Success bool `json:"success"`
|
||||
Message string `json:"message"`
|
||||
Data interface{} `json:"data"`
|
||||
}
|
||||
|
||||
// 生成JSON响应字符串
|
||||
func jsonResponse(data interface{}, err error) string {
|
||||
resp := ResponseAPI{
|
||||
Success: err == nil,
|
||||
Message: getErrorMsg(err),
|
||||
Data: data,
|
||||
}
|
||||
bytes, _ := json.Marshal(resp)
|
||||
return string(bytes)
|
||||
}
|
||||
|
||||
// 获取错误信息
|
||||
func getErrorMsg(err error) string {
|
||||
if err != nil {
|
||||
errorMsg := fmt.Sprintf("ERROR: %v", err)
|
||||
return C.CString(errorMsg)
|
||||
return err.Error()
|
||||
}
|
||||
return C.CString(proxyURL)
|
||||
return ""
|
||||
}
|
||||
|
||||
// GetMachineCode 导出函数:查询机器码
|
||||
// GetProxyByType 根据代理类型获取代理URL
|
||||
//
|
||||
//export GetProxyByType
|
||||
func GetProxyByType(proxyType, machineCode *C.char) *C.char {
|
||||
proxyTypeStr := C.GoString(proxyType)
|
||||
machineCodeStr := C.GoString(machineCode)
|
||||
|
||||
proxyURL, err := getProxyByType(proxyTypeStr, machineCodeStr)
|
||||
return C.CString(jsonResponse(proxyURL, err))
|
||||
}
|
||||
|
||||
// GetTailProxyURL 获取尾巴代理URL
|
||||
//
|
||||
//export GetTailProxyURL
|
||||
func GetTailProxyURL(machineCode *C.char) *C.char {
|
||||
machineCodeStr := C.GoString(machineCode)
|
||||
proxyURL, err := getTailProxyURL(machineCodeStr)
|
||||
return C.CString(jsonResponse(proxyURL, err))
|
||||
}
|
||||
|
||||
// GetMachineCode 查询机器码
|
||||
//
|
||||
//export GetMachineCode
|
||||
func GetMachineCode(tailCardSecret *C.char) *C.char {
|
||||
goTailCardSecret := C.GoString(tailCardSecret)
|
||||
|
||||
resp, err := getMachineCode(goTailCardSecret)
|
||||
if err != nil {
|
||||
errorMsg := fmt.Sprintf("ERROR: %v", err)
|
||||
log.Printf("[ERROR] 查询机器码错误: %v", err)
|
||||
return C.CString(errorMsg)
|
||||
}
|
||||
|
||||
// 将响应转换为JSON
|
||||
jsonData, err := json.Marshal(resp)
|
||||
if err != nil {
|
||||
errorMsg := fmt.Sprintf("ERROR: 序列化响应失败: %v", err)
|
||||
log.Printf("[ERROR] 序列化机器码响应失败: %v", err)
|
||||
return C.CString(errorMsg)
|
||||
}
|
||||
|
||||
return C.CString(string(jsonData))
|
||||
tailCardSecretStr := C.GoString(tailCardSecret)
|
||||
resp, err := getMachineCode(tailCardSecretStr)
|
||||
return C.CString(jsonResponse(resp, err))
|
||||
}
|
||||
|
||||
// RechargeCard 导出函数:充值卡密
|
||||
// RechargeCard 充值卡密
|
||||
//
|
||||
//export RechargeCard
|
||||
func RechargeCard(tailCardSecret, machineCode *C.char) *C.char {
|
||||
goTailCardSecret := C.GoString(tailCardSecret)
|
||||
goMachineCode := C.GoString(machineCode)
|
||||
log.Printf("[DEBUG] 充值卡密调用: card=%s, machine_code=%s", goTailCardSecret, goMachineCode)
|
||||
|
||||
newMachineCode, err := rechargeCard(goTailCardSecret, goMachineCode)
|
||||
if err != nil {
|
||||
errorMsg := fmt.Sprintf("ERROR: %v", err)
|
||||
log.Printf("[ERROR] 充值卡密错误: %v", err)
|
||||
return C.CString(errorMsg)
|
||||
return C.CString(jsonResponse(nil, err))
|
||||
}
|
||||
|
||||
// 构建成功响应
|
||||
response := map[string]interface{}{
|
||||
"success": true,
|
||||
"machine_code": newMachineCode,
|
||||
"message": "充值成功",
|
||||
"machineCode": newMachineCode,
|
||||
"rechargeInfo": "充值成功",
|
||||
}
|
||||
|
||||
jsonData, err := json.Marshal(response)
|
||||
if err != nil {
|
||||
errorMsg := fmt.Sprintf("ERROR: 序列化响应失败: %v", err)
|
||||
log.Printf("[ERROR] 序列化充值响应失败: %v", err)
|
||||
return C.CString(errorMsg)
|
||||
}
|
||||
|
||||
log.Printf("[DEBUG] 充值卡密成功: new_machine_code=%s", newMachineCode)
|
||||
return C.CString(string(jsonData))
|
||||
return C.CString(jsonResponse(response, nil))
|
||||
}
|
||||
|
||||
// GetProxies 导出函数:获取代理服务器列表
|
||||
// GetProxies 获取代理服务器列表
|
||||
//
|
||||
//export GetProxies
|
||||
func GetProxies(machineCode *C.char) *C.char {
|
||||
goMachineCode := C.GoString(machineCode)
|
||||
log.Printf("[DEBUG] 获取代理服务器列表调用: machine_code=%s", goMachineCode)
|
||||
|
||||
proxies, err := getProxies(goMachineCode)
|
||||
if err != nil {
|
||||
errorMsg := fmt.Sprintf("ERROR: %v", err)
|
||||
log.Printf("[ERROR] 获取代理服务器列表错误: %v", err)
|
||||
return C.CString(errorMsg)
|
||||
return C.CString(jsonResponse(nil, err))
|
||||
}
|
||||
|
||||
// 将代理列表转换为JSON
|
||||
response := map[string]interface{}{
|
||||
"success": true,
|
||||
"count": len(proxies),
|
||||
"proxies": proxies,
|
||||
}
|
||||
|
||||
jsonData, err := json.Marshal(response)
|
||||
if err != nil {
|
||||
errorMsg := fmt.Sprintf("ERROR: 序列化响应失败: %v", err)
|
||||
log.Printf("[ERROR] 序列化代理列表失败: %v", err)
|
||||
return C.CString(errorMsg)
|
||||
}
|
||||
|
||||
log.Printf("[DEBUG] 获取代理服务器列表成功: count=%d", len(proxies))
|
||||
return C.CString(string(jsonData))
|
||||
return C.CString(jsonResponse(response, nil))
|
||||
}
|
||||
|
||||
// CheckTailCardSecretExpired 导出函数:检查卡密是否过期
|
||||
// CheckTailCardSecretExpired 检查卡密是否过期
|
||||
//
|
||||
//export CheckTailCardSecretExpired
|
||||
func CheckTailCardSecretExpired(tailCardSecret *C.char) *C.char {
|
||||
@ -740,104 +319,30 @@ func CheckTailCardSecretExpired(tailCardSecret *C.char) *C.char {
|
||||
|
||||
isValid, err := checkTailCardSecretExpired(goTailCardSecret)
|
||||
if err != nil {
|
||||
// 如果是过期错误,返回特定格式
|
||||
if strings.Contains(err.Error(), "卡密已经过期") {
|
||||
response := map[string]interface{}{
|
||||
"is_valid": false,
|
||||
"message": err.Error(),
|
||||
}
|
||||
jsonData, _ := json.Marshal(response)
|
||||
return C.CString(string(jsonData))
|
||||
}
|
||||
|
||||
errorMsg := fmt.Sprintf("ERROR: %v", err)
|
||||
log.Printf("[ERROR] 检查卡密过期错误: %v", err)
|
||||
return C.CString(errorMsg)
|
||||
return C.CString(jsonResponse(nil, err))
|
||||
}
|
||||
|
||||
response := map[string]interface{}{
|
||||
"is_valid": isValid,
|
||||
"message": "卡密有效",
|
||||
"isValid": isValid,
|
||||
"checkInfo": "卡密有效",
|
||||
}
|
||||
|
||||
jsonData, err := json.Marshal(response)
|
||||
if err != nil {
|
||||
errorMsg := fmt.Sprintf("ERROR: 序列化响应失败: %v", err)
|
||||
return C.CString(errorMsg)
|
||||
}
|
||||
return C.CString(string(jsonData))
|
||||
return C.CString(jsonResponse(response, nil))
|
||||
}
|
||||
|
||||
// InitProxyManager 导出函数:初始化代理管理器
|
||||
//
|
||||
//export InitProxyManager
|
||||
func InitProxyManager(serversJson, username, password, tailCardSecret, proxyType *C.char) *C.char {
|
||||
goServersJson := C.GoString(serversJson)
|
||||
goUsername := C.GoString(username)
|
||||
goPassword := C.GoString(password)
|
||||
goTailCardSecret := C.GoString(tailCardSecret)
|
||||
goProxyType := C.GoString(proxyType)
|
||||
|
||||
log.Printf("[DEBUG] 初始化代理管理器调用: type=%s", goProxyType)
|
||||
|
||||
// 解析服务器列表
|
||||
var serverList []string
|
||||
if goServersJson != "" {
|
||||
if err := json.Unmarshal([]byte(goServersJson), &serverList); err != nil {
|
||||
errorMsg := fmt.Sprintf("ERROR: 解析服务器列表失败: %v", err)
|
||||
log.Printf("[ERROR] 解析服务器列表失败: %v", err)
|
||||
return C.CString(errorMsg)
|
||||
}
|
||||
}
|
||||
|
||||
// 更新全局服务器列表
|
||||
if len(serverList) > 0 {
|
||||
randMutex.Lock()
|
||||
servers = serverList
|
||||
randMutex.Unlock()
|
||||
log.Printf("[INFO] 更新服务器列表,共 %d 个服务器", len(servers))
|
||||
}
|
||||
|
||||
// 创建代理管理器
|
||||
manager := ProxyManager{
|
||||
servers: servers,
|
||||
username: goUsername,
|
||||
password: goPassword,
|
||||
tailCardSecret: goTailCardSecret,
|
||||
proxyType: goProxyType,
|
||||
}
|
||||
|
||||
// 序列化管理器信息
|
||||
jsonData, err := json.Marshal(manager)
|
||||
if err != nil {
|
||||
errorMsg := fmt.Sprintf("ERROR: 序列化代理管理器失败: %v", err)
|
||||
log.Printf("[ERROR] 序列化代理管理器失败: %v", err)
|
||||
return C.CString(errorMsg)
|
||||
}
|
||||
|
||||
log.Printf("[DEBUG] 代理管理器初始化成功")
|
||||
return C.CString(string(jsonData))
|
||||
}
|
||||
|
||||
// FreeCString 导出函数:释放C字符串内存
|
||||
// FreeCString 释放C字符串内存
|
||||
//
|
||||
//export FreeCString
|
||||
func FreeCString(str *C.char) {
|
||||
C.free(unsafe.Pointer(str))
|
||||
}
|
||||
|
||||
// PROXY_VERSION 版本号
|
||||
const (
|
||||
PROXY_VERSION = "v2"
|
||||
)
|
||||
|
||||
// GetVersion 获取版本
|
||||
//
|
||||
//export GetVersion
|
||||
func GetVersion() *C.char {
|
||||
return C.CString(PROXY_VERSION)
|
||||
return C.CString("v3")
|
||||
}
|
||||
|
||||
// 空main函数,编译DLL时需要
|
||||
// 空main函数
|
||||
func main() {
|
||||
}
|
||||
|
||||
133
proxy/proxy.h
133
proxy/proxy.h
@ -1,133 +0,0 @@
|
||||
/* Code generated by cmd/cgo; DO NOT EDIT. */
|
||||
|
||||
/* package command-line-arguments */
|
||||
|
||||
|
||||
#line 1 "cgo-builtin-export-prolog"
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#ifndef GO_CGO_EXPORT_PROLOGUE_H
|
||||
#define GO_CGO_EXPORT_PROLOGUE_H
|
||||
|
||||
#ifndef GO_CGO_GOSTRING_TYPEDEF
|
||||
typedef struct { const char *p; ptrdiff_t n; } _GoString_;
|
||||
extern size_t _GoStringLen(_GoString_ s);
|
||||
extern const char *_GoStringPtr(_GoString_ s);
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
/* Start of preamble from import "C" comments. */
|
||||
|
||||
|
||||
#line 3 "proxy.go"
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#line 1 "cgo-generated-wrapper"
|
||||
|
||||
|
||||
/* End of preamble from import "C" comments. */
|
||||
|
||||
|
||||
/* Start of boilerplate cgo prologue. */
|
||||
#line 1 "cgo-gcc-export-header-prolog"
|
||||
|
||||
#ifndef GO_CGO_PROLOGUE_H
|
||||
#define GO_CGO_PROLOGUE_H
|
||||
|
||||
typedef signed char GoInt8;
|
||||
typedef unsigned char GoUint8;
|
||||
typedef short GoInt16;
|
||||
typedef unsigned short GoUint16;
|
||||
typedef int GoInt32;
|
||||
typedef unsigned int GoUint32;
|
||||
typedef long long GoInt64;
|
||||
typedef unsigned long long GoUint64;
|
||||
typedef GoInt64 GoInt;
|
||||
typedef GoUint64 GoUint;
|
||||
typedef size_t GoUintptr;
|
||||
typedef float GoFloat32;
|
||||
typedef double GoFloat64;
|
||||
#ifdef _MSC_VER
|
||||
#if !defined(__cplusplus) || _MSVC_LANG <= 201402L
|
||||
#include <complex.h>
|
||||
typedef _Fcomplex GoComplex64;
|
||||
typedef _Dcomplex GoComplex128;
|
||||
#else
|
||||
#include <complex>
|
||||
typedef std::complex<float> GoComplex64;
|
||||
typedef std::complex<double> GoComplex128;
|
||||
#endif
|
||||
#else
|
||||
typedef float _Complex GoComplex64;
|
||||
typedef double _Complex GoComplex128;
|
||||
#endif
|
||||
|
||||
/*
|
||||
static assertion to make sure the file is being used on architecture
|
||||
at least with matching size of GoInt.
|
||||
*/
|
||||
typedef char _check_for_64_bit_pointer_matching_GoInt[sizeof(void*)==64/8 ? 1:-1];
|
||||
|
||||
#ifndef GO_CGO_GOSTRING_TYPEDEF
|
||||
typedef _GoString_ GoString;
|
||||
#endif
|
||||
typedef void *GoMap;
|
||||
typedef void *GoChan;
|
||||
typedef struct { void *t; void *v; } GoInterface;
|
||||
typedef struct { void *data; GoInt len; GoInt cap; } GoSlice;
|
||||
|
||||
#endif
|
||||
|
||||
/* End of boilerplate cgo prologue. */
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
|
||||
// GetProxyHealth 导出函数:获取代理健康状态(用于调试)
|
||||
//
|
||||
extern __declspec(dllexport) char* GetProxyHealth(void);
|
||||
|
||||
// ProxyTypeManager 导出函数:代理类型管理器
|
||||
//
|
||||
extern __declspec(dllexport) char* ProxyTypeManager(char* proxyType, char* username, char* password, char* machineCode);
|
||||
|
||||
// ProxyTypeManagerNew 导出函数:代理类型管理器
|
||||
//
|
||||
extern __declspec(dllexport) char* ProxyTypeManagerNew(char* proxyType, char* username, char* password, char* machineCode);
|
||||
|
||||
// GetMachineCode 导出函数:查询机器码
|
||||
//
|
||||
extern __declspec(dllexport) char* GetMachineCode(char* tailCardSecret);
|
||||
|
||||
// RechargeCard 导出函数:充值卡密
|
||||
//
|
||||
extern __declspec(dllexport) char* RechargeCard(char* tailCardSecret, char* machineCode);
|
||||
|
||||
// GetProxies 导出函数:获取代理服务器列表
|
||||
//
|
||||
extern __declspec(dllexport) char* GetProxies(char* machineCode);
|
||||
|
||||
// CheckTailCardSecretExpired 导出函数:检查卡密是否过期
|
||||
//
|
||||
extern __declspec(dllexport) char* CheckTailCardSecretExpired(char* tailCardSecret);
|
||||
|
||||
// InitProxyManager 导出函数:初始化代理管理器
|
||||
//
|
||||
extern __declspec(dllexport) char* InitProxyManager(char* serversJson, char* username, char* password, char* tailCardSecret, char* proxyType);
|
||||
|
||||
// FreeCString 导出函数:释放C字符串内存
|
||||
//
|
||||
extern __declspec(dllexport) void FreeCString(char* str);
|
||||
|
||||
// GetVersion 获取版本
|
||||
//
|
||||
extern __declspec(dllexport) char* GetVersion(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
848
proxy/proxyLao.go
Normal file
848
proxy/proxyLao.go
Normal file
@ -0,0 +1,848 @@
|
||||
package main
|
||||
|
||||
///*
|
||||
//#include <stdlib.h>
|
||||
//*/
|
||||
//import "C"
|
||||
//import (
|
||||
// "crypto/md5"
|
||||
// "encoding/json"
|
||||
// "fmt"
|
||||
// "github.com/parnurzeal/gorequest"
|
||||
// "log"
|
||||
// "math/rand"
|
||||
// "net/url"
|
||||
// "strings"
|
||||
// "sync"
|
||||
// "time"
|
||||
// "unsafe"
|
||||
//)
|
||||
//
|
||||
//// 代理类型常量
|
||||
//const (
|
||||
// CalfElephantProxyType = "CALF_ELEPHANT_PROXY" // 小象代理类型
|
||||
// TailProxyType = "TAIL_PROXY" // 尾巴代理类型
|
||||
//)
|
||||
//
|
||||
//// 小象代理服务器列表
|
||||
//var (
|
||||
// servers = []string{
|
||||
// "http-dynamic.xiaoxiangdaili.com",
|
||||
// "http-dynamic-S02.xiaoxiangdaili.com",
|
||||
// "http-dynamic-S03.xiaoxiangdaili.com",
|
||||
// "http-dynamic-S04.xiaoxiangdaili.com",
|
||||
// }
|
||||
// // 互斥锁,用于保护全局随机数生成器的并发访问
|
||||
// randMutex sync.Mutex
|
||||
// globalRand *rand.Rand // 全局随机数生成器
|
||||
// // 代理健康状态管理
|
||||
// // 代理健康状态映射表,key为代理主机名,value为健康状态
|
||||
// proxyHealthMaps = make(map[string]*ProxyHealth)
|
||||
// proxyHealthMutex sync.RWMutex
|
||||
//)
|
||||
//
|
||||
//// ProxyManager 代理管理器结构体,用于管理代理配置信息
|
||||
//type ProxyManager struct {
|
||||
// servers []string `json:"servers"` // 代理服务器列表
|
||||
// username string `json:"username"` // 代理账号
|
||||
// password string `json:"password"` // 代理密码
|
||||
// tailCardSecret string `json:"tail_card_secret"` // 尾巴代理卡密
|
||||
// proxyType string `json:"proxy_type"` // 代理类型 CALF_ELEPHANT_PROXY/TAIL_PROXY
|
||||
//}
|
||||
//
|
||||
//// ProxyHealth 代理健康状态结构体,用于跟踪代理的健康状况
|
||||
//type ProxyHealth struct {
|
||||
// SuccessCount int // 成功次数
|
||||
// FailCount int // 失败次数
|
||||
// LastCheck time.Time // 最后检查时间
|
||||
// ResponseTime time.Duration // 响应时间
|
||||
// IsHealthy bool // 是否健康
|
||||
//}
|
||||
//
|
||||
//// 初始化函数,在程序启动时自动执行
|
||||
//func init() {
|
||||
// // 创建全局的随机数生成器,使用当前时间作为种子
|
||||
// globalRand = rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
//}
|
||||
//
|
||||
///*
|
||||
//* 获取代理URL,代理类型管理器,根据代理类型构建不同的代理URL
|
||||
//* param proxyType[string] 代理类型
|
||||
//* param username[string] 代理用户名
|
||||
//* param password[string] 代理密码
|
||||
//* param machineCode[string] 机器码
|
||||
//* return 代理服务器IP,错误信息
|
||||
// */
|
||||
//func proxyTypeManager(proxyType, username, password, machineCode string) (string, error) {
|
||||
// switch proxyType {
|
||||
// case CalfElephantProxyType:
|
||||
// // 小象代理:使用用户名和密码构建代理URL
|
||||
// return buildCalfElephantProxyURL(username, password)
|
||||
// case TailProxyType:
|
||||
// // 尾巴代理:使用机器码构建代理URL
|
||||
// return buildTailProxyURL(machineCode)
|
||||
// default:
|
||||
// // 不支持的代理类型,返回错误
|
||||
// return "", fmt.Errorf("不支持的代理类型: %s", proxyType)
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//func proxyTypeManagerNew(proxyType, username, password, machineCode string) (string, error) {
|
||||
// // 获取代理列表
|
||||
// proxies, err := getProxies(machineCode)
|
||||
// if err != nil {
|
||||
// return "", err
|
||||
// }
|
||||
//
|
||||
// // 检查是否获取到有效代理
|
||||
// if len(proxies) == 0 {
|
||||
// return "", fmt.Errorf("未获取到有效代理")
|
||||
// }
|
||||
// // 获取代理
|
||||
// proxy := randomElement(proxies)
|
||||
// proxy = fmt.Sprintf("http://%s", proxy)
|
||||
// return proxy, nil
|
||||
//}
|
||||
//
|
||||
///*
|
||||
//* 构建小象代理URL
|
||||
//* param username[string] 代理用户名
|
||||
//* param password[string] 代理密码
|
||||
//* return 代理服务器IP,错误信息
|
||||
// */
|
||||
//func buildCalfElephantProxyURL(username, password string) (string, error) {
|
||||
// // 随机选择一个代理服务器
|
||||
// server := randomServer()
|
||||
// // 构建代理URL格式:http://用户名:密码@服务器:端口
|
||||
// proxyURL := fmt.Sprintf("http://%s:%s@%s:%d",
|
||||
// url.QueryEscape(username), // URL编码用户名,防止特殊字符问题
|
||||
// url.QueryEscape(password), // URL编码密码,防止特殊字符问题
|
||||
// server, // 服务器地址
|
||||
// 10030) // 固定端口号
|
||||
//
|
||||
// // 检测代理可用性
|
||||
// if err := checkProxyHealth(proxyURL); err != nil {
|
||||
// // 代理检测失败,记录警告日志
|
||||
// log.Printf("[WARN] 代理 %s 检测失败: %v", server, err)
|
||||
// // 尝试下一个代理服务器
|
||||
// return tryNextCalfElephantProxy(username, password, server)
|
||||
// }
|
||||
// // 代理检测成功,记录信息日志
|
||||
// log.Printf("[INFO] 使用小象代理: %s", server)
|
||||
// return proxyURL, nil
|
||||
//}
|
||||
//
|
||||
///*
|
||||
//* 尝试下一个小象代理服务器
|
||||
//* param username[string] 代理用户名
|
||||
//* param password[string] 代理密码
|
||||
//* param failedServer[string] 代理服务器
|
||||
//* return 代理服务器IP,错误信息
|
||||
// */
|
||||
//func tryNextCalfElephantProxy(username, password, failedServer string) (string, error) {
|
||||
// // 创建服务器副本并排除失败的服务器
|
||||
// availableServers := make([]string, 0)
|
||||
// for _, server := range servers {
|
||||
// if server != failedServer {
|
||||
// availableServers = append(availableServers, server)
|
||||
// }
|
||||
// }
|
||||
// // 如果没有可用的服务器,返回错误
|
||||
// if len(availableServers) == 0 {
|
||||
// return "", fmt.Errorf("所有小象代理服务器都不可用")
|
||||
// }
|
||||
//
|
||||
// // 随机尝试可用服务器
|
||||
// for _, server := range shuffleServers(availableServers) {
|
||||
// // 构建代理URL
|
||||
// proxyURL := fmt.Sprintf("http://%s:%s@%s:%d",
|
||||
// url.QueryEscape(username),
|
||||
// url.QueryEscape(password),
|
||||
// server,
|
||||
// 10030)
|
||||
// // 检测代理可用性
|
||||
// if err := checkProxyHealth(proxyURL); err == nil {
|
||||
// log.Printf("[INFO] 切换到可用代理: %s", server)
|
||||
// return proxyURL, nil
|
||||
// }
|
||||
// // 代理不可用,记录警告日志
|
||||
// log.Printf("[WARN] 代理 %s 检测失败", server)
|
||||
// }
|
||||
// // 所有服务器都检测失败,返回错误
|
||||
// return "", fmt.Errorf("所有可用的小象代理服务器都检测失败")
|
||||
//}
|
||||
//
|
||||
///*
|
||||
//* 构建内置代理URL
|
||||
//* param machineCode[string] 机器码
|
||||
//* return 代理服务器IP,错误信息
|
||||
// */
|
||||
//func buildTailProxyURL(machineCode string) (string, error) {
|
||||
// // 获取代理列表
|
||||
// proxies, err := getProxies(machineCode)
|
||||
// if err != nil {
|
||||
// return "", err
|
||||
// }
|
||||
// // 检查是否获取到有效代理
|
||||
// if len(proxies) == 0 {
|
||||
// return "", fmt.Errorf("未获取到有效代理")
|
||||
// }
|
||||
//
|
||||
// // 过滤并选择健康的代理
|
||||
// healthyProxies := filterHealthyProxies(proxies)
|
||||
// if len(healthyProxies) > 0 {
|
||||
// // 从健康代理中随机选择一个
|
||||
// proxyURL := "http://" + randomElement(healthyProxies)
|
||||
// log.Printf("[INFO] 使用健康尾巴代理: %s", proxyURL)
|
||||
// return proxyURL, nil
|
||||
// }
|
||||
//
|
||||
// // 如果没有健康代理,检测所有代理
|
||||
// log.Printf("[INFO] 未找到健康代理,开始检测代理可用性...")
|
||||
// return findWorkingTailProxy(proxies)
|
||||
//}
|
||||
//
|
||||
///*
|
||||
//* 过滤健康代理,返回当前健康的代理列表
|
||||
//* param proxies[[]string] 内置代理服务器数组
|
||||
//* return 内置代理服务器数组,错误信息
|
||||
// */
|
||||
//func filterHealthyProxies(proxies []string) []string {
|
||||
// // 获取读锁,允许多个goroutine同时读取
|
||||
// proxyHealthMutex.RLock()
|
||||
// defer proxyHealthMutex.RUnlock() // 确保函数结束时释放锁
|
||||
//
|
||||
// var healthy []string
|
||||
// for _, proxy := range proxies {
|
||||
// if health, exists := proxyHealthMaps[proxy]; exists && health.IsHealthy {
|
||||
// // 检查是否在最近检查过(5分钟内),避免使用过时的健康状态
|
||||
// if time.Since(health.LastCheck) < 5*time.Minute {
|
||||
// healthy = append(healthy, proxy)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// return healthy
|
||||
//}
|
||||
//
|
||||
///*
|
||||
//* 查找可用的尾巴代理
|
||||
//* param proxies[[]string] 内置代理服务器数组
|
||||
//* return 可用的代理服务器IP,错误信息
|
||||
// */
|
||||
//func findWorkingTailProxy(proxies []string) (string, error) {
|
||||
// // 打乱代理顺序
|
||||
// shuffledProxies := shuffleServers(proxies)
|
||||
//
|
||||
// // 并发检测代理
|
||||
// type proxyResult struct {
|
||||
// proxy string
|
||||
// err error
|
||||
// }
|
||||
// // 创建带缓冲的通道,用于收集检测结果
|
||||
// ch := make(chan proxyResult, len(shuffledProxies))
|
||||
// var wg sync.WaitGroup // 等待组,用于等待所有检测goroutine完成
|
||||
//
|
||||
// // 限制并发数,避免同时检测太多代理导致网络拥塞
|
||||
// sem := make(chan struct{}, 5)
|
||||
// // 并发检测每个代理
|
||||
// for _, proxy := range shuffledProxies {
|
||||
// wg.Add(1) // 增加等待组计数
|
||||
// go func(p string) {
|
||||
// defer wg.Done() // 函数结束时减少等待组计数
|
||||
// sem <- struct{}{} // 获取信号量,限制并发数
|
||||
// defer func() { <-sem }() // 释放信号量
|
||||
//
|
||||
// proxyURL := "http://" + p
|
||||
// err := checkProxyHealth(proxyURL)
|
||||
// // 将检测结果发送到通道
|
||||
// ch <- proxyResult{proxy: p, err: err}
|
||||
// }(proxy)
|
||||
// }
|
||||
// // 等待所有检测goroutine完成
|
||||
// wg.Wait()
|
||||
// close(ch) // 关闭通道,表示不会再有数据发送
|
||||
//
|
||||
// // 收集结果,查找可用的代理
|
||||
// var workingProxies []string
|
||||
// for result := range ch {
|
||||
// if result.err == nil {
|
||||
// // 代理可用,添加到工作代理列表
|
||||
// workingProxies = append(workingProxies, result.proxy)
|
||||
// // 更新健康状态
|
||||
// updateProxyHealth(result.proxy, true, 0)
|
||||
// } else {
|
||||
// // 代理不可用,更新健康状态
|
||||
// updateProxyHealth(result.proxy, false, 0)
|
||||
// }
|
||||
// }
|
||||
// // 如果有可用的代理,随机选择一个
|
||||
// if len(workingProxies) > 0 {
|
||||
// selected := randomElement(workingProxies)
|
||||
// log.Printf("[INFO] 找到可用代理: %s (共 %d 个可用)", selected, len(workingProxies))
|
||||
// return "http://" + selected, nil
|
||||
// }
|
||||
// // 所有代理都不可用,返回错误
|
||||
// return "", fmt.Errorf("所有尾巴代理都不可用")
|
||||
//}
|
||||
//
|
||||
///*
|
||||
//* 检测代理健康状态,通过访问测试网站验证代理可用性
|
||||
//* param proxyURL[string] 代理服务器
|
||||
//* return 错误信息
|
||||
// */
|
||||
//func checkProxyHealth(proxyURL string) error {
|
||||
// start := time.Now() // 记录开始时间,用于计算响应时间
|
||||
// // 创建HTTP请求,设置代理和超时
|
||||
// req := gorequest.New().Proxy(proxyURL).Timeout(10 * time.Second)
|
||||
// // 发送GET请求到测试网站
|
||||
// resp, _, errs := req.Get("https://shop.kongfz.com/").End()
|
||||
//
|
||||
// responseTime := time.Since(start) // 计算响应时间
|
||||
// // 检查请求错误
|
||||
// if len(errs) > 0 {
|
||||
// updateProxyHealth(proxyURL, false, responseTime)
|
||||
// return fmt.Errorf("代理连接失败: %v", errs)
|
||||
// }
|
||||
// // 检查HTTP状态码
|
||||
// if resp.StatusCode != 200 {
|
||||
// updateProxyHealth(proxyURL, false, responseTime)
|
||||
// return fmt.Errorf("代理响应状态码错误: %d", resp.StatusCode)
|
||||
// }
|
||||
// // 代理检测成功
|
||||
// updateProxyHealth(proxyURL, true, responseTime)
|
||||
// log.Printf("[DEBUG] 代理 %s 检测成功, 响应时间: %v", getProxyHost(proxyURL), responseTime)
|
||||
// return nil
|
||||
//}
|
||||
//
|
||||
///*
|
||||
//* 更新代理健康状态
|
||||
//* param proxyURL[string] 代理服务器
|
||||
//* param success[bool] 是否成功
|
||||
//* param responseTime[time.Duration] 响应时间
|
||||
// */
|
||||
//func updateProxyHealth(proxyURL string, success bool, responseTime time.Duration) {
|
||||
// // 获取写锁,确保更新操作的互斥性
|
||||
// proxyHealthMutex.Lock()
|
||||
// defer proxyHealthMutex.Unlock() // 确保函数结束时释放锁
|
||||
// // 获取代理主机名作为键
|
||||
// host := getProxyHost(proxyURL)
|
||||
// // 如果代理健康状态不存在,创建新的
|
||||
// if _, exists := proxyHealthMaps[host]; !exists {
|
||||
// proxyHealthMaps[host] = &ProxyHealth{}
|
||||
// }
|
||||
//
|
||||
// health := proxyHealthMaps[host]
|
||||
// health.LastCheck = time.Now() // 更新最后检查时间
|
||||
//
|
||||
// if success {
|
||||
// // 成功的情况
|
||||
// health.SuccessCount++ // 增加成功次数
|
||||
// health.FailCount = 0 // 重置失败次数
|
||||
// health.ResponseTime = responseTime // 记录响应时间
|
||||
// health.IsHealthy = true // 标记为健康
|
||||
// } else {
|
||||
// // 失败的情况
|
||||
// health.FailCount++ // 增加失败次数
|
||||
// health.SuccessCount = 0 // 重置成功次数
|
||||
// // 连续失败3次标记为不健康
|
||||
// if health.FailCount >= 3 {
|
||||
// health.IsHealthy = false
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
//
|
||||
///*
|
||||
//* 获取代理主机名
|
||||
//* param proxyURL[string] 代理服务器
|
||||
//* return 代理服务器IP
|
||||
// */
|
||||
//func getProxyHost(proxyURL string) string {
|
||||
// // 去除协议前缀
|
||||
// if strings.HasPrefix(proxyURL, "http://") {
|
||||
// proxyURL = proxyURL[7:]
|
||||
// }
|
||||
// // 去除认证信息(用户名:密码@部分)
|
||||
// if atIndex := strings.Index(proxyURL, "@"); atIndex != -1 {
|
||||
// proxyURL = proxyURL[atIndex+1:]
|
||||
// }
|
||||
// // 去除端口
|
||||
// if colonIndex := strings.Index(proxyURL, ":"); colonIndex != -1 {
|
||||
// proxyURL = proxyURL[:colonIndex]
|
||||
// }
|
||||
// return proxyURL
|
||||
//}
|
||||
//
|
||||
//// 随机代理服务器
|
||||
//func randomServer() string {
|
||||
// return randomElement(servers)
|
||||
//}
|
||||
//
|
||||
//// 线程安全的随机元素选择
|
||||
//func randomElement(slice []string) string {
|
||||
// randMutex.Lock() // 获取互斥锁
|
||||
// defer randMutex.Unlock() // 确保函数结束时释放锁
|
||||
// return slice[globalRand.Intn(len(slice))] // 随机选择一个元素
|
||||
//}
|
||||
//
|
||||
//// 打乱服务器顺序
|
||||
//func shuffleServers(servers []string) []string {
|
||||
// randMutex.Lock()
|
||||
// defer randMutex.Unlock()
|
||||
// // 创建服务器副本,避免修改原始切片
|
||||
// shuffled := make([]string, len(servers))
|
||||
// copy(shuffled, servers)
|
||||
// // 使用Fisher-Yates算法打乱顺序
|
||||
// globalRand.Shuffle(len(shuffled), func(i, j int) {
|
||||
// shuffled[i], shuffled[j] = shuffled[j], shuffled[i]
|
||||
// })
|
||||
// return shuffled
|
||||
//}
|
||||
//
|
||||
///*
|
||||
//* 检查卡密是否过期
|
||||
//* param tailCardSecret[string] 卡密
|
||||
//* return 是否过期,错误信息
|
||||
// */
|
||||
//func checkTailCardSecretExpired(tailCardSecret string) (bool, error) {
|
||||
// // 获取机器码信息
|
||||
// code, err := getMachineCode(tailCardSecret)
|
||||
// if err != nil {
|
||||
// return false, fmt.Errorf("请求错误: %v", err)
|
||||
// }
|
||||
// // 解析过期时间字符串
|
||||
// targetTime, err := time.Parse("2006-01-02 15:04:05", code.Data.IpExpTime)
|
||||
// if err != nil {
|
||||
// return false, fmt.Errorf("时间格式错误: %v", err)
|
||||
// }
|
||||
// currentTime := time.Now()
|
||||
// // 卡密日期在当前日期之后表示有效
|
||||
// if targetTime.After(currentTime) {
|
||||
// return true, nil
|
||||
// } else {
|
||||
// // 卡密已过期
|
||||
// return false, fmt.Errorf("卡密已经过期!过期时间: %v", targetTime)
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//// 定义响应结构体
|
||||
//type getMachineCodeResp struct {
|
||||
// Code int `json:"code"` // 状态码
|
||||
// Message string `json:"message"` // 消息
|
||||
// Data struct {
|
||||
// MachineCode string `json:"machine_code"` // 机器码
|
||||
// IpExpTime string `json:"ip_exp_time"` // IP过期时间
|
||||
// IpThread int `json:"ip_thread"` // IP线程数
|
||||
// IpCardCode string `json:"ip_card_code"` // IP卡密
|
||||
// } `json:"data"`
|
||||
//}
|
||||
//
|
||||
///*
|
||||
//* 查询机器码
|
||||
//* param tailCardSecret[string] 卡密
|
||||
//* return 响应结构体,错误信息
|
||||
// */
|
||||
//func getMachineCode(tailCardSecret string) (*getMachineCodeResp, error) {
|
||||
// url := "http://114.66.2.223:7842/api/proxies/ip_show"
|
||||
// // 构建请求数据
|
||||
// data := map[string]interface{}{
|
||||
// "ip_card_code": tailCardSecret,
|
||||
// "agent_id": 9999,
|
||||
// }
|
||||
// // 发送POST请求
|
||||
// _, body, errs := gorequest.New().Post(url).Send(data).End()
|
||||
// if len(errs) > 0 {
|
||||
// return nil, fmt.Errorf("查询机器码失败: %v", errs)
|
||||
// }
|
||||
// // 解析响应
|
||||
// var resp getMachineCodeResp
|
||||
// if err := json.Unmarshal([]byte(body), &resp); err != nil {
|
||||
// return nil, fmt.Errorf("解析响应失败: %v", err)
|
||||
// }
|
||||
// // 处理不同的响应码
|
||||
// if resp.Code == 201 {
|
||||
// // 需要充值卡密
|
||||
// machineCode, err := rechargeCard(tailCardSecret, resp.Data.MachineCode)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// resp.Data.MachineCode = machineCode
|
||||
// return &resp, nil
|
||||
// } else if resp.Code == 200 {
|
||||
// // 成功获取机器码
|
||||
// return &resp, nil
|
||||
// } else {
|
||||
// // 其他错误
|
||||
// return nil, fmt.Errorf("查询机器码失败: %s", resp.Message)
|
||||
// }
|
||||
//}
|
||||
//
|
||||
///*
|
||||
//* 充值卡密
|
||||
//* param tailCardSecret[string] 卡密
|
||||
//* param machineCode[string] 机器码
|
||||
//* return 机器码,错误信息
|
||||
// */
|
||||
//func rechargeCard(tailCardSecret, machineCode string) (string, error) {
|
||||
// url := "http://114.66.2.223:7842/api/proxies/ip_recharge"
|
||||
// // 构建请求数据
|
||||
// data := map[string]interface{}{
|
||||
// "machine_code": machineCode,
|
||||
// "ip_card_code": tailCardSecret,
|
||||
// "agent_id": 9999,
|
||||
// }
|
||||
// // 发送POST请求
|
||||
// _, body, errs := gorequest.New().Post(url).Send(data).End()
|
||||
// if len(errs) > 0 {
|
||||
// return "", fmt.Errorf("充值卡密失败: %v", errs)
|
||||
// }
|
||||
// // 解析响应
|
||||
// var resp struct {
|
||||
// Code int `json:"code"` // 状态码
|
||||
// Message string `json:"message"` // 返回消息
|
||||
// Data struct {
|
||||
// IpExpTime string `json:"ip_exp_time"` // 过期时间
|
||||
// IpThread int `json:"ip_thread"` // 线程
|
||||
// MachineCode string `json:"machine_code"` // 机器码
|
||||
// } `json:"data"`
|
||||
// }
|
||||
// if err := json.Unmarshal([]byte(body), &resp); err != nil {
|
||||
// return "", fmt.Errorf("解析响应失败: %v", err)
|
||||
// }
|
||||
//
|
||||
// // 检查响应状态
|
||||
// if resp.Code != 200 {
|
||||
// return "", fmt.Errorf("充值卡密失败: %s", resp.Message)
|
||||
// }
|
||||
// return resp.Data.MachineCode, nil
|
||||
//}
|
||||
//
|
||||
///*
|
||||
//* 获取代理服务器列表
|
||||
//* param machineCode[string] 机器码
|
||||
//* return 代理服务器组,错误信息
|
||||
// */
|
||||
//func getProxies(machineCode string) ([]string, error) {
|
||||
// log.Printf("[INFO] 开始获取代理列表: %s", machineCode)
|
||||
// // 生成签名
|
||||
// sign := fmt.Sprintf("%x", md5.Sum([]byte(fmt.Sprintf("9999%s9999", machineCode))))
|
||||
//
|
||||
// // 构建请求URL
|
||||
// GetProxiesUrl := fmt.Sprintf("http://114.66.2.223:7842/api/proxies/get_proxy?machine_code=%s&sign=%s&agent_id=9999",
|
||||
// machineCode, sign)
|
||||
//
|
||||
// // 发送GET请求
|
||||
// req := gorequest.New().Get(GetProxiesUrl).Timeout(20 * time.Second)
|
||||
// _, body, errs := req.End()
|
||||
// if len(errs) > 0 {
|
||||
// return nil, fmt.Errorf("获取代理失败: %v", errs)
|
||||
// }
|
||||
//
|
||||
// // 检查是否为JSON错误响应(响应以{开头,以}结尾)
|
||||
// if strings.HasPrefix(strings.TrimSpace(body), "{") && strings.HasSuffix(strings.TrimSpace(body), "}") {
|
||||
// // 尝试解析为JSON错误响应
|
||||
// var errorResp struct {
|
||||
// Code int `json:"code"`
|
||||
// Message string `json:"message"`
|
||||
// }
|
||||
// if err := json.Unmarshal([]byte(body), &errorResp); err == nil {
|
||||
// return nil, fmt.Errorf("获取代理失败: %s (错误码: %d)", errorResp.Message, errorResp.Code)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// // 解析响应(每行一个代理地址)
|
||||
// lines := strings.Split(strings.TrimSpace(body), "\n")
|
||||
// var proxies []string
|
||||
// for _, line := range lines {
|
||||
// line = strings.TrimSpace(line)
|
||||
// // 过滤空行和JSON格式的行
|
||||
// if line != "" && !strings.HasPrefix(line, "{") {
|
||||
// proxies = append(proxies, line)
|
||||
// }
|
||||
// }
|
||||
// // 检查是否获取到有效代理
|
||||
// if len(proxies) == 0 {
|
||||
// return nil, fmt.Errorf("未获取到有效代理")
|
||||
// }
|
||||
//
|
||||
// log.Printf("[INFO] 获取到 %d 个代理", len(proxies))
|
||||
// return proxies, nil
|
||||
//}
|
||||
//
|
||||
//// 初始化代理信息
|
||||
//func initProxy() {
|
||||
//
|
||||
//}
|
||||
//
|
||||
//// =================== C 导出函数 =======================
|
||||
//
|
||||
//// GetProxyHealth 导出函数:获取代理健康状态(用于调试)
|
||||
////
|
||||
////export GetProxyHealth
|
||||
//func GetProxyHealth() *C.char {
|
||||
// proxyHealthMutex.RLock()
|
||||
// defer proxyHealthMutex.RUnlock()
|
||||
// // 构建健康信息映射
|
||||
// healthInfo := make(map[string]interface{})
|
||||
// for proxy, health := range proxyHealthMaps {
|
||||
// healthInfo[proxy] = map[string]interface{}{
|
||||
// "success_count": health.SuccessCount,
|
||||
// "fail_count": health.FailCount,
|
||||
// "last_check": health.LastCheck.Format(time.RFC3339),
|
||||
// "response_time": health.ResponseTime.String(),
|
||||
// "is_healthy": health.IsHealthy,
|
||||
// }
|
||||
// }
|
||||
// // 序列化为JSON
|
||||
// jsonData, err := json.Marshal(healthInfo)
|
||||
// if err != nil {
|
||||
// return C.CString(fmt.Sprintf(`{"error": "序列化健康信息失败: %v"}`, err))
|
||||
// }
|
||||
//
|
||||
// return C.CString(string(jsonData))
|
||||
//}
|
||||
//
|
||||
//// ProxyTypeManager 导出函数:代理类型管理器
|
||||
////
|
||||
////export ProxyTypeManager
|
||||
//func ProxyTypeManager(proxyType, username, password, machineCode *C.char) *C.char {
|
||||
// // C字符串转换为Go字符串
|
||||
// goProxyType := C.GoString(proxyType)
|
||||
// goUsername := C.GoString(username)
|
||||
// goPassword := C.GoString(password)
|
||||
// goMachineCode := C.GoString(machineCode)
|
||||
//
|
||||
// log.Printf("[DEBUG] 代理类型管理器调用: type=%s", goProxyType)
|
||||
// // 调用代理类型管理器
|
||||
// proxyURL, err := proxyTypeManager(goProxyType, goUsername, goPassword, goMachineCode)
|
||||
// if err != nil {
|
||||
// errorMsg := fmt.Sprintf("ERROR: %v", err)
|
||||
// log.Printf("[ERROR] 代理类型管理器错误: %v", err)
|
||||
// return C.CString(errorMsg)
|
||||
// }
|
||||
//
|
||||
// log.Printf("[DEBUG] 代理类型管理器返回: %s", proxyURL)
|
||||
// return C.CString(proxyURL)
|
||||
//}
|
||||
//
|
||||
//// ProxyTypeManagerNew 导出函数:代理类型管理器
|
||||
////
|
||||
////export ProxyTypeManagerNew
|
||||
//func ProxyTypeManagerNew(proxyType, username, password, machineCode *C.char) *C.char {
|
||||
// // C字符串转换为Go字符串
|
||||
// goProxyType := C.GoString(proxyType)
|
||||
// goUsername := C.GoString(username)
|
||||
// goPassword := C.GoString(password)
|
||||
// goMachineCode := C.GoString(machineCode)
|
||||
//
|
||||
// // 调用代理类型管理器
|
||||
// proxyURL, err := proxyTypeManagerNew(goProxyType, goUsername, goPassword, goMachineCode)
|
||||
// if err != nil {
|
||||
// errorMsg := fmt.Sprintf("ERROR: %v", err)
|
||||
// return C.CString(errorMsg)
|
||||
// }
|
||||
// return C.CString(proxyURL)
|
||||
//}
|
||||
//
|
||||
//// GetMachineCode 导出函数:查询机器码
|
||||
////
|
||||
////export GetMachineCode
|
||||
//func GetMachineCode(tailCardSecret *C.char) *C.char {
|
||||
// goTailCardSecret := C.GoString(tailCardSecret)
|
||||
//
|
||||
// resp, err := getMachineCode(goTailCardSecret)
|
||||
// if err != nil {
|
||||
// errorMsg := fmt.Sprintf("ERROR: %v", err)
|
||||
// log.Printf("[ERROR] 查询机器码错误: %v", err)
|
||||
// return C.CString(errorMsg)
|
||||
// }
|
||||
//
|
||||
// // 将响应转换为JSON
|
||||
// jsonData, err := json.Marshal(resp)
|
||||
// if err != nil {
|
||||
// errorMsg := fmt.Sprintf("ERROR: 序列化响应失败: %v", err)
|
||||
// log.Printf("[ERROR] 序列化机器码响应失败: %v", err)
|
||||
// return C.CString(errorMsg)
|
||||
// }
|
||||
//
|
||||
// return C.CString(string(jsonData))
|
||||
//}
|
||||
//
|
||||
//// RechargeCard 导出函数:充值卡密
|
||||
////
|
||||
////export RechargeCard
|
||||
//func RechargeCard(tailCardSecret, machineCode *C.char) *C.char {
|
||||
// goTailCardSecret := C.GoString(tailCardSecret)
|
||||
// goMachineCode := C.GoString(machineCode)
|
||||
// log.Printf("[DEBUG] 充值卡密调用: card=%s, machine_code=%s", goTailCardSecret, goMachineCode)
|
||||
//
|
||||
// newMachineCode, err := rechargeCard(goTailCardSecret, goMachineCode)
|
||||
// if err != nil {
|
||||
// errorMsg := fmt.Sprintf("ERROR: %v", err)
|
||||
// log.Printf("[ERROR] 充值卡密错误: %v", err)
|
||||
// return C.CString(errorMsg)
|
||||
// }
|
||||
//
|
||||
// // 构建成功响应
|
||||
// response := map[string]interface{}{
|
||||
// "success": true,
|
||||
// "machine_code": newMachineCode,
|
||||
// "message": "充值成功",
|
||||
// }
|
||||
//
|
||||
// jsonData, err := json.Marshal(response)
|
||||
// if err != nil {
|
||||
// errorMsg := fmt.Sprintf("ERROR: 序列化响应失败: %v", err)
|
||||
// log.Printf("[ERROR] 序列化充值响应失败: %v", err)
|
||||
// return C.CString(errorMsg)
|
||||
// }
|
||||
//
|
||||
// log.Printf("[DEBUG] 充值卡密成功: new_machine_code=%s", newMachineCode)
|
||||
// return C.CString(string(jsonData))
|
||||
//}
|
||||
//
|
||||
//// GetProxies 导出函数:获取代理服务器列表
|
||||
////
|
||||
////export GetProxies
|
||||
//func GetProxies(machineCode *C.char) *C.char {
|
||||
// goMachineCode := C.GoString(machineCode)
|
||||
// log.Printf("[DEBUG] 获取代理服务器列表调用: machine_code=%s", goMachineCode)
|
||||
//
|
||||
// proxies, err := getProxies(goMachineCode)
|
||||
// if err != nil {
|
||||
// errorMsg := fmt.Sprintf("ERROR: %v", err)
|
||||
// log.Printf("[ERROR] 获取代理服务器列表错误: %v", err)
|
||||
// return C.CString(errorMsg)
|
||||
// }
|
||||
//
|
||||
// // 将代理列表转换为JSON
|
||||
// response := map[string]interface{}{
|
||||
// "success": true,
|
||||
// "count": len(proxies),
|
||||
// "proxies": proxies,
|
||||
// }
|
||||
//
|
||||
// jsonData, err := json.Marshal(response)
|
||||
// if err != nil {
|
||||
// errorMsg := fmt.Sprintf("ERROR: 序列化响应失败: %v", err)
|
||||
// log.Printf("[ERROR] 序列化代理列表失败: %v", err)
|
||||
// return C.CString(errorMsg)
|
||||
// }
|
||||
//
|
||||
// log.Printf("[DEBUG] 获取代理服务器列表成功: count=%d", len(proxies))
|
||||
// return C.CString(string(jsonData))
|
||||
//}
|
||||
//
|
||||
//// CheckTailCardSecretExpired 导出函数:检查卡密是否过期
|
||||
////
|
||||
////export CheckTailCardSecretExpired
|
||||
//func CheckTailCardSecretExpired(tailCardSecret *C.char) *C.char {
|
||||
// goTailCardSecret := C.GoString(tailCardSecret)
|
||||
//
|
||||
// isValid, err := checkTailCardSecretExpired(goTailCardSecret)
|
||||
// if err != nil {
|
||||
// // 如果是过期错误,返回特定格式
|
||||
// if strings.Contains(err.Error(), "卡密已经过期") {
|
||||
// response := map[string]interface{}{
|
||||
// "is_valid": false,
|
||||
// "message": err.Error(),
|
||||
// }
|
||||
// jsonData, _ := json.Marshal(response)
|
||||
// return C.CString(string(jsonData))
|
||||
// }
|
||||
//
|
||||
// errorMsg := fmt.Sprintf("ERROR: %v", err)
|
||||
// log.Printf("[ERROR] 检查卡密过期错误: %v", err)
|
||||
// return C.CString(errorMsg)
|
||||
// }
|
||||
//
|
||||
// response := map[string]interface{}{
|
||||
// "is_valid": isValid,
|
||||
// "message": "卡密有效",
|
||||
// }
|
||||
//
|
||||
// jsonData, err := json.Marshal(response)
|
||||
// if err != nil {
|
||||
// errorMsg := fmt.Sprintf("ERROR: 序列化响应失败: %v", err)
|
||||
// return C.CString(errorMsg)
|
||||
// }
|
||||
// return C.CString(string(jsonData))
|
||||
//}
|
||||
//
|
||||
//// InitProxyManager 导出函数:初始化代理管理器
|
||||
////
|
||||
////export InitProxyManager
|
||||
//func InitProxyManager(serversJson, username, password, tailCardSecret, proxyType *C.char) *C.char {
|
||||
// goServersJson := C.GoString(serversJson)
|
||||
// goUsername := C.GoString(username)
|
||||
// goPassword := C.GoString(password)
|
||||
// goTailCardSecret := C.GoString(tailCardSecret)
|
||||
// goProxyType := C.GoString(proxyType)
|
||||
//
|
||||
// log.Printf("[DEBUG] 初始化代理管理器调用: type=%s", goProxyType)
|
||||
//
|
||||
// // 解析服务器列表
|
||||
// var serverList []string
|
||||
// if goServersJson != "" {
|
||||
// if err := json.Unmarshal([]byte(goServersJson), &serverList); err != nil {
|
||||
// errorMsg := fmt.Sprintf("ERROR: 解析服务器列表失败: %v", err)
|
||||
// log.Printf("[ERROR] 解析服务器列表失败: %v", err)
|
||||
// return C.CString(errorMsg)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// // 更新全局服务器列表
|
||||
// if len(serverList) > 0 {
|
||||
// randMutex.Lock()
|
||||
// servers = serverList
|
||||
// randMutex.Unlock()
|
||||
// log.Printf("[INFO] 更新服务器列表,共 %d 个服务器", len(servers))
|
||||
// }
|
||||
//
|
||||
// // 创建代理管理器
|
||||
// manager := ProxyManager{
|
||||
// servers: servers,
|
||||
// username: goUsername,
|
||||
// password: goPassword,
|
||||
// tailCardSecret: goTailCardSecret,
|
||||
// proxyType: goProxyType,
|
||||
// }
|
||||
//
|
||||
// // 序列化管理器信息
|
||||
// jsonData, err := json.Marshal(manager)
|
||||
// if err != nil {
|
||||
// errorMsg := fmt.Sprintf("ERROR: 序列化代理管理器失败: %v", err)
|
||||
// log.Printf("[ERROR] 序列化代理管理器失败: %v", err)
|
||||
// return C.CString(errorMsg)
|
||||
// }
|
||||
//
|
||||
// log.Printf("[DEBUG] 代理管理器初始化成功")
|
||||
// return C.CString(string(jsonData))
|
||||
//}
|
||||
//
|
||||
//// FreeCString 导出函数:释放C字符串内存
|
||||
////
|
||||
////export FreeCString
|
||||
//func FreeCString(str *C.char) {
|
||||
// C.free(unsafe.Pointer(str))
|
||||
//}
|
||||
//
|
||||
//// PROXY_VERSION 版本号
|
||||
//const (
|
||||
// PROXY_VERSION = "v2"
|
||||
//)
|
||||
//
|
||||
//// GetVersion 获取版本
|
||||
////
|
||||
////export GetVersion
|
||||
//func GetVersion() *C.char {
|
||||
// return C.CString(PROXY_VERSION)
|
||||
//}
|
||||
//
|
||||
//// 空main函数,编译DLL时需要
|
||||
//func main() {
|
||||
// managerNew, err := proxyTypeManagerNew("", "", "", "07f4d0fbcff99966c2b37b0c1fb7f01c")
|
||||
// if err != nil {
|
||||
// fmt.Println(err.Error())
|
||||
// }
|
||||
// fmt.Println(managerNew)
|
||||
//}
|
||||
@ -1,7 +1,5 @@
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
///*
|
||||
//#cgo LDFLAGS: -ldl
|
||||
//
|
||||
@ -700,10 +698,10 @@ import "fmt"
|
||||
//func main() {
|
||||
//}
|
||||
|
||||
func main() {
|
||||
managerNew, err := proxyTypeManagerNew("", "", "", "07f4d0fbcff99966c2b37b0c1fb7f01c")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
fmt.Println(managerNew)
|
||||
}
|
||||
//func main() {
|
||||
// managerNew, err := proxyTypeManagerNew("", "", "", "07f4d0fbcff99966c2b37b0c1fb7f01c")
|
||||
// if err != nil {
|
||||
// fmt.Println(err)
|
||||
// }
|
||||
// fmt.Println(managerNew)
|
||||
//}
|
||||
|
||||
675
redis/redis.go
Normal file
675
redis/redis.go
Normal file
@ -0,0 +1,675 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
// RedisClient Redis客户端结构体
|
||||
type RedisClient struct {
|
||||
Client *redis.Client
|
||||
Ctx context.Context
|
||||
}
|
||||
|
||||
// RedisConfig Redis配置结构体
|
||||
type RedisConfig struct {
|
||||
Addr string // 服务器地址:端口
|
||||
Password string // 密码
|
||||
DB int // 数据库编号
|
||||
PoolSize int // 连接池大小
|
||||
MinIdleConns int // 最小空闲连接数
|
||||
DialTimeout time.Duration // 连接超时
|
||||
ReadTimeout time.Duration // 读取超时
|
||||
WriteTimeout time.Duration // 写入超时
|
||||
PoolTimeout time.Duration // 获取连接超时
|
||||
}
|
||||
|
||||
// NewRedisClient 创建新的Redis客户端
|
||||
func NewRedisClient(config *RedisConfig) *RedisClient {
|
||||
if config == nil {
|
||||
// 默认配置 - 根据您的需求设置
|
||||
config = &RedisConfig{
|
||||
Addr: "36.212.20.113:7963",
|
||||
Password: "",
|
||||
DB: 2,
|
||||
PoolSize: 10,
|
||||
MinIdleConns: 5,
|
||||
DialTimeout: 5 * time.Second,
|
||||
ReadTimeout: 3 * time.Second,
|
||||
WriteTimeout: 3 * time.Second,
|
||||
PoolTimeout: 4 * time.Second,
|
||||
}
|
||||
}
|
||||
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: config.Addr,
|
||||
Password: config.Password,
|
||||
DB: config.DB,
|
||||
PoolSize: config.PoolSize,
|
||||
MinIdleConns: config.MinIdleConns,
|
||||
DialTimeout: config.DialTimeout,
|
||||
ReadTimeout: config.ReadTimeout,
|
||||
WriteTimeout: config.WriteTimeout,
|
||||
PoolTimeout: config.PoolTimeout,
|
||||
})
|
||||
|
||||
return &RedisClient{
|
||||
Client: rdb,
|
||||
Ctx: context.Background(),
|
||||
}
|
||||
}
|
||||
|
||||
// Close 关闭Redis连接
|
||||
func (r *RedisClient) Close() error {
|
||||
return r.Client.Close()
|
||||
}
|
||||
|
||||
// Ping 测试连接
|
||||
func (r *RedisClient) Ping() error {
|
||||
pong, err := r.Client.Ping(r.Ctx).Result()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Redis连接失败: %v", err)
|
||||
}
|
||||
log.Printf("Redis连接成功: %s", pong)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ==================== 字符串操作 ====================
|
||||
|
||||
// Set 设置键值对
|
||||
func (r *RedisClient) Set(key string, value interface{}, expiration time.Duration) error {
|
||||
err := r.Client.Set(r.Ctx, key, value, expiration).Err()
|
||||
if err != nil {
|
||||
return fmt.Errorf("设置键 %s 失败: %v", key, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get 获取字符串值
|
||||
func (r *RedisClient) Get(key string) (string, error) {
|
||||
val, err := r.Client.Get(r.Ctx, key).Result()
|
||||
if err == redis.Nil {
|
||||
return "", fmt.Errorf("键 %s 不存在", key)
|
||||
} else if err != nil {
|
||||
return "", fmt.Errorf("获取键 %s 失败: %v", key, err)
|
||||
}
|
||||
return val, nil
|
||||
}
|
||||
|
||||
// GetInt 获取整数值
|
||||
func (r *RedisClient) GetInt(key string) (int, error) {
|
||||
val, err := r.Client.Get(r.Ctx, key).Int()
|
||||
if err == redis.Nil {
|
||||
return 0, fmt.Errorf("键 %s 不存在", key)
|
||||
} else if err != nil {
|
||||
return 0, fmt.Errorf("获取键 %s 失败: %v", key, err)
|
||||
}
|
||||
return val, nil
|
||||
}
|
||||
|
||||
// GetInt64 获取64位整数值
|
||||
func (r *RedisClient) GetInt64(key string) (int64, error) {
|
||||
val, err := r.Client.Get(r.Ctx, key).Int64()
|
||||
if err == redis.Nil {
|
||||
return 0, fmt.Errorf("键 %s 不存在", key)
|
||||
} else if err != nil {
|
||||
return 0, fmt.Errorf("获取键 %s 失败: %v", key, err)
|
||||
}
|
||||
return val, nil
|
||||
}
|
||||
|
||||
// GetFloat64 获取浮点数值
|
||||
func (r *RedisClient) GetFloat64(key string) (float64, error) {
|
||||
val, err := r.Client.Get(r.Ctx, key).Float64()
|
||||
if err == redis.Nil {
|
||||
return 0, fmt.Errorf("键 %s 不存在", key)
|
||||
} else if err != nil {
|
||||
return 0, fmt.Errorf("获取键 %s 失败: %v", key, err)
|
||||
}
|
||||
return val, nil
|
||||
}
|
||||
|
||||
// GetBool 获取布尔值
|
||||
func (r *RedisClient) GetBool(key string) (bool, error) {
|
||||
val, err := r.Client.Get(r.Ctx, key).Bool()
|
||||
if err == redis.Nil {
|
||||
return false, fmt.Errorf("键 %s 不存在", key)
|
||||
} else if err != nil {
|
||||
return false, fmt.Errorf("获取键 %s 失败: %v", key, err)
|
||||
}
|
||||
return val, nil
|
||||
}
|
||||
|
||||
// SetNX 只有当键不存在时设置值
|
||||
func (r *RedisClient) SetNX(key string, value interface{}, expiration time.Duration) (bool, error) {
|
||||
success, err := r.Client.SetNX(r.Ctx, key, value, expiration).Result()
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("SetNX操作失败: %v", err)
|
||||
}
|
||||
return success, nil
|
||||
}
|
||||
|
||||
// SetXX 只有当键存在时设置值
|
||||
func (r *RedisClient) SetXX(key string, value interface{}, expiration time.Duration) (bool, error) {
|
||||
success, err := r.Client.SetXX(r.Ctx, key, value, expiration).Result()
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("SetXX操作失败: %v", err)
|
||||
}
|
||||
return success, nil
|
||||
}
|
||||
|
||||
// MSet 批量设置键值对
|
||||
func (r *RedisClient) MSet(values map[string]interface{}) error {
|
||||
pairs := make([]interface{}, 0, len(values)*2)
|
||||
for k, v := range values {
|
||||
pairs = append(pairs, k, v)
|
||||
}
|
||||
err := r.Client.MSet(r.Ctx, pairs...).Err()
|
||||
if err != nil {
|
||||
return fmt.Errorf("批量设置失败: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// MGet 批量获取值
|
||||
func (r *RedisClient) MGet(keys ...string) ([]interface{}, error) {
|
||||
vals, err := r.Client.MGet(r.Ctx, keys...).Result()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("批量获取失败: %v", err)
|
||||
}
|
||||
return vals, nil
|
||||
}
|
||||
|
||||
// Incr 自增1
|
||||
func (r *RedisClient) Incr(key string) (int64, error) {
|
||||
val, err := r.Client.Incr(r.Ctx, key).Result()
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("自增失败: %v", err)
|
||||
}
|
||||
return val, nil
|
||||
}
|
||||
|
||||
// IncrBy 自增指定值
|
||||
func (r *RedisClient) IncrBy(key string, value int64) (int64, error) {
|
||||
val, err := r.Client.IncrBy(r.Ctx, key, value).Result()
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("自增 %d 失败: %v", value, err)
|
||||
}
|
||||
return val, nil
|
||||
}
|
||||
|
||||
// Decr 自减1
|
||||
func (r *RedisClient) Decr(key string) (int64, error) {
|
||||
val, err := r.Client.Decr(r.Ctx, key).Result()
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("自减失败: %v", err)
|
||||
}
|
||||
return val, nil
|
||||
}
|
||||
|
||||
// DecrBy 自减指定值
|
||||
func (r *RedisClient) DecrBy(key string, value int64) (int64, error) {
|
||||
val, err := r.Client.DecrBy(r.Ctx, key, value).Result()
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("自减 %d 失败: %v", value, err)
|
||||
}
|
||||
return val, nil
|
||||
}
|
||||
|
||||
// Append 追加字符串
|
||||
func (r *RedisClient) Append(key, value string) (int64, error) {
|
||||
length, err := r.Client.Append(r.Ctx, key, value).Result()
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("追加字符串失败: %v", err)
|
||||
}
|
||||
return length, nil
|
||||
}
|
||||
|
||||
// StrLen 获取字符串长度
|
||||
func (r *RedisClient) StrLen(key string) (int64, error) {
|
||||
length, err := r.Client.StrLen(r.Ctx, key).Result()
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("获取字符串长度失败: %v", err)
|
||||
}
|
||||
return length, nil
|
||||
}
|
||||
|
||||
// ==================== 键操作 ====================
|
||||
|
||||
// Exists 检查键是否存在
|
||||
func (r *RedisClient) Exists(keys ...string) (int64, error) {
|
||||
count, err := r.Client.Exists(r.Ctx, keys...).Result()
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("检查键是否存在失败: %v", err)
|
||||
}
|
||||
return count, nil
|
||||
}
|
||||
|
||||
// Del 删除键
|
||||
func (r *RedisClient) Del(keys ...string) (int64, error) {
|
||||
count, err := r.Client.Del(r.Ctx, keys...).Result()
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("删除键失败: %v", err)
|
||||
}
|
||||
return count, nil
|
||||
}
|
||||
|
||||
// Expire 设置过期时间
|
||||
func (r *RedisClient) Expire(key string, expiration time.Duration) (bool, error) {
|
||||
success, err := r.Client.Expire(r.Ctx, key, expiration).Result()
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("设置过期时间失败: %v", err)
|
||||
}
|
||||
return success, nil
|
||||
}
|
||||
|
||||
// TTL 获取剩余过期时间
|
||||
func (r *RedisClient) TTL(key string) (time.Duration, error) {
|
||||
ttl, err := r.Client.TTL(r.Ctx, key).Result()
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("获取过期时间失败: %v", err)
|
||||
}
|
||||
return ttl, nil
|
||||
}
|
||||
|
||||
// Persist 移除过期时间
|
||||
func (r *RedisClient) Persist(key string) (bool, error) {
|
||||
success, err := r.Client.Persist(r.Ctx, key).Result()
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("移除过期时间失败: %v", err)
|
||||
}
|
||||
return success, nil
|
||||
}
|
||||
|
||||
// Type 获取键类型
|
||||
func (r *RedisClient) Type(key string) (string, error) {
|
||||
typeStr, err := r.Client.Type(r.Ctx, key).Result()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("获取键类型失败: %v", err)
|
||||
}
|
||||
return typeStr, nil
|
||||
}
|
||||
|
||||
// Keys 查找所有匹配的键
|
||||
func (r *RedisClient) Keys(pattern string) ([]string, error) {
|
||||
keys, err := r.Client.Keys(r.Ctx, pattern).Result()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("查找键失败: %v", err)
|
||||
}
|
||||
return keys, nil
|
||||
}
|
||||
|
||||
// RandomKey 随机获取一个键
|
||||
func (r *RedisClient) RandomKey() (string, error) {
|
||||
key, err := r.Client.RandomKey(r.Ctx).Result()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("随机获取键失败: %v", err)
|
||||
}
|
||||
return key, nil
|
||||
}
|
||||
|
||||
// Rename 重命名键
|
||||
func (r *RedisClient) Rename(key, newKey string) error {
|
||||
err := r.Client.Rename(r.Ctx, key, newKey).Err()
|
||||
if err != nil {
|
||||
return fmt.Errorf("重命名键失败: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RenameNX 只有当新键不存在时重命名
|
||||
func (r *RedisClient) RenameNX(key, newKey string) (bool, error) {
|
||||
success, err := r.Client.RenameNX(r.Ctx, key, newKey).Result()
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("重命名键失败: %v", err)
|
||||
}
|
||||
return success, nil
|
||||
}
|
||||
|
||||
// ==================== Hash操作 ====================
|
||||
|
||||
// HSet 设置Hash字段
|
||||
func (r *RedisClient) HSet(key string, values ...interface{}) error {
|
||||
err := r.Client.HSet(r.Ctx, key, values...).Err()
|
||||
if err != nil {
|
||||
return fmt.Errorf("设置Hash失败: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// HGet 获取Hash字段
|
||||
func (r *RedisClient) HGet(key, field string) (string, error) {
|
||||
val, err := r.Client.HGet(r.Ctx, key, field).Result()
|
||||
if err == redis.Nil {
|
||||
return "", fmt.Errorf("字段 %s 不存在", field)
|
||||
} else if err != nil {
|
||||
return "", fmt.Errorf("获取Hash字段失败: %v", err)
|
||||
}
|
||||
return val, nil
|
||||
}
|
||||
|
||||
// HGetAll 获取所有Hash字段
|
||||
func (r *RedisClient) HGetAll(key string) (map[string]string, error) {
|
||||
vals, err := r.Client.HGetAll(r.Ctx, key).Result()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("获取所有Hash字段失败: %v", err)
|
||||
}
|
||||
return vals, nil
|
||||
}
|
||||
|
||||
// HDel 删除Hash字段
|
||||
func (r *RedisClient) HDel(key string, fields ...string) (int64, error) {
|
||||
count, err := r.Client.HDel(r.Ctx, key, fields...).Result()
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("删除Hash字段失败: %v", err)
|
||||
}
|
||||
return count, nil
|
||||
}
|
||||
|
||||
// HExists 检查Hash字段是否存在
|
||||
func (r *RedisClient) HExists(key, field string) (bool, error) {
|
||||
exists, err := r.Client.HExists(r.Ctx, key, field).Result()
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("检查Hash字段失败: %v", err)
|
||||
}
|
||||
return exists, nil
|
||||
}
|
||||
|
||||
// HKeys 获取所有Hash字段名
|
||||
func (r *RedisClient) HKeys(key string) ([]string, error) {
|
||||
keys, err := r.Client.HKeys(r.Ctx, key).Result()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("获取Hash字段名失败: %v", err)
|
||||
}
|
||||
return keys, nil
|
||||
}
|
||||
|
||||
// HVals 获取所有Hash字段值
|
||||
func (r *RedisClient) HVals(key string) ([]string, error) {
|
||||
vals, err := r.Client.HVals(r.Ctx, key).Result()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("获取Hash字段值失败: %v", err)
|
||||
}
|
||||
return vals, nil
|
||||
}
|
||||
|
||||
// HLen 获取Hash字段数量
|
||||
func (r *RedisClient) HLen(key string) (int64, error) {
|
||||
count, err := r.Client.HLen(r.Ctx, key).Result()
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("获取Hash字段数量失败: %v", err)
|
||||
}
|
||||
return count, nil
|
||||
}
|
||||
|
||||
// HIncrBy Hash字段自增
|
||||
func (r *RedisClient) HIncrBy(key, field string, incr int64) (int64, error) {
|
||||
val, err := r.Client.HIncrBy(r.Ctx, key, field, incr).Result()
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("Hash字段自增失败: %v", err)
|
||||
}
|
||||
return val, nil
|
||||
}
|
||||
|
||||
// ==================== List操作 ====================
|
||||
|
||||
// LPush 从左侧插入列表
|
||||
func (r *RedisClient) LPush(key string, values ...interface{}) (int64, error) {
|
||||
count, err := r.Client.LPush(r.Ctx, key, values...).Result()
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("LPush失败: %v", err)
|
||||
}
|
||||
return count, nil
|
||||
}
|
||||
|
||||
// RPush 从右侧插入列表
|
||||
func (r *RedisClient) RPush(key string, values ...interface{}) (int64, error) {
|
||||
count, err := r.Client.RPush(r.Ctx, key, values...).Result()
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("RPush失败: %v", err)
|
||||
}
|
||||
return count, nil
|
||||
}
|
||||
|
||||
// LPop 从左侧弹出元素
|
||||
func (r *RedisClient) LPop(key string) (string, error) {
|
||||
val, err := r.Client.LPop(r.Ctx, key).Result()
|
||||
if err == redis.Nil {
|
||||
return "", fmt.Errorf("列表为空")
|
||||
} else if err != nil {
|
||||
return "", fmt.Errorf("LPop失败: %v", err)
|
||||
}
|
||||
return val, nil
|
||||
}
|
||||
|
||||
// RPop 从右侧弹出元素
|
||||
func (r *RedisClient) RPop(key string) (string, error) {
|
||||
val, err := r.Client.RPop(r.Ctx, key).Result()
|
||||
if err == redis.Nil {
|
||||
return "", fmt.Errorf("列表为空")
|
||||
} else if err != nil {
|
||||
return "", fmt.Errorf("RPop失败: %v", err)
|
||||
}
|
||||
return val, nil
|
||||
}
|
||||
|
||||
// LLen 获取列表长度
|
||||
func (r *RedisClient) LLen(key string) (int64, error) {
|
||||
length, err := r.Client.LLen(r.Ctx, key).Result()
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("获取列表长度失败: %v", err)
|
||||
}
|
||||
return length, nil
|
||||
}
|
||||
|
||||
// LRange 获取列表范围
|
||||
func (r *RedisClient) LRange(key string, start, stop int64) ([]string, error) {
|
||||
vals, err := r.Client.LRange(r.Ctx, key, start, stop).Result()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("获取列表范围失败: %v", err)
|
||||
}
|
||||
return vals, nil
|
||||
}
|
||||
|
||||
// ==================== Set操作 ====================
|
||||
|
||||
// SAdd 添加集合元素
|
||||
func (r *RedisClient) SAdd(key string, members ...interface{}) (int64, error) {
|
||||
count, err := r.Client.SAdd(r.Ctx, key, members...).Result()
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("SAdd失败: %v", err)
|
||||
}
|
||||
return count, nil
|
||||
}
|
||||
|
||||
// SMembers 获取所有集合成员
|
||||
func (r *RedisClient) SMembers(key string) ([]string, error) {
|
||||
members, err := r.Client.SMembers(r.Ctx, key).Result()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("获取集合成员失败: %v", err)
|
||||
}
|
||||
return members, nil
|
||||
}
|
||||
|
||||
// SIsMember 检查是否是集合成员
|
||||
func (r *RedisClient) SIsMember(key string, member interface{}) (bool, error) {
|
||||
exists, err := r.Client.SIsMember(r.Ctx, key, member).Result()
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("检查集合成员失败: %v", err)
|
||||
}
|
||||
return exists, nil
|
||||
}
|
||||
|
||||
// SCard 获取集合基数
|
||||
func (r *RedisClient) SCard(key string) (int64, error) {
|
||||
count, err := r.Client.SCard(r.Ctx, key).Result()
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("获取集合基数失败: %v", err)
|
||||
}
|
||||
return count, nil
|
||||
}
|
||||
|
||||
// ==================== Sorted Set操作 ====================
|
||||
|
||||
// ZAdd 添加有序集合元素
|
||||
func (r *RedisClient) ZAdd(key string, members ...redis.Z) (int64, error) {
|
||||
count, err := r.Client.ZAdd(r.Ctx, key, members...).Result()
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("ZAdd失败: %v", err)
|
||||
}
|
||||
return count, nil
|
||||
}
|
||||
|
||||
// ZRange 获取有序集合范围
|
||||
func (r *RedisClient) ZRange(key string, start, stop int64) ([]string, error) {
|
||||
vals, err := r.Client.ZRange(r.Ctx, key, start, stop).Result()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ZRange失败: %v", err)
|
||||
}
|
||||
return vals, nil
|
||||
}
|
||||
|
||||
// ZRangeWithScores 获取有序集合范围和分数
|
||||
func (r *RedisClient) ZRangeWithScores(key string, start, stop int64) ([]redis.Z, error) {
|
||||
vals, err := r.Client.ZRangeWithScores(r.Ctx, key, start, stop).Result()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ZRangeWithScores失败: %v", err)
|
||||
}
|
||||
return vals, nil
|
||||
}
|
||||
|
||||
// ZScore 获取元素分数
|
||||
func (r *RedisClient) ZScore(key, member string) (float64, error) {
|
||||
score, err := r.Client.ZScore(r.Ctx, key, member).Result()
|
||||
if err == redis.Nil {
|
||||
return 0, fmt.Errorf("元素不存在")
|
||||
} else if err != nil {
|
||||
return 0, fmt.Errorf("获取分数失败: %v", err)
|
||||
}
|
||||
return score, nil
|
||||
}
|
||||
|
||||
// ==================== JSON序列化操作 ====================
|
||||
|
||||
// SetJSON 将结构体序列化为JSON并存储
|
||||
func (r *RedisClient) SetJSON(key string, data interface{}, expiration time.Duration) error {
|
||||
jsonData, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("JSON序列化失败: %v", err)
|
||||
}
|
||||
return r.Set(key, jsonData, expiration)
|
||||
}
|
||||
|
||||
// GetJSON 获取JSON并反序列化到结构体
|
||||
func (r *RedisClient) GetJSON(key string, result interface{}) error {
|
||||
data, err := r.Get(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = json.Unmarshal([]byte(data), result)
|
||||
if err != nil {
|
||||
return fmt.Errorf("JSON反序列化失败: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ==================== 分布式锁 ====================
|
||||
|
||||
// TryLock 尝试获取分布式锁
|
||||
func (r *RedisClient) TryLock(key string, expiration time.Duration) (bool, error) {
|
||||
success, err := r.Client.SetNX(r.Ctx, key, "locked", expiration).Result()
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("获取锁失败: %v", err)
|
||||
}
|
||||
return success, nil
|
||||
}
|
||||
|
||||
// ReleaseLock 释放分布式锁
|
||||
func (r *RedisClient) ReleaseLock(key string) error {
|
||||
// 使用Lua脚本确保原子性操作
|
||||
script := `
|
||||
if redis.call("get", KEYS[1]) == ARGV[1] then
|
||||
return redis.call("del", KEYS[1])
|
||||
else
|
||||
return 0
|
||||
end
|
||||
`
|
||||
result, err := r.Client.Eval(r.Ctx, script, []string{key}, "locked").Result()
|
||||
if err != nil {
|
||||
return fmt.Errorf("释放锁失败: %v", err)
|
||||
}
|
||||
if result.(int64) == 0 {
|
||||
return fmt.Errorf("锁已释放或不属于当前客户端")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ==================== 管道操作 ====================
|
||||
|
||||
// Pipeline 执行管道操作
|
||||
func (r *RedisClient) Pipeline(fn func(pipe redis.Pipeliner) error) error {
|
||||
pipe := r.Client.Pipeline()
|
||||
err := fn(pipe)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = pipe.Exec(r.Ctx)
|
||||
return err
|
||||
}
|
||||
|
||||
// ==================== 事务操作 ====================
|
||||
|
||||
// Watch 监视键并执行事务
|
||||
func (r *RedisClient) Watch(fn func(tx *redis.Tx) error, keys ...string) error {
|
||||
err := r.Client.Watch(r.Ctx, fn, keys...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("事务执行失败: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ==================== 发布订阅 ====================
|
||||
|
||||
// Publish 发布消息
|
||||
func (r *RedisClient) Publish(channel string, message interface{}) error {
|
||||
err := r.Client.Publish(r.Ctx, channel, message).Err()
|
||||
if err != nil {
|
||||
return fmt.Errorf("发布消息失败: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Subscribe 订阅频道
|
||||
func (r *RedisClient) Subscribe(channels ...string) *redis.PubSub {
|
||||
return r.Client.Subscribe(r.Ctx, channels...)
|
||||
}
|
||||
|
||||
// ==================== 数据库操作 ====================
|
||||
|
||||
// SelectDB 切换数据库
|
||||
// 注意:go-redis/v9的Client没有直接的Select方法,需要通过Do命令执行
|
||||
func (r *RedisClient) SelectDB(db int) error {
|
||||
// 使用Do命令执行SELECT
|
||||
cmd := redis.NewCmd(r.Ctx, "SELECT", db)
|
||||
err := r.Client.Process(r.Ctx, cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("切换数据库到 %d 失败: %v", db, err)
|
||||
}
|
||||
|
||||
// 检查命令执行结果
|
||||
_, err = cmd.Result()
|
||||
if err != nil {
|
||||
return fmt.Errorf("切换数据库到 %d 失败: %v", db, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
//// 主函数
|
||||
//func main() {
|
||||
//}
|
||||
334
redis/redisDll.go
Normal file
334
redis/redisDll.go
Normal file
@ -0,0 +1,334 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// KfzVerifyPriceInfo 主结构体
|
||||
type KfzVerifyPriceInfo struct {
|
||||
Isbn string `json:"isbn"` // ISBN
|
||||
OnSaleCount string `json:"onSaleCount"` // 在售数量
|
||||
AvgPrice string `json:"avgPrice"` // 平均价格
|
||||
LastPushTime string `json:"lastPushTime"` // 最后推送时间
|
||||
Source string `json:"source"` // 来源
|
||||
Data []BookItem `json:"data"` // 商品列表
|
||||
}
|
||||
|
||||
// BookItem 商品项的具体结构
|
||||
type BookItem struct {
|
||||
Price string `json:"price"` // 价格 "17.01"
|
||||
ShippingFee float64 `json:"shippingFee"` // 运费 0 或 5.8
|
||||
Condition string `json:"condition"` // 品相 "八五品"
|
||||
ImgBigUrl string `json:"imgBigUrl"` // 图片URL
|
||||
ShopAvgShippingTime string `json:"shopAvgShippingTime"` // 平均发货时间 "10"
|
||||
ShopSuccessOrderRate string `json:"shopSuccessOrderRate"` // 成功率 "90.03"
|
||||
GoodsUrl string `json:"goodsUrl"` // 商品URL "575550/9643470711"
|
||||
Title string `json:"title"` // 标题
|
||||
GoodsImgCount int `json:"goodsImgCount"` // 图片数量 9
|
||||
}
|
||||
|
||||
// ParseRedisData 解析Redis返回的数据
|
||||
func ParseRedisData(redisData interface{}) (*KfzVerifyPriceInfo, error) {
|
||||
// 将redisData转换为[]interface{}类型
|
||||
data, ok := redisData.([]interface{})
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("redis数据格式错误: 期望数组,得到 %T", redisData)
|
||||
}
|
||||
|
||||
// 检查数组长度
|
||||
if len(data) < 6 {
|
||||
return nil, fmt.Errorf("redis数据格式错误: 数组长度不足,期望至少6个元素,实际 %d", len(data))
|
||||
}
|
||||
|
||||
// 创建结果对象
|
||||
result := &KfzVerifyPriceInfo{}
|
||||
|
||||
// 解析ISBN (索引0)
|
||||
if isbn, ok := data[0].(string); ok {
|
||||
result.Isbn = isbn
|
||||
} else {
|
||||
result.Isbn = fmt.Sprintf("%v", data[0])
|
||||
}
|
||||
|
||||
// 解析在售数量 (索引2)
|
||||
if onSaleCount, ok := data[2].(string); ok {
|
||||
result.OnSaleCount = onSaleCount
|
||||
} else {
|
||||
result.OnSaleCount = fmt.Sprintf("%v", data[2])
|
||||
}
|
||||
|
||||
// 解析平均价格 (索引3)
|
||||
if avgPrice, ok := data[3].(string); ok {
|
||||
result.AvgPrice = avgPrice
|
||||
} else {
|
||||
result.AvgPrice = fmt.Sprintf("%v", data[3])
|
||||
}
|
||||
|
||||
// 解析最后推送时间 (索引4)
|
||||
if lastPushTime, ok := data[4].(string); ok {
|
||||
result.LastPushTime = lastPushTime
|
||||
} else {
|
||||
result.LastPushTime = fmt.Sprintf("%v", data[4])
|
||||
}
|
||||
|
||||
// 解析来源 (索引5)
|
||||
if source, ok := data[5].(string); ok {
|
||||
result.Source = source
|
||||
} else {
|
||||
result.Source = fmt.Sprintf("%v", data[5])
|
||||
}
|
||||
|
||||
// 解析商品列表 (索引1)
|
||||
bookItems, err := parseBookItems(data[1])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("解析商品列表失败: %v", err)
|
||||
}
|
||||
result.Data = bookItems
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// parseBookItems 解析商品列表
|
||||
func parseBookItems(items interface{}) ([]BookItem, error) {
|
||||
// 将items转换为[]interface{}
|
||||
itemList, ok := items.([]interface{})
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("商品列表格式错误: 期望数组,得到 %T", items)
|
||||
}
|
||||
|
||||
bookItems := make([]BookItem, 0, len(itemList))
|
||||
|
||||
for i, item := range itemList {
|
||||
// 每个商品项应该是一个包含9个元素的数组
|
||||
bookData, ok := item.([]interface{})
|
||||
if !ok {
|
||||
log.Printf("第%d个商品格式错误: 期望数组,得到 %T", i, item)
|
||||
continue
|
||||
}
|
||||
|
||||
if len(bookData) < 9 {
|
||||
log.Printf("第%d个商品数据长度不足: 期望至少9个元素,实际 %d", i, len(bookData))
|
||||
continue
|
||||
}
|
||||
|
||||
bookItem := BookItem{}
|
||||
|
||||
// 解析价格 (索引0)
|
||||
if price, ok := bookData[0].(string); ok {
|
||||
bookItem.Price = price
|
||||
} else {
|
||||
bookItem.Price = fmt.Sprintf("%v", bookData[0])
|
||||
}
|
||||
|
||||
// 解析运费 (索引1)
|
||||
switch v := bookData[1].(type) {
|
||||
case float64:
|
||||
bookItem.ShippingFee = v
|
||||
case int:
|
||||
bookItem.ShippingFee = float64(v)
|
||||
case string:
|
||||
if f, err := strconv.ParseFloat(v, 64); err == nil {
|
||||
bookItem.ShippingFee = f
|
||||
}
|
||||
default:
|
||||
bookItem.ShippingFee = 0
|
||||
}
|
||||
|
||||
// 解析品相 (索引2)
|
||||
if condition, ok := bookData[2].(string); ok {
|
||||
bookItem.Condition = condition
|
||||
} else {
|
||||
bookItem.Condition = fmt.Sprintf("%v", bookData[2])
|
||||
}
|
||||
|
||||
// 解析图片URL (索引3)
|
||||
if imgUrl, ok := bookData[3].(string); ok {
|
||||
bookItem.ImgBigUrl = imgUrl
|
||||
} else {
|
||||
bookItem.ImgBigUrl = fmt.Sprintf("%v", bookData[3])
|
||||
}
|
||||
|
||||
// 解析平均发货时间 (索引4)
|
||||
if shippingTime, ok := bookData[4].(string); ok {
|
||||
bookItem.ShopAvgShippingTime = shippingTime
|
||||
} else {
|
||||
bookItem.ShopAvgShippingTime = fmt.Sprintf("%v", bookData[4])
|
||||
}
|
||||
|
||||
// 解析成功率 (索引5)
|
||||
if successRate, ok := bookData[5].(string); ok {
|
||||
bookItem.ShopSuccessOrderRate = successRate
|
||||
} else {
|
||||
bookItem.ShopSuccessOrderRate = fmt.Sprintf("%v", bookData[5])
|
||||
}
|
||||
|
||||
// 解析商品URL (索引6)
|
||||
if goodsUrl, ok := bookData[6].(string); ok {
|
||||
bookItem.GoodsUrl = goodsUrl
|
||||
} else {
|
||||
bookItem.GoodsUrl = fmt.Sprintf("%v", bookData[6])
|
||||
}
|
||||
|
||||
// 解析标题 (索引7)
|
||||
if title, ok := bookData[7].(string); ok {
|
||||
bookItem.Title = title
|
||||
} else {
|
||||
bookItem.Title = fmt.Sprintf("%v", bookData[7])
|
||||
}
|
||||
|
||||
// 解析图片数量 (索引8)
|
||||
switch v := bookData[8].(type) {
|
||||
case int:
|
||||
bookItem.GoodsImgCount = v
|
||||
case float64:
|
||||
bookItem.GoodsImgCount = int(v)
|
||||
case string:
|
||||
if count, err := strconv.Atoi(v); err == nil {
|
||||
bookItem.GoodsImgCount = count
|
||||
}
|
||||
default:
|
||||
bookItem.GoodsImgCount = 0
|
||||
}
|
||||
|
||||
bookItems = append(bookItems, bookItem)
|
||||
}
|
||||
|
||||
return bookItems, nil
|
||||
}
|
||||
|
||||
// GetValueFromDB1 当从DB2获取失败时,从DB1的cp85hash中查找对应的key并加1
|
||||
func GetValueFromDB1(redisClient *RedisClient, originalKey string) (string, error) {
|
||||
// 提取ISBN号(去掉"cp:"前缀)
|
||||
isbn := strings.TrimPrefix(originalKey, "cp:")
|
||||
|
||||
// 切换到DB1
|
||||
err := redisClient.SelectDB(1)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("切换到DB1失败: %v", err)
|
||||
}
|
||||
|
||||
// 从cp85hash中获取对应的值
|
||||
hashKey := "cp85"
|
||||
value, err := redisClient.HGet(hashKey, isbn)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("从cp85hash获取数据失败: %v", err)
|
||||
}
|
||||
|
||||
log.Println("cp85Isbn:", isbn, "value:", value)
|
||||
// 将值转换为整数并加1
|
||||
intValue, err := strconv.Atoi(value)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("值转换失败: %v", err)
|
||||
}
|
||||
|
||||
newValue := intValue + 1
|
||||
|
||||
// 更新hash中的值
|
||||
err = redisClient.HSet(hashKey, isbn, strconv.Itoa(newValue))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("更新cp85hash值失败: %v", err)
|
||||
}
|
||||
|
||||
log.Printf("成功更新DB1中cp85hash的键 %s,值从 %d 增加到 %d", isbn, intValue, newValue)
|
||||
|
||||
// 切换回DB2(保持一致性)
|
||||
err = redisClient.SelectDB(2)
|
||||
if err != nil {
|
||||
log.Printf("切换回DB2失败: %v", err)
|
||||
}
|
||||
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// 修改您的main函数
|
||||
func main() {
|
||||
// 自定义配置
|
||||
config := &RedisConfig{
|
||||
Addr: "36.212.20.113:7963",
|
||||
Password: "j8nZ4jra2E",
|
||||
DB: 2,
|
||||
PoolSize: 20,
|
||||
MinIdleConns: 10,
|
||||
DialTimeout: 5 * time.Second,
|
||||
ReadTimeout: 3 * time.Second,
|
||||
WriteTimeout: 3 * time.Second,
|
||||
PoolTimeout: 4 * time.Second,
|
||||
}
|
||||
|
||||
// 使用自定义配置
|
||||
redisClient := NewRedisClient(config)
|
||||
defer redisClient.Close()
|
||||
|
||||
// 字符串操作示例
|
||||
key := "cp:9787040110951"
|
||||
|
||||
// 获取字符串值
|
||||
val, err := redisClient.Get(key)
|
||||
if err != nil {
|
||||
log.Printf("从DB2获取失败: %v", err)
|
||||
|
||||
// 尝试从DB1的cp85hash中查找并加1
|
||||
newValue, db1Err := GetValueFromDB1(redisClient, key)
|
||||
if db1Err != nil {
|
||||
log.Printf("从DB1获取也失败: %v", db1Err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("从DB1的cp85hash中找到并更新值: %s\n", newValue)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("键 %s 的原始值: %s\n", key, val)
|
||||
|
||||
// 由于Redis返回的是JSON字符串,需要先解析
|
||||
var redisData interface{}
|
||||
err = json.Unmarshal([]byte(val), &redisData)
|
||||
if err != nil {
|
||||
log.Printf("JSON解析失败: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// 解析为结构体
|
||||
result, err := ParseRedisData(redisData)
|
||||
if err != nil {
|
||||
log.Printf("解析Redis数据失败: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// 输出解析后的结构体
|
||||
fmt.Println("\n=== 解析后的数据 ===")
|
||||
fmt.Printf("ISBN: %s\n", result.Isbn)
|
||||
fmt.Printf("在售数量: %s\n", result.OnSaleCount)
|
||||
fmt.Printf("平均价格: %s\n", result.AvgPrice)
|
||||
fmt.Printf("最后推送时间: %s\n", result.LastPushTime)
|
||||
fmt.Printf("来源: %s\n", result.Source)
|
||||
fmt.Printf("商品数量: %d\n", len(result.Data))
|
||||
|
||||
// 输出商品列表
|
||||
for i, item := range result.Data {
|
||||
fmt.Printf("\n--- 商品 %d ---\n", i+1)
|
||||
fmt.Printf(" 价格: %s\n", item.Price)
|
||||
fmt.Printf(" 运费: %.2f\n", item.ShippingFee)
|
||||
fmt.Printf(" 品相: %s\n", item.Condition)
|
||||
fmt.Printf(" 图片URL: %s\n", item.ImgBigUrl)
|
||||
fmt.Printf(" 平均发货时间: %s\n", item.ShopAvgShippingTime)
|
||||
fmt.Printf(" 成功率: %s\n", item.ShopSuccessOrderRate)
|
||||
fmt.Printf(" 商品URL: %s\n", item.GoodsUrl)
|
||||
fmt.Printf(" 标题: %s\n", item.Title)
|
||||
fmt.Printf(" 图片数量: %d\n", item.GoodsImgCount)
|
||||
}
|
||||
|
||||
// 如果需要转换为JSON
|
||||
jsonResult, err := json.MarshalIndent(result, "", " ")
|
||||
if err != nil {
|
||||
log.Printf("JSON序列化失败: %v", err)
|
||||
} else {
|
||||
fmt.Println("\n=== JSON格式输出 ===")
|
||||
fmt.Println(string(jsonResult))
|
||||
}
|
||||
}
|
||||
1
version.yaml
Normal file
1
version.yaml
Normal file
@ -0,0 +1 @@
|
||||
verifyPriceLatestVersion: v1.0
|
||||
Loading…
Reference in New Issue
Block a user