feat(supplier): 新增核销卡发送任务类型及优化签名生成逻辑

- 为 `aibo.go` 添加 `generateSign` 方法用于生成 MD5 签名,并替换原有全局函数调用方式
- 引入 `sort` 和 `maputil` 包以支持参数排序和键提取
- 在多个文件中将 `fakeuseragent.RandomUserAgent()` 替换为 `useragent.GetUserAgentByPlatform(useragent.PlatformPhone)` 以统一 User-Agent 生成策略
- 新增 `SendCardTaskTypeEnumNuclear` 类型及其完整实现,包括订单创建、验证码识别、支付提交等流程
-为部分 HTTP 请求显式添加 `SetContext(ctx)`以确保上下文传递正确
- 更新依赖版本,包括 go version、golang.org/x/text 及新增 golang.org/x/image 等模块- 增加指纹生成测试用例和工具函数
```
This commit is contained in:
danial
2025-09-22 18:23:49 +08:00
parent a373715936
commit 1eca3a085a
37 changed files with 3129 additions and 69 deletions

View File

@@ -0,0 +1,12 @@
{
"permissions": {
"allow": [
"Bash(go build:*)",
"Bash(go test:*)",
"Bash(go run:*)",
"Bash(timeout:*)"
],
"deny": [],
"ask": []
}
}

8
go.mod
View File

@@ -1,8 +1,8 @@
module gateway
go 1.24
go 1.24.0
toolchain go1.24.3
toolchain go1.24.6
require github.com/beego/beego/v2 v2.3.8
@@ -17,6 +17,7 @@ require (
github.com/go-resty/resty/v2 v2.16.5
github.com/go-sql-driver/mysql v1.9.3
github.com/go-stomp/stomp/v3 v3.1.3
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0
github.com/google/uuid v1.6.0
github.com/iunary/fakeuseragent v1.0.0
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826
@@ -41,6 +42,7 @@ require (
go.opentelemetry.io/otel/trace v1.38.0
go.uber.org/zap v1.27.0
golang.org/x/exp v0.0.0-20250718183923-645b1fa84792
golang.org/x/image v0.31.0
)
require (
@@ -80,7 +82,7 @@ require (
golang.org/x/crypto v0.41.0 // indirect
golang.org/x/net v0.43.0 // indirect
golang.org/x/sys v0.35.0 // indirect
golang.org/x/text v0.28.0 // indirect
golang.org/x/text v0.29.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5 // indirect
google.golang.org/grpc v1.75.0 // indirect

8
go.sum
View File

@@ -55,6 +55,8 @@ github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1
github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
github.com/go-stomp/stomp/v3 v3.1.3 h1:5/wi+bI38O1Qkf2cc7Gjlw7N5beHMWB/BxpX+4p/MGI=
github.com/go-stomp/stomp/v3 v3.1.3/go.mod h1:ztzZej6T2W4Y6FlD+Tb5n7HQP3/O5UNQiuC169pIp10=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
@@ -189,6 +191,8 @@ golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
golang.org/x/exp v0.0.0-20250718183923-645b1fa84792 h1:R9PFI6EUdfVKgwKjZef7QIwGcBKu86OEFpJ9nUEP2l4=
golang.org/x/exp v0.0.0-20250718183923-645b1fa84792/go.mod h1:A+z0yzpGtvnG90cToK5n2tu8UJVP2XUATh+r+sfOOOc=
golang.org/x/image v0.31.0 h1:mLChjE2MV6g1S7oqbXC0/UcKijjm5fnJLUYKIYrLESA=
golang.org/x/image v0.31.0/go.mod h1:R9ec5Lcp96v9FTF+ajwaH3uGxPH4fKfHHAVbUILxghA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
@@ -250,8 +254,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.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U=
golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=

View File

@@ -81,7 +81,7 @@ func (r *RedisClient) GetSize(ctx context.Context, key string) (int64, error) {
return r.Client.DBSize(ctx).Result()
}
// 获取以 prefix 为前缀的键值数量
// GetSizeByPrefix 获取以 prefix 为前缀的键值数量
func (r *RedisClient) GetSizeByPrefix(ctx context.Context, prefix string) (int64, error) {
keys, err := r.Client.Keys(ctx, prefix+"*").Result()
if err != nil {
@@ -253,7 +253,7 @@ func (r *RedisClient) XDel(ctx context.Context, key string, ids ...string) (int6
return r.Client.XDel(ctx, key, ids...).Result()
}
// XRead 自动反序列化到 map[string]interface{}
// XReadUnmarshal 自动反序列化到 map[string]interface{}
func (r *RedisClient) XReadUnmarshal(ctx context.Context, key, start string, count int64, block time.Duration) ([]map[string]interface{}, error) {
args := &redis.XReadArgs{
Streams: []string{key, start},
@@ -279,8 +279,8 @@ func (r *RedisClient) XReadUnmarshal(ctx context.Context, key, start string, cou
// XRange 区间查询 Stream
func (r *RedisClient) XRange(ctx context.Context, key, start, end string, count int64) ([]redis.XMessage, error) {
msgs, err := r.Client.XRangeN(ctx, key, start, end, count).Result()
return msgs, err
msg, err := r.Client.XRangeN(ctx, key, start, end, count).Result()
return msg, err
}
// XLen 获取 Stream 长度

View File

@@ -39,7 +39,7 @@ func (c *OCRClient) Calc(ctx context.Context, input *CalcInput) (string, error)
webClient := resty.New()
otelresty.TraceClient(webClient)
resp, err := webClient.R().SetFormData(map[string]string{
resp, err := webClient.R().SetContext(ctx).SetFormData(map[string]string{
"category": input.Category,
"length": fmt.Sprintf("%d", input.Length),
}).SetFileReader("file", input.FileName, bytes.NewReader(input.File)).

View File

@@ -68,7 +68,7 @@ func SendOrderNotify(ctx context.Context, bankOrderId string) {
webClient := resty.New().SetTimeout(time.Second * 5).SetRetryCount(3).SetRetryMaxWaitTime(5 * time.Second)
otelresty.TraceClient(webClient)
response, err := webClient.R().Get(notifyInfo.Url)
response, err := webClient.R().SetContext(ctx).Get(notifyInfo.Url)
if err == nil {
otelTrace.Logger.WithContext(ctx).Info("回调结果", zap.String("bankOrderId", bankOrderId), zap.String("response", response.String()))
}

View File

@@ -203,7 +203,9 @@ func SolvePayFail(ctx context.Context, bankOrderId, transId string, cardReturnDa
attribute.String("cardReturnData", cardReturnData),
))
defer cancel()
if cardReturnData == "" {
cardReturnData = "核销失败"
}
o := orm.NewOrm()
err := o.DoTxWithCtx(ctx, func(ctx context.Context, txOrm orm.TxOrmer) error {
var orderTmp order.OrderInfo

View File

@@ -20,6 +20,7 @@ import (
"gateway/internal/utils"
"net/http"
"net/url"
"sort"
"strconv"
"time"
@@ -28,6 +29,7 @@ import (
"github.com/bytedance/sonic"
"github.com/duke-git/lancet/v2/convertor"
"github.com/duke-git/lancet/v2/cryptor"
"github.com/duke-git/lancet/v2/maputil"
"github.com/widuu/gojson"
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
"go.opentelemetry.io/otel/attribute"
@@ -44,6 +46,20 @@ func (c *AiboCardImpl) HasDependencyHTML() bool {
return false
}
// 生成md5加密
func (c *AiboCardImpl) generateSign(ctx context.Context, params map[string]any, key string) string {
keys := maputil.Keys(params)
sort.Strings(keys)
sign_ := ""
for _, key2 := range keys {
if params[key2] == "" {
continue
}
sign_ += key2 + "=" + convertor.ToString(params[key2]) + "&"
}
sign_ += "key=" + key
return utils.EncodeMd5Str(sign_)
}
func (c *AiboCardImpl) SendCard(ctx context.Context, jsonStr string, cardInfo supplier.RedeemCardInfo, attach string) (bool, string) {
// 构建数据结构
cfg := config.Config{}
@@ -72,7 +88,7 @@ func (c *AiboCardImpl) SendCard(ctx context.Context, jsonStr string, cardInfo su
"timestamp": fmt.Sprintf("%d", time.Now().Unix()),
"sign": "",
}
data["sign"] = generateSign(ctx, data, gojson.Json(jsonStr).Get("key").Tostring())
data["sign"] = c.generateSign(ctx, data, gojson.Json(jsonStr).Get("key").Tostring())
otelTrace.Logger.WithContext(ctx).Info("请求sign", zap.Any("sign", data["sign"]), zap.Any("params", data))
req, _ := httplib.NewBeegoRequestWithCtx(ctx, "http://aibomoyi.cn/index.php/api/api/sendcard", "POST").
SetTransport(otelhttp.NewTransport(http.DefaultTransport)).
@@ -207,7 +223,7 @@ func (c *AiboCardImpl) PayNotify() {
c.Ctx.WriteString("FAIL")
return
}
tmpSign := generateSign(ctx, params, gojson.Json(roadInfo.Params).Get("key").Tostring())
tmpSign := c.generateSign(ctx, params, gojson.Json(roadInfo.Params).Get("key").Tostring())
if tmpSign != c.GetString("sign") {
otelTrace.Logger.WithContext(ctx).Error("【爱博】回调验签失败,订单号=",
zap.String("attach", convertor.ToString(params["orderId"])),

View File

@@ -8,6 +8,7 @@ import (
"gateway/internal/otelTrace"
"gateway/internal/service/client"
"gateway/internal/utils"
"gateway/internal/utils/useragent"
"math/rand/v2"
"net/http"
"net/url"
@@ -19,7 +20,6 @@ import (
"github.com/dubonzi/otelresty"
"github.com/duke-git/lancet/v2/convertor"
"github.com/go-resty/resty/v2"
"github.com/iunary/fakeuseragent"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace"
"go.uber.org/zap"
@@ -134,7 +134,7 @@ func (c *cardTypeQuery) xinTianQuery(ctx context.Context) (balance float64, err
}
func (c *cardTypeQuery) outletCardQuery(ctx context.Context) (balance float64, err error) {
ua := fakeuseragent.RandomUserAgent()
ua := useragent.GetUserAgentByPlatform(useragent.PlatformPhone)
webClient := resty.New().EnableTrace().SetTimeout(5 * time.Second).SetRetryCount(3).SetHeaders(map[string]string{
"user-agent": ua,
}).SetHeaders(map[string]string{
@@ -162,7 +162,7 @@ func (c *cardTypeQuery) outletCardQuery(ctx context.Context) (balance float64, e
if len(ocrResp) != 5 {
continue
}
resp, err = webClient.R().SetFormData(map[string]string{
resp, err = webClient.R().SetContext(ctx).SetFormData(map[string]string{
"card": c.CardNo,
"pass": c.CardPwd,
"code": ocrResp,
@@ -370,7 +370,7 @@ func (c *cardTypeQuery) chinagPayQuery(ctx context.Context) (balance float64, er
if proxy != "" {
webClient.SetProxy(proxy)
}
captcha, err2 := webClient.R().Get("https://gpaymp.chinagpay.com/api/noAuth/card/getCaptcha?" + strconv.FormatInt(time.Now().UnixMicro(), 10))
captcha, err2 := webClient.R().SetContext(ctx).Get("https://gpaymp.chinagpay.com/api/noAuth/card/getCaptcha?" + strconv.FormatInt(time.Now().UnixMicro(), 10))
if err2 != nil || captcha.IsError() {
proxyId = utils.GenerateId()
continue

View File

@@ -69,7 +69,7 @@ func TestFatSixImpl_Scan(t *testing.T) {
}
func TestFatSixImpl_SendCard(t *testing.T) {
ua := fakeuseragent.RandomUserAgent()
ua := useragent.GetUserAgentByPlatform(useragent.PlatformPhone)
randStrList := random.RandStringSlice(random.Numeral+random.LowwerLetters, 1, 6)
client := resty.New().SetHeaders(map[string]string{
"user-agent": ua,

View File

@@ -0,0 +1,469 @@
package third_party
import (
"context"
"crypto/aes"
"crypto/cipher"
"encoding/base64"
"encoding/json"
"fmt"
"gateway/internal/config"
"gateway/internal/models/merchant"
"gateway/internal/models/merchant_deploy"
"gateway/internal/models/order"
"gateway/internal/models/payfor"
"gateway/internal/models/road"
"gateway/internal/models/supply_model"
"gateway/internal/otelTrace"
"gateway/internal/service"
"gateway/internal/service/supplier"
"gateway/internal/service/supplier/third_party/pool/card_sender"
"gateway/internal/utils"
"sort"
"strconv"
"strings"
"time"
"github.com/beego/beego/v2/client/httplib"
"github.com/beego/beego/v2/server/web"
"github.com/bytedance/sonic"
"github.com/duke-git/lancet/v2/convertor"
"github.com/duke-git/lancet/v2/maputil"
"github.com/widuu/gojson"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace"
"go.uber.org/zap"
)
type NuclearImpl struct {
web.Controller
}
// HasDependencyHTML 是否有单独的支付页面
func (c *NuclearImpl) HasDependencyHTML() bool {
return false
}
// 生成md5加密
func (c *NuclearImpl) generateSign(ctx context.Context, params map[string]string, key string) string {
keys := maputil.Keys(params)
sort.Strings(keys)
sign_ := ""
for _, key2 := range keys {
if params[key2] == "" || key2 == "SignType" {
continue
}
sign_ += key2 + "=" + convertor.ToString(params[key2]) + "&"
}
sign_ = strings.Trim(sign_, "&")
sign_ += key
return utils.EncodeMd5Str(sign_)
}
func (c *NuclearImpl) sendCard(ctx context.Context, jsonStr string, cardInfo supplier.RedeemCardInfo, attach string, road road.RoadInfo, merchantInfo merchant.MerchantInfo, roadInfo road.RoadInfo) (bool, string) {
orderAmount, _ := (&cardTypeQuery{
OrderNo: attach,
QueryType: gojson.Json(roadInfo.Params).Get("queryType").Tostring(),
CardNo: cardInfo.CardNo,
ChannelCode: gojson.Json(roadInfo.Params).Get("channelCode").Tostring(),
CardPwd: cardInfo.Data,
Balance: cardInfo.GetFaceTypeFloat(ctx),
}).GetBalance(ctx)
cardInfo.FaceType = strconv.FormatFloat(orderAmount, 'f', 2, 64)
merchantDeployInfo := merchant_deploy.GetMerchantDeployByUidAndRoadUid(ctx, merchantInfo.MerchantUid, road.RoadUid)
if merchantDeployInfo == nil {
return false, "商户配置不存在"
}
if merchantDeployInfo.SubmitStrategy == merchant_deploy.SUBMIT_STRATEGY_POOL {
if err := orderPoolService.PushOrder(ctx, card_sender.SendCardTask{
CardInfo: cardInfo,
RoadUid: road.RoadUid,
LocalOrderID: attach,
SendCardTaskType: card_sender.SendCardTaskTypeEnumNuclear,
NeedQuery: gojson.Json(road.Params).Get("needQuery").Tostring() == "1",
}); err != nil {
otelTrace.Logger.WithContext(ctx).Error("推送订单失败", zap.Error(err))
return false, err.Error()
}
return true, "订单提交中"
}
if err := orderPoolService.SubmitOrder(ctx, card_sender.SendCardTask{
CardInfo: cardInfo,
RoadUid: road.RoadUid,
LocalOrderID: attach,
SendCardTaskType: card_sender.SendCardTaskTypeEnumNuclear,
NeedQuery: gojson.Json(road.Params).Get("needQuery").Tostring() == "1",
}); err != nil {
return false, err.Error()
}
return true, "订单提交中"
}
func (c *NuclearImpl) Scan(ctx context.Context, orderInfo order.OrderInfo, roadInfo road.RoadInfo, merchantInfo merchant.MerchantInfo) supplier.ScanData {
ctx, cancel := otelTrace.SpanFunc(ctx, "qixi", "Scan", trace.WithAttributes(
attribute.String("bankOrderId", orderInfo.BankOrderId),
attribute.String("merchantUid", orderInfo.MerchantUid),
attribute.String("exValue", orderInfo.ExValue),
))
defer cancel()
cdata := supplier.RedeemCardInfo{}
err := json.Unmarshal([]byte(orderInfo.ExValue), &cdata)
if err != nil {
otelTrace.Logger.WithContext(ctx).Error("格式化数据失败", zap.String("ExValue", orderInfo.ExValue))
return supplier.ScanData{Status: "-1", Msg: "订单有误,请稍后再试"}
}
ok, str := c.sendCard(ctx, roadInfo.Params, cdata, orderInfo.BankOrderId, roadInfo, merchantInfo, roadInfo)
var scanData supplier.ScanData
if !ok {
scanData = supplier.ScanData{
Status: "-1",
Msg: "订单有误,请稍后再试:" + str,
BankNo: orderInfo.MerchantOrderId,
OrderNo: orderInfo.BankOrderId,
ReturnData: str,
}
return scanData
}
scanData.Status = "00"
scanData.OrderNo = orderInfo.BankOrderId
scanData.BankNo = orderInfo.MerchantOrderId
scanData.OrderPrice = strconv.FormatFloat(orderInfo.OrderAmount, 'f', 2, 64)
scanData.ReturnData = str
return scanData
}
// KMEncrypt 加密卡密
func (c *NuclearImpl) kMEncrypt(kf, appSecret string) (string, error) {
ctx := context.Background()
secret := utils.GetMd5Lower(appSecret)[:16] // 加密秘钥
block, err := aes.NewCipher([]byte(secret))
if err != nil {
otelTrace.Logger.WithContext(ctx).Error("nuclear: AesDecrypt failed to NewCipher")
return "", err
}
// 数据填充
plaintext := utils.PadType(kf)
iv := "0102030405060708"
mode := cipher.NewCBCEncrypter(block, []byte(iv))
mode.CryptBlocks(plaintext, plaintext)
return base64.StdEncoding.EncodeToString(plaintext), nil
}
func (c *NuclearImpl) PayNotify() {
ctx := c.Ctx.Request.Context()
ctx, span := otelTrace.Span(ctx, "NuclearImpl", "NuclearImpl.PayNotify")
defer span.End()
response := struct {
AppId string `json:"AppId" form:"AppId"`
OrderId string `json:"OrderId" form:"OrderId"`
Status string `json:"Status" form:"Status"`
Amount string `json:"Amount" form:"Amount"`
ActualAmount string `json:"ActualAmount" form:"ActualAmount"`
SettleAmount string `json:"SettleAmount" form:"SettleAmount"`
TradeOrderId string `json:"TradeOrderId" form:"TradeOrderId"`
CompleteTime string `json:"CompleteTime" form:"CompleteTime"`
Attach string `json:"Attach" form:"Attach"`
Msg string `json:"Msg" form:"Msg"`
Sign string `json:"Sign" form:"Sign"`
SignType string `json:"SignType" form:"SignType"`
}{}
if err := c.Bind(&response); err != nil {
otelTrace.Logger.WithContext(ctx).Error("【nuclear】回调参数解析失败", zap.Error(err))
c.Ctx.WriteString("FAIL")
return
}
span.SetAttributes(
attribute.String("bankOrderId", convertor.ToString(response.OrderId)),
attribute.String("formData", convertor.ToString(response)),
)
//通form表单里提取数据
orderInfo := order.GetOrderByBankOrderId(ctx, convertor.ToString(response.OrderId))
if orderInfo.BankOrderId == "" || len(orderInfo.BankOrderId) == 0 {
otelTrace.Logger.WithContext(ctx).Error("【nuclear】回调的订单号不存在订单号=", zap.String("attach", convertor.ToString(response.OrderId)))
c.Ctx.WriteString("FAIL")
return
}
if orderInfo.Status != "wait" {
c.Ctx.WriteString("订单已经回调")
return
}
roadInfo := road.GetRoadInfoByRoadUid(ctx, orderInfo.RoadUid)
if roadInfo.RoadUid == "" || len(roadInfo.RoadUid) == 0 {
otelTrace.Logger.WithContext(ctx).Error("【nuclear】支付通道已经关系或者删除不进行回调")
c.Ctx.WriteString("FAIL")
return
}
if response.Status == "Success" {
successAmount, _ := strconv.ParseFloat(response.ActualAmount, 64)
isOk := false
if successAmount == orderInfo.OrderAmount {
isOk = service.SolvePaySuccess(ctx, orderInfo.BankOrderId, successAmount, orderInfo.MerchantUid, response.Msg)
} else {
isOk = service.SolvePayFail(ctx, orderInfo.BankOrderId, response.TradeOrderId, fmt.Sprintf("金额不一致 卡面金额:%.2f 订单金额:%.2f", orderInfo.OrderAmount, successAmount))
}
if isOk {
c.Ctx.WriteString("success")
} else {
c.Ctx.WriteString("FAIL")
}
} else {
isOk := service.SolvePayFail(ctx, orderInfo.BankOrderId, response.TradeOrderId, response.Msg)
if isOk {
c.Ctx.WriteString("success")
} else {
c.Ctx.WriteString("FAIL")
}
}
return
}
// 查询订单
func (c *NuclearImpl) bindSearch(ctx context.Context, orderNo string) bool {
req := httplib.NewBeegoRequestWithCtx(ctx, "http://api.qixika.com/blindSearch.html", "POST")
req.Param("orderId", orderNo)
type bindSearchResp struct {
Code string `json:"code"`
Message string `json:"message"`
Data struct {
CustomerId int `json:"customerId"`
OrderId string `json:"orderId"`
SystemOrderId string `json:"systemOrderId"`
Message string `json:"message"`
Status string `json:"status"`
} `json:"data"`
}
var resp bindSearchResp
err := req.ToJSON(&resp)
if err != nil {
otelTrace.Logger.WithContext(ctx).Error("【nuclear】查询订单失败", zap.Error(err))
return false
}
otelTrace.Logger.WithContext(ctx).Info("【nuclear】查询订单返回", zap.Any("resp", resp))
return resp.Data.Status == "0"
}
func (c *NuclearImpl) PayQuery(orderInfo order.OrderInfo, roadInfo road.RoadInfo) bool {
params := map[string]any{}
ctx := context.Background()
cardData, err := sonic.GetFromString(orderInfo.CardReturnData)
if err != nil {
return false
}
orderId, err := cardData.Get("order_id").String()
if err != nil {
return false
}
params["order_id"] = orderId
params["app_key"] = gojson.Json(roadInfo.Params).Get("appKey").Tostring()
params["timestamp"] = strconv.FormatInt(time.Now().Unix(), 10)
params["sign"] = utils.GetMD5SignMF(params, gojson.Json(roadInfo.Params).Get("appSecret").Tostring())
cfg := config.Config{}
url, err := cfg.GetMFCardQueryUrl()
req := httplib.Post(url)
marshal, err := json.Marshal(params)
if err != nil {
otelTrace.Logger.WithContext(ctx).Error("Map转化为byte数组失败,异常。", zap.Error(err))
// fmt.Printf("Map转化为byte数组失败,异常:%s\n",zap.Error(err))
return false
}
otelTrace.Logger.WithContext(ctx).Info("请求参数:" + string(marshal))
req.Header("Content-Type", "application/json")
req.Body(marshal)
req.Header("Accept-Charset", "utf-8")
response, err := req.String()
if err != nil {
otelTrace.Logger.WithContext(ctx).Error("MF GetToken 请求失败:", zap.Error(err))
return false
}
otelTrace.Logger.WithContext(ctx).Info("远端请求返回数据:" + response)
resData, err := sonic.GetFromString(response)
if err != nil {
return false
}
resStatus, err := resData.Get("data").Get("status").Int64()
if err != nil {
return false
}
resCode, err := resData.Get("code").Int64()
if err != nil {
return false
}
if resCode == 0 && resStatus == 9 {
return true
}
return false
}
func (c *NuclearImpl) PayQueryV2(orderInfo order.OrderInfo, roadInfo road.RoadInfo) supply_model.MsgModel {
params := map[string]any{}
ctx := context.Background()
cardData, err := sonic.GetFromString(orderInfo.CardReturnData)
if err != nil {
return supply_model.CardMsgErr
}
orderId, err := cardData.Get("order_id").String()
if err != nil {
return supply_model.CardMsgErr
}
params["order_id"] = orderId
params["app_key"] = gojson.Json(roadInfo.Params).Get("appKey").Tostring()
params["timestamp"] = strconv.FormatInt(time.Now().Unix(), 10)
params["sign"] = utils.GetMD5SignMF(params, gojson.Json(roadInfo.Params).Get("appSecret").Tostring())
cfg := config.Config{}
url, err := cfg.GetMFCardQueryUrl()
req := httplib.Post(url)
marshal, err := json.Marshal(params)
if err != nil {
otelTrace.Logger.WithContext(ctx).Error("Map转化为byte数组失败,异常。", zap.Error(err))
// fmt.Printf("Map转化为byte数组失败,异常:%s\n",zap.Error(err))
return supply_model.DataErr
}
otelTrace.Logger.WithContext(ctx).Info("请求参数:" + string(marshal))
req.Header("Content-Type", "application/json")
req.Body(marshal)
req.Header("Accept-Charset", "utf-8")
response, err := req.String()
if err != nil {
return supply_model.RemoteDataErr
}
otelTrace.Logger.WithContext(ctx).Info("远端请求返回数据:" + response)
resData, err := sonic.GetFromString(response)
if err != nil {
return supply_model.RemoteDataErr
}
resStatus, err := resData.Get("data").Get("status").Int64()
if err != nil {
return supply_model.RemoteDataErr
}
resCode, err := resData.Get("code").Int64()
if err != nil {
return supply_model.RemoteDataErr
}
if resCode == 0 {
switch resStatus {
case 9:
return supply_model.RemoteSuccess
case 2, 3, 4:
return supply_model.RemoteDataDealing
case 7:
return supply_model.RemoteDataHandErr
case 8:
return supply_model.RemoteDataHealingErr
}
}
return supply_model.RemoteDataErr
}
func (c *NuclearImpl) PayFor(info payfor.PayforInfo) string {
return ""
}
func (c *NuclearImpl) PayForQuery(payFor payfor.PayforInfo) (string, string) {
cfg := config.Config{}
url, err := cfg.GetMFCardQueryUrl()
ctx := context.Background()
if err != nil {
return config.PAYFOR_FAIL, ""
}
params := map[string]string{}
params["order_id"] = payFor.BankOrderId
params["app_key"] = gojson.Json("").Get("appKey").Tostring()
req := httplib.Post(url)
marshal, err := json.Marshal(params)
if err != nil {
otelTrace.Logger.WithContext(ctx).Error("Map转化为byte数组失败,异常。", zap.Error(err))
// fmt.Printf("Map转化为byte数组失败,异常:%s\n",zap.Error(err))
return config.PAYFOR_FAIL, "内部错误请稍后再试试"
}
otelTrace.Logger.WithContext(ctx).Info("请求参数:" + string(marshal))
req.Header("Content-Type", "application/json")
req.Body(marshal)
req.Header("Accept-Charset", "utf-8")
response, err := req.String()
if err != nil {
otelTrace.Logger.WithContext(ctx).Error("MF GetToken 请求失败:", zap.Error(err))
return config.PAYFOR_FAIL, ""
}
otelTrace.Logger.WithContext(ctx).Info("远端请求返回数据:" + response)
if gojson.Json(response).Get("code").Tostring() == "" {
otelTrace.Logger.WithContext(ctx).Error("远程调用失败")
return config.PAYFOR_BANKING, ""
}
if gojson.Json(response).Get("code").Tostring() == "0" {
type data struct {
OrderID int64 `json:"order_id"`
CardNo string `json:"card_no"`
CardPwd string `json:"card_pwd"`
Status int `json:"status"`
RspInfo string `json:"rsp_info"`
FaceVal int `json:"face_val"`
Amount int `json:"amount"`
Discount string `json:"discount"`
}
var d data
err2 := json.Unmarshal([]byte(gojson.Json(response).Get("data").Tostring()), &d)
if err2 != nil {
return config.PAYFOR_FAIL, ""
}
if d.Status == 9 {
return config.PAYFOR_SUCCESS, ""
}
if d.Status == 4 {
return config.PAYFOR_BANKING, ""
}
if d.Status == 7 || d.Status == 8 {
return config.PAYFOR_FAIL, ""
}
}
otelTrace.Logger.WithContext(ctx).Error("远程调用失败")
return config.PAYFOR_BANKING, ""
}
func (c *NuclearImpl) BalanceQuery(roadInfo road.RoadInfo) float64 {
return 0.00
}
func (c *NuclearImpl) PayForNotify() string {
return ""
}

View File

@@ -0,0 +1,46 @@
package third_party
import (
"encoding/json"
"gateway/internal/otelTrace"
"gateway/internal/utils"
"testing"
"github.com/go-resty/resty/v2"
"go.uber.org/zap"
)
func TestNuclearImpl_sendCard(t *testing.T) {
params := map[string]string{
"AppId": "20099032",
"ChannelCode": "138",
"Amount": "10",
"OrderId": utils.GenerateId(),
"NonceStr": utils.GenerateId(),
"NotifyUrl": "https://baidu.com",
"ClientId": "12353543534",
"SignType": "MD5",
"Sign": "",
}
params["Sign"] = (&NuclearImpl{}).generateSign(t.Context(), params, "55j2P5sE6eGJk83OZWk9jdGs4auOgMO0")
otelTrace.Logger.WithContext(t.Context()).Info("构造sign参数", zap.Any("sign", params))
webClient := resty.New()
respData := struct {
Code string `json:"Code"`
Payload struct {
OrderId string `json:"OrderId"`
TradeOrderId string `json:"TradeOrderId"`
Amount float64 `json:"Amount"`
AppId string `json:"AppId"`
BankId int `json:"BankId"`
Data string `json:"Data"`
DataType int `json:"DataType"`
}
}{}
response, _ := webClient.R().SetFormData(params).Post("https://gate.nuclearpow.net:2087/api/order/create")
_ = json.Unmarshal(response.Body(), &respData)
otelTrace.Logger.WithContext(t.Context()).Info("请求结果", zap.Any("result", respData))
t.Log(response.String())
}

View File

@@ -73,7 +73,7 @@ func (s *SendCardTaskTypeCareless) CreateOrder(ctx context.Context, roadUid stri
if createdOrderUrl == "" {
createdOrderUrl = "https://api.wuxinpay.xyz/api/v1/payment/init"
}
response, err := client.R().SetBody(reqStruct).Post(createdOrderUrl)
response, err := client.R().SetContext(ctx).SetBody(reqStruct).Post(createdOrderUrl)
if err != nil {
return orderPoolItem, errors.New("请求失败")
}

View File

@@ -373,7 +373,7 @@ func (c *eggplantProductCode) channelThree(ctx context.Context, forwardUrl *url.
func (c *eggplantProductCode) channelFour(ctx context.Context, forwardUrl *url.URL) (bool, string, string) {
webClient := resty.New()
otelresty.TraceClient(webClient)
queryResp, err := webClient.R().EnableTrace().SetContext(ctx).Get(forwardUrl.String())
queryResp, err := webClient.R().SetContext(ctx).EnableTrace().SetContext(ctx).Get(forwardUrl.String())
if err != nil {
otelTrace.Logger.WithContext(ctx).Error("请求失败", zap.Error(err))

View File

@@ -43,6 +43,7 @@ const (
SendCardTaskTypeEnumWTR SendCardTaskEnum = "WTR"
SendCardTaskTypeEnumUp SendCardTaskEnum = "UP"
SendCardTaskTypeEnumOct SendCardTaskEnum = "OCT"
SendCardTaskTypeEnumNuclear SendCardTaskEnum = "NUCLEAR"
)
func GetAllSendCardTaskType() []SendCardTaskEnum {
@@ -65,6 +66,7 @@ func GetAllSendCardTaskType() []SendCardTaskEnum {
SendCardTaskTypeEnumWTR,
SendCardTaskTypeEnumUp,
SendCardTaskTypeEnumOct,
SendCardTaskTypeEnumNuclear,
}
}
@@ -108,6 +110,8 @@ func (sendCardTaskTypeEnum SendCardTaskEnum) GetSendCardTaskType() sendCardTaskT
return &SendCardTaskTypeWtr{}
case SendCardTaskTypeEnumUp:
return &SendCardTaskTypeUp{}
case SendCardTaskTypeEnumNuclear:
return &SendCardTaskTypeNuclear{}
}
return nil
}

View File

@@ -10,6 +10,7 @@ import (
"gateway/internal/otelTrace"
"gateway/internal/service/client"
"gateway/internal/utils"
"gateway/internal/utils/useragent"
"net/http"
"net/url"
"slices"
@@ -26,7 +27,6 @@ import (
"github.com/duke-git/lancet/v2/random"
"github.com/duke-git/lancet/v2/structs"
"github.com/go-resty/resty/v2"
"github.com/iunary/fakeuseragent"
"github.com/widuu/gojson"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace"
@@ -107,10 +107,10 @@ func (s *SendCardTaskTypeFatSix) CreateOrder(ctx context.Context, roadUid string
return orderPoolItem, err
}
//创建session
webClient := resty.New().EnableTrace().SetTimeout(3 * time.Second).SetHeaders(map[string]string{
webClient := resty.New().SetTimeout(3 * time.Second).SetHeaders(map[string]string{
"Origin": "https://api.xxsbm.com",
}).SetHeaders(map[string]string{
"user-agent": fakeuseragent.RandomUserAgent(),
"user-agent": useragent.GetUserAgentByPlatform(useragent.PlatformPhone),
})
otelresty.TraceClient(webClient)
@@ -174,7 +174,7 @@ func (s *SendCardTaskTypeFatSix) HandleSendCardTask(ctx context.Context, orderIt
needChangeProxyId := utils.GenerateId()
webClient := resty.New().EnableTrace().SetRetryCount(1).
SetTimeout(5 * time.Second).SetHeaders(map[string]string{
"user-agent": fakeuseragent.RandomUserAgent(),
"user-agent": useragent.GetUserAgentByPlatform(useragent.PlatformPhone),
}).SetHeaders(map[string]string{
"Origin": "https://api.xxsbm.com",
})

View File

@@ -9,6 +9,7 @@ import (
"gateway/internal/models/road"
"gateway/internal/otelTrace"
"gateway/internal/utils"
"gateway/internal/utils/useragent"
"net/http"
"net/url"
"regexp"
@@ -21,7 +22,6 @@ import (
"github.com/duke-git/lancet/v2/maputil"
"github.com/duke-git/lancet/v2/pointer"
"github.com/duke-git/lancet/v2/random"
"github.com/iunary/fakeuseragent"
"github.com/widuu/gojson"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace"
@@ -104,7 +104,7 @@ func (s *SendCardTaskTypeFlyFish) CreateOrder(ctx context.Context, roadUid strin
}
// 随机,点击链接
randomAgent := fakeuseragent.RandomUserAgent()
randomAgent := useragent.GetUserAgentByPlatform(useragent.PlatformPhone)
if random.RandInt(1, 100) <= 30 {
// 解析url
payUrl, err := url.Parse(respData.Data.Url)
@@ -204,7 +204,7 @@ func (s *SendCardTaskTypeFlyFish) HandleSendCardTask(ctx context.Context, orderI
randomAgent, ok := orderItem.Params["random_agent"]
randomAgentStr, ok2 := randomAgent.(string)
if !ok2 || !ok || randomAgentStr == "" {
randomAgentStr = fakeuseragent.RandomUserAgent()
randomAgentStr = useragent.GetUserAgentByPlatform(useragent.PlatformPhone)
_ = s.ClickLink(ctx, paths[len(paths)-1], randomAgentStr, task.LocalOrderID)
}

View File

@@ -48,7 +48,7 @@ func (s *SendCardTaskTypeFlyFishV2) CreateOrder(ctx context.Context, roadUid str
SetHeader("Accept", "application/json").SetTimeout(time.Second * 5).SetRetryCount(1)
otelresty.TraceClient(webClient)
resp, err := webClient.R().SetFormData(formData).Post("https://apify.fkpay.online/submitPay.html")
resp, err := webClient.R().SetContext(ctx).SetFormData(formData).Post("https://apify.fkpay.online/submitPay.html")
if err != nil {
return OrderPoolItem{}, errors.Join(err, errors.New("飞鱼下单请求失败"))
}
@@ -115,7 +115,7 @@ func (s *SendCardTaskTypeFlyFishV2) HandleSendCardTask(ctx context.Context, orde
})
otelresty.TraceClient(webClient)
queryRes, err := webClient.R().Get(queryUrl.String())
queryRes, err := webClient.R().SetContext(ctx).Get(queryUrl.String())
if err != nil {
return errors.New("请求提交地址失败")
}
@@ -142,7 +142,7 @@ func (s *SendCardTaskTypeFlyFishV2) HandleSendCardTask(ctx context.Context, orde
"productCode": productCode,
}
resp, err := webClient.R().SetFormData(formData).Post("https://apify.fkpay.online/submitcard")
resp, err := webClient.R().SetContext(ctx).SetFormData(formData).Post("https://apify.fkpay.online/submitcard")
if err != nil {
return err
}

View File

@@ -10,6 +10,7 @@ import (
"gateway/internal/otelTrace"
"gateway/internal/service/client"
"gateway/internal/utils"
"gateway/internal/utils/useragent"
"net/url"
"sort"
"strings"
@@ -23,7 +24,6 @@ import (
"github.com/duke-git/lancet/v2/random"
"github.com/duke-git/lancet/v2/structs"
"github.com/go-resty/resty/v2"
"github.com/iunary/fakeuseragent"
"github.com/widuu/gojson"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace"
@@ -71,7 +71,7 @@ func (s *SendCardTaskTypeMagicFish) CreateOrder(ctx context.Context, roadUid str
webClient := resty.New().EnableTrace().SetTimeout(3 * time.Second).SetRetryCount(1).SetHeaders(map[string]string{
"Origin": "http://api.zbgj79.com",
}).SetHeaders(map[string]string{
"user-agent": fakeuseragent.RandomUserAgent(),
"user-agent": useragent.GetUserAgentByPlatform(useragent.PlatformPhone),
})
otelresty.TraceClient(webClient)

View File

@@ -53,7 +53,7 @@ func (s *SendCardTaskTypeMyself) CreateOrder(ctx context.Context, roadUid string
}
func (s *SendCardTaskTypeMyself) HandleSendCardTask(ctx context.Context, orderItem OrderPoolItem, task SendCardTask) error {
ctx, span := otelTrace.Span(ctx, "SendCardTaskTypeMyself", "SendCardTaskTypeMyself.HandleSendCardTask", trace.WithAttributes(
ctx, span := otelTrace.Span(ctx, "SendCardTaskTypeNuclear", "SendCardTaskTypeNuclear.HandleSendCardTask", trace.WithAttributes(
attribute.String("bankOrderId", task.LocalOrderID),
attribute.String("cardNo", task.CardInfo.CardNo),
attribute.String("cardPassword", task.CardInfo.Data),

View File

@@ -0,0 +1,404 @@
package card_sender
import (
"context"
"encoding/json"
"errors"
"fmt"
"gateway/internal/cache"
"gateway/internal/config"
"gateway/internal/models/road"
"gateway/internal/otelTrace"
"gateway/internal/service/client"
"gateway/internal/utils"
"gateway/internal/utils/fingerprint"
"gateway/internal/utils/useragent"
"math/rand/v2"
"net/http"
"net/url"
"slices"
"sort"
"strconv"
"strings"
"time"
"github.com/dubonzi/otelresty"
"github.com/duke-git/lancet/v2/convertor"
"github.com/duke-git/lancet/v2/maputil"
"github.com/duke-git/lancet/v2/pointer"
"github.com/duke-git/lancet/v2/random"
"github.com/go-resty/resty/v2"
"github.com/widuu/gojson"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace"
"go.uber.org/zap"
)
type SendCardTaskTypeNuclear struct {
sendCardTaskTypeSendCardTaskBase
}
func (s *SendCardTaskTypeNuclear) getRandomId(ctx context.Context) (string, string) {
redisClient := cache.GetRedisClient()
// 检查是否已经有足够的ID在Redis中
keys, err := redisClient.Client.Keys(ctx, "nuclear_random_ids:*").Result()
if err != nil {
otelTrace.Logger.WithContext(ctx).Error("Redis keys error", zap.Error(err))
}
if len(keys) < 20000 {
// 生成2w个随机数
for i := 0; i < 20000; i++ {
nuclearRandomId := utils.GetMd5Lower(utils.GenerateId())
// 生成浏览器指纹哈希
fingerprintHash := fingerprint.GenerateRandomBrowserFingerprintHash()
err = redisClient.Set(ctx, "nuclear_random_ids:"+nuclearRandomId, fingerprintHash, 0)
if err != nil {
otelTrace.Logger.WithContext(ctx).Error("Failed to set nuclear ID", zap.Error(err))
}
}
// 重新获取keys
keys, err = redisClient.Client.Keys(ctx, "nuclear_random_ids:*").Result()
if err != nil {
otelTrace.Logger.WithContext(ctx).Error("Redis keys error after generation", zap.Error(err))
}
}
if len(keys) == 0 {
// 如果获取失败生成一个临时ID
tempId := utils.GetMd5Lower(utils.GenerateId())
tempFingerprint := fingerprint.GenerateRandomBrowserFingerprintHash()
return tempId, tempFingerprint
}
// 随机选择一个key
selectedKey := keys[rand.IntN(len(keys))]
// 万分之一概率随机删除一个key-value
if rand.IntN(10000) == 0 {
// 随机选择一个key删除
selectedKeyToDelete := keys[rand.IntN(len(keys))]
if err2 := redisClient.Delete(ctx, selectedKeyToDelete); err != nil {
otelTrace.Logger.WithContext(ctx).Error("Failed to delete random key",
zap.String("key", selectedKeyToDelete),
zap.Error(err2))
}
}
// 从key中提取ID部分
parts := strings.Split(selectedKey, ":")
if len(parts) != 2 {
// 如果格式不对生成一个临时ID
tempId := utils.GetMd5Lower(utils.GenerateId())
tempFingerprint := fingerprint.GenerateRandomBrowserFingerprintHash()
return tempId, tempFingerprint
}
randomId := parts[1]
// 获取对应的浏览器指纹
var fingerprintHash string
err = redisClient.Get(ctx, selectedKey, &fingerprintHash)
if err != nil {
// 如果获取指纹失败,生成一个新的
fingerprintHash = fingerprint.GenerateRandomBrowserFingerprintHash()
err = redisClient.Set(ctx, selectedKey, fingerprintHash, 0)
if err != nil {
otelTrace.Logger.WithContext(ctx).Error("Failed to set fingerprint", zap.Error(err))
}
}
return randomId, fingerprintHash
}
// 生成md5加密
func (s *SendCardTaskTypeNuclear) generateSign(ctx context.Context, params map[string]string, key string) string {
keys := maputil.Keys(params)
sort.Strings(keys)
sign_ := ""
for _, key2 := range keys {
if params[key2] == "" || key2 == "SignType" {
continue
}
sign_ += key2 + "=" + convertor.ToString(params[key2]) + "&"
}
sign_ = strings.Trim(sign_, "&")
sign_ += key
return utils.EncodeMd5Str(sign_)
}
func (s *SendCardTaskTypeNuclear) CreateOrder(ctx context.Context, roadUid string, faceValue float64) (OrderPoolItem, error) {
orderPoolItem := OrderPoolItem{}
roadInfo := road.GetRoadInfoByRoadUid(ctx, roadUid)
cfg := config.GetConfig()
orderId := utils.GenerateId()
clientId, fingerprintHash := s.getRandomId(ctx)
params := map[string]string{
"AppId": gojson.Json(roadInfo.Params).Get("appId").Tostring(),
"ChannelCode": gojson.Json(roadInfo.Params).Get("channelCode").Tostring(),
"Amount": strconv.FormatFloat(faceValue, 'f', 0, 64),
"OrderId": orderId,
"NonceStr": utils.GenerateId(),
"NotifyUrl": cfg.GatewayAddr() + "/nuclear/notify",
"ClientId": clientId,
"SignType": "MD5",
"Sign": "",
}
params["Sign"] = s.generateSign(ctx, params, gojson.Json(roadInfo.Params).Get("key").Tostring())
otelTrace.Logger.WithContext(ctx).Info("构造sign参数", zap.Any("sign", params))
webClient := resty.New().SetTimeout(time.Second * 5).SetRetryCount(2)
otelresty.TraceClient(webClient)
response, err := webClient.R().SetContext(ctx).SetFormData(params).
Post("https://gate.nuclearpow.net:2087/api/order/create")
if err != nil {
return orderPoolItem, err
}
respData := struct {
Code string `json:"Code"`
Payload struct {
OrderId string `json:"OrderId"`
TradeOrderId string `json:"TradeOrderId"`
Amount float64 `json:"Amount"`
AppId string `json:"AppId"`
BankId int `json:"BankId"`
Data string `json:"Data"`
DataType int `json:"DataType"`
}
}{}
otelTrace.Logger.WithContext(ctx).Info("请求结果", zap.Any("result", respData))
if err2 := json.Unmarshal(response.Body(), &respData); err2 != nil {
otelTrace.Logger.WithContext(ctx).Error("请求结果解析失败", zap.Error(err2))
return orderPoolItem, err2
}
//创建session
webClient2 := resty.New().SetTimeout(3 * time.Second).SetHeaders(map[string]string{
"Origin": "pay50.baolong18080.com",
}).SetHeaders(map[string]string{
"user-agent": useragent.GetUserAgentByPlatform(useragent.PlatformAndroid),
})
otelresty.TraceClient(webClient2)
createTime := time.Now()
captchaCode := ""
for range 5 {
captchaRes, err2 := webClient2.R().SetContext(ctx).SetQueryParamsFromValues(url.Values{
"oe": []string{
respData.Payload.TradeOrderId,
},
}).Get("https://pay50.baolong18080.com/pay/index/captcha.html")
if err2 != nil || pointer.IsNil(captchaRes) {
continue
}
ocrResp, err2 := client.NewOCRClient().Calc(ctx, &client.CalcInput{
File: captchaRes.Body(),
FileName: "captcha.jpg",
Category: "calc",
})
if err2 != nil || ocrResp == "" {
continue
}
captchaCode = ocrResp
break
}
return OrderPoolItem{
OrderID: orderId,
CreateTime: createTime,
PayURL: respData.Payload.Data,
RoadUid: roadUid,
FaceValue: faceValue,
DispatchCount: 0,
SendCardTaskType: SendCardTaskTypeEnumNuclear,
RemoteOrderID: respData.Payload.TradeOrderId,
Params: map[string]any{
"captchaCode": captchaCode,
"cookie": webClient.Cookies,
"fingerprintHash": fingerprintHash,
},
}, nil
}
func (s *SendCardTaskTypeNuclear) HandleSendCardTask(ctx context.Context, orderItem OrderPoolItem, task SendCardTask) error {
ctx, span := otelTrace.Span(ctx, "SendCardTaskTypeNuclear", "SendCardTaskTypeNuclear.HandleSendCardTask", trace.WithAttributes(
attribute.String("bankOrderId", task.LocalOrderID),
attribute.String("cardNo", task.CardInfo.CardNo),
attribute.String("cardPassword", task.CardInfo.Data),
attribute.String("orderId", orderItem.OrderID),
))
defer span.End()
webClient := resty.New().EnableTrace().SetRetryCount(1).
SetTimeout(5 * time.Second).SetHeaders(map[string]string{
"user-agent": useragent.GetUserAgentByPlatform(useragent.PlatformAndroid),
}).SetHeaders(map[string]string{
"Origin": "pay50.baolong18080.com",
})
otelresty.TraceClient(webClient)
ocrResp := ""
if ocrRespCode, ok := orderItem.Params["captchaCode"]; ok {
ocrResp, _ = ocrRespCode.(string)
}
if cookie, ok := orderItem.Params["cookie"]; ok {
if cookies, ok2 := cookie.([]*http.Cookie); ok2 {
webClient.SetCookies(cookies)
}
}
fingerprintHash := ""
if fingerprintHashCode, ok := orderItem.Params["fingerprintHash"]; ok {
fingerprintHash, _ = fingerprintHashCode.(string)
} else {
fingerprintHash = fingerprint.GenerateRandomBrowserFingerprintHash()
}
span.AddEvent("changeProxy")
//needChangeProxyId := utils.GenerateId()
var errRes error
var err error
for range 20 {
errRes = nil
//span.AddEvent("getProxy")
//proxy, err2 := utils.GetProxy(ctx, needChangeProxyId, SendCardTaskTypeEnumNuclear.String())
//if err2 != nil {
// otelTrace.Logger.WithContext(ctx).Error("获取代理失败", zap.Error(err))
// continue
//}
//span.AddEvent("endGetProxy")
//webClient = webClient.SetProxy(proxy)
if ocrResp == "" {
span.AddEvent("startGetCaptcha")
resp, err3 := webClient.R().SetContext(ctx).SetQueryParamsFromValues(url.Values{
"oe": []string{
orderItem.RemoteOrderID,
},
}).Get("https://pay50.baolong18080.com/pay/index/captcha.html")
span.AddEvent("finishGetCaptcha")
if err3 != nil {
//needChangeProxyId = utils.GenerateId()
otelTrace.Logger.WithContext(ctx).Error("获取验证码失败", zap.Error(err3))
continue
}
span.AddEvent("getCaptchaOCRResult")
ocrResp, err = client.NewOCRClient().Calc(ctx, &client.CalcInput{
File: resp.Body(),
FileName: "captcha.jpg",
Category: "calc",
})
span.AddEvent("finishCaptchaOCRResult")
if err != nil {
errRes = errors.New("识别验证码失败,需要重新提交")
otelTrace.Logger.WithContext(ctx).Error("识别验证码失败", zap.Error(err))
continue
}
if ocrResp == "" {
errRes = errors.New("识别验证码失败,需要重新提交")
continue
}
}
if time.Since(orderItem.CreateTime).Seconds() <= 10 {
time.Sleep(time.Duration(random.RandInt(int(10-time.Since(orderItem.CreateTime).Seconds()), 10)) * time.Second)
}
span.AddEvent("startSubmitData")
bodyData := map[string]string{
"cardNo": task.CardInfo.CardNo,
"pass": task.CardInfo.Data,
"verifyCode": ocrResp,
"token": fingerprintHash,
}
if token, ok := orderItem.Params["token"].(string); ok {
bodyData["token"] = token
}
resp, err2 := webClient.R().SetContext(ctx).SetBody(bodyData).SetQueryParamsFromValues(url.Values{
"oe": []string{
orderItem.RemoteOrderID,
},
}).Post("https://pay50.baolong18080.com/pay/PxyAlai/setParams")
span.AddEvent("finishSubmitData")
//重置验证码
ocrResp = ""
if err2 != nil {
otelTrace.Logger.WithContext(ctx).Error("提交支付失败", zap.Error(err2))
//needChangeProxyId = utils.GenerateId()
continue
}
submitRespStr := struct {
Code int `json:"code"`
Msg string `json:"msg"`
}{}
otelTrace.Logger.WithContext(ctx).Info("提交支付返回", zap.Any("response", resp), zap.Any("formData", map[string]string{
"card_no": task.CardInfo.CardNo,
"pass": task.CardInfo.Data,
"captcha": ocrResp,
}))
err2 = json.Unmarshal(resp.Body(), &submitRespStr)
if err2 != nil {
otelTrace.Logger.WithContext(ctx).Error("json解析失败", zap.Error(err2), zap.Any("response", resp))
continue
}
msgList := []string{
"必须是数字",
"验证码计算错误",
}
if slices.ContainsFunc(msgList, func(msg string) bool {
return strings.Contains(submitRespStr.Msg, msg)
}) {
errRes = errors.New("识别验证码失败,需要重新提交")
continue
}
if submitRespStr.Code != 0 {
otelTrace.Logger.WithContext(ctx).Error("支付失败", zap.Any("response", resp))
return fmt.Errorf("支付失败:%s", submitRespStr.Msg)
}
return nil
}
if errRes != nil {
return errRes
}
return errors.New("提交失败,请重新提交")
}
func (s *SendCardTaskTypeNuclear) QueryOrder(ctx context.Context, orderItem OrderPoolItem, task SendCardTask) error {
return nil
}
// GetFingerprintHashByRandomId 通过randomId获取对应的fingerprintHash
func (s *SendCardTaskTypeNuclear) getFingerprintHashByRandomId(ctx context.Context, randomId string) string {
redisClient := cache.GetRedisClient()
// 构造Redis key
key := "nuclear_random_ids:" + randomId
// 从Redis中获取指纹
var fingerprintHash string
err := redisClient.Get(ctx, key, &fingerprintHash)
if err != nil || fingerprintHash == "" {
// 如果获取失败或指纹为空,重新生成一个指纹
otelTrace.Logger.WithContext(ctx).Info("Fingerprint not found or empty, generating new one",
zap.String("randomId", randomId),
zap.String("key", key),
zap.Error(err))
// 生成新的浏览器指纹哈希
fingerprintHash = fingerprint.GenerateRandomBrowserFingerprintHash()
// 将新指纹存入Redis
err = redisClient.Set(ctx, key, fingerprintHash, 0)
if err != nil {
otelTrace.Logger.WithContext(ctx).Error("Failed to set new fingerprint hash",
zap.String("randomId", randomId),
zap.String("key", key),
zap.Error(err))
}
return fingerprintHash
}
return fingerprintHash
}

View File

@@ -0,0 +1,32 @@
package card_sender
import (
"gateway/internal/utils/useragent"
"net/url"
"os"
"testing"
"time"
"github.com/go-resty/resty/v2"
)
func TestSendCardTaskTypeNuclear_getRandomId(t *testing.T) {
id, _ := (&SendCardTaskTypeNuclear{}).getRandomId(t.Context())
id2, _ := (&SendCardTaskTypeNuclear{}).getRandomId(t.Context())
t.Log(id, id2)
}
func TestSendCardTaskTypeNuclear_CreateOrder(t *testing.T) {
//创建session
webClient2 := resty.New().SetTimeout(3 * time.Second).SetHeaders(map[string]string{
"Origin": "pay50.baolong18080.com",
}).SetHeaders(map[string]string{
"user-agent": useragent.GetUserAgentByPlatform(useragent.PlatformAndroid),
})
captchaRes, _ := webClient2.R().SetContext(t.Context()).SetQueryParamsFromValues(url.Values{
"oe": []string{
"BL20250922175154004154",
},
}).Get("https://pay50.baolong18080.com/pay/index/captcha.html")
os.WriteFile("captcha.jpg", captchaRes.Body(), 0666)
}

View File

@@ -11,6 +11,7 @@ import (
"gateway/internal/otelTrace"
"gateway/internal/service"
"gateway/internal/utils"
"gateway/internal/utils/useragent"
"net/http"
"regexp"
"sort"
@@ -24,7 +25,6 @@ import (
"github.com/duke-git/lancet/v2/maputil"
"github.com/duke-git/lancet/v2/structs"
"github.com/go-resty/resty/v2"
"github.com/iunary/fakeuseragent"
"github.com/widuu/gojson"
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
"go.opentelemetry.io/otel/attribute"
@@ -150,7 +150,7 @@ func (s *SendCardTaskTypeSesame) HandleSendCardTask(ctx context.Context, orderIt
for range 5 {
webClient.SetHeaders(map[string]string{
"user-agent": fakeuseragent.RandomUserAgent(),
"user-agent": useragent.GetUserAgentByPlatform(useragent.PlatformPhone),
})
resp, err = webClient.R().SetContext(ctx).SetFormData(map[string]string{
"payOrderId": orderNo,

View File

@@ -11,6 +11,7 @@ import (
"gateway/internal/otelTrace"
"gateway/internal/service"
"gateway/internal/utils"
"gateway/internal/utils/useragent"
"net/url"
"sort"
"strings"
@@ -22,7 +23,6 @@ import (
"github.com/duke-git/lancet/v2/maputil"
"github.com/duke-git/lancet/v2/structs"
"github.com/go-resty/resty/v2"
"github.com/iunary/fakeuseragent"
"github.com/widuu/gojson"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace"
@@ -195,7 +195,7 @@ func (s *SendCardTaskTypeShengtian) HandleSendCardTask(ctx context.Context, orde
// continue
// }
webClient := resty.New().SetTimeout(5 * time.Second).SetRetryCount(3).SetHeaders(map[string]string{
"user-agent": fakeuseragent.RandomUserAgent(),
"user-agent": useragent.GetUserAgentByPlatform(useragent.PlatformPhone),
})
resp, err2 := webClient.R().SetContext(ctx).SetBody(struct {
PayOrderId string `json:"payOrderId"`

View File

@@ -9,6 +9,7 @@ import (
"gateway/internal/models/road"
"gateway/internal/otelTrace"
"gateway/internal/utils"
"gateway/internal/utils/useragent"
"regexp"
"sort"
"strconv"
@@ -20,7 +21,6 @@ import (
"github.com/duke-git/lancet/v2/maputil"
"github.com/duke-git/lancet/v2/pointer"
"github.com/go-resty/resty/v2"
"github.com/iunary/fakeuseragent"
"github.com/widuu/gojson"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace"
@@ -106,14 +106,14 @@ func (s *SendCardTaskTypeWtr) HandleSendCardTask(ctx context.Context, orderItem
attribute.String("orderId", orderItem.OrderID),
))
defer span.End()
//ua := fakeuseragent.RandomUserAgent()
//ua := useragent.GetUserAgentByPlatform(useragent.PlatformPhone)
// 创建resty客户端
client := resty.New().
SetRetryCount(3).
SetRetryWaitTime(5*time.Second).
SetRetryMaxWaitTime(10*time.Second).
SetHeader("User-Agent", fakeuseragent.RandomUserAgent()).
SetHeader("User-Agent", useragent.GetUserAgentByPlatform(useragent.PlatformPhone)).
SetTimeout(5 * time.Second).OnBeforeRequest(func(client *resty.Client, request *resty.Request) error {
proxy, _ := utils.GetProxy(ctx, utils.GenerateId(), "SendCardTaskTypeWtr_cardTask")
if proxy != "" {

View File

@@ -19,7 +19,6 @@ import (
"gateway/internal/service"
"gateway/internal/service/supplier"
"gateway/internal/utils"
"net/http"
"sort"
"strconv"
"time"
@@ -27,13 +26,13 @@ import (
"github.com/beego/beego/v2/client/httplib"
"github.com/beego/beego/v2/server/web"
"github.com/bytedance/sonic"
"github.com/dubonzi/otelresty"
"github.com/duke-git/lancet/v2/convertor"
"github.com/duke-git/lancet/v2/cryptor"
"github.com/duke-git/lancet/v2/maputil"
"github.com/go-resty/resty/v2"
"github.com/widuu/gojson"
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
"go.opentelemetry.io/otel/attribute"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
"go.opentelemetry.io/otel/trace"
"go.uber.org/zap"
)
@@ -48,7 +47,7 @@ func (c *QiXiCardImpl) HasDependencyHTML() bool {
}
// 生成md5加密
func generateSign(ctx context.Context, params map[string]any, key string) string {
func (c *QiXiCardImpl) generateSign(ctx context.Context, params map[string]any, key string) string {
keys := maputil.Keys(params)
sort.Strings(keys)
sign_ := ""
@@ -59,7 +58,6 @@ func generateSign(ctx context.Context, params map[string]any, key string) string
sign_ += key2 + "=" + convertor.ToString(params[key2]) + "&"
}
sign_ += "key=" + key
otelTrace.Logger.WithContext(ctx).Info("构造sign参数", zap.Any("sign", sign_))
return utils.EncodeMd5Str(sign_)
}
@@ -81,26 +79,26 @@ func (c *QiXiCardImpl) SendCard(ctx context.Context, jsonStr string, cardInfo su
"timestamp": strconv.Itoa(int(time.Now().Unix())),
}
params["sign"] = generateSign(ctx, params, gojson.Json(jsonStr).Get("key").Tostring())
params["sign"] = c.generateSign(ctx, params, gojson.Json(jsonStr).Get("key").Tostring())
req := httplib.NewBeegoRequestWithCtx(ctx,
fmt.Sprintf("%s/tocard.html", gojson.Json(jsonStr).Get("url").Tostring()), "POST").
SetTransport(otelhttp.NewTransport(http.DefaultTransport)).
SetTimeout(time.Second*30, time.Second*30).Retries(3)
webClient := resty.New().SetTimeout(time.Second * 5).SetRetryCount(1)
otelresty.TraceClient(webClient)
for key, value := range params {
req.Param(key, convertor.ToString(value))
paramsStr := map[string]string{}
for a, b := range params {
paramsStr[a] = convertor.ToString(b)
}
response, err := webClient.R().SetContext(ctx).SetFormData(paramsStr).
Post(fmt.Sprintf("%s/tocard.html", gojson.Json(jsonStr).Get("url").Tostring()))
otelTrace.Logger.WithContext(ctx).Info("请求参数:", zap.Any("params", params))
response, err := req.String()
resp, _ := utils.UnescapeUnicode([]byte(response))
if err != nil {
otelTrace.Logger.WithContext(ctx).Error("七喜 请求失败:", zap.Error(err))
return false, ""
otelTrace.Logger.WithContext(ctx).Error("七喜 获取卡密失败:", zap.Error(err))
return false, "内部数据处理失败"
}
otelTrace.Logger.WithContext(ctx).Info("请求参数", zap.Any("params", params))
resp, _ := utils.UnescapeUnicode([]byte(response.String()))
type AutoGenerated struct {
XMLName xml.Name `xml:"think"`
Code int `json:"code" xml:"code"`
@@ -108,10 +106,10 @@ func (c *QiXiCardImpl) SendCard(ctx context.Context, jsonStr string, cardInfo su
}
resData := AutoGenerated{}
err = xml.Unmarshal([]byte(response), &resData)
otelTrace.Logger.WithContext(ctx).Info("远端请求返回数据:", zap.Any("response", string(resp)), zap.Any("解析结果", resData))
err = xml.Unmarshal(resp, &resData)
otelTrace.Logger.WithContext(ctx).Info("远端请求返回数据:", zap.Any("response", string(resp)))
if err != nil {
otelTrace.Logger.WithContext(ctx).Error("json解析失败", zap.Error(err), zap.String("response", response))
otelTrace.Logger.WithContext(ctx).Error("json解析失败", zap.Error(err))
return false, "内部数据处理失败"
}
if resData.Code == 1 {
@@ -189,18 +187,23 @@ func (c *QiXiCardImpl) PayNotify() {
}
ctx, span := otelTrace.Span(
ctx, "ScreamImpl", "ScreamImpl.PayNotify", trace.WithAttributes(
ctx, "ScreamImpl", "NuclearImpl.PayNotify", trace.WithAttributes(
attribute.String("bankOrderId", convertor.ToString(params["orderId"])),
attribute.String("systemOrderId", convertor.ToString(params["systemOrderId"])),
attribute.String("amount", convertor.ToString(params["amount"])),
attribute.String("successAmount", convertor.ToString(params["successAmount"])),
attribute.String("status", convertor.ToString(params["status"])),
attribute.String("message", convertor.ToString(params["message"])),
),
)
sdktrace.AlwaysSample()
defer span.End()
if params["status"] == "1" {
otelTrace.Logger.WithContext(ctx).Info("【七喜】回调了正在处理的订单")
c.Ctx.WriteString("FAIL")
}
//通form表单里提取数据
orderInfo := order.GetOrderByBankOrderId(ctx, convertor.ToString(params["orderId"]))
if orderInfo.BankOrderId == "" || len(orderInfo.BankOrderId) == 0 {

View File

@@ -60,9 +60,7 @@ func (s *ScreamImpl) generateSign(ctx context.Context, params map[string]any, ke
}
func (s *ScreamImpl) sendCard(ctx context.Context, jsonStr string, cardInfo supplier.RedeemCardInfo, attach string) (bool, string) {
ctx, span := otelTrace.Span(ctx, "ScreamImpl",
"ScreamImpl.sendCard",
trace.WithAttributes(attribute.String("bankOrderId", attach)))
ctx, span := otelTrace.Span(ctx, "ScreamImpl", "ScreamImpl.sendCard", trace.WithAttributes(attribute.String("bankOrderId", attach)))
defer span.End()
cfg := new(config.Config)
@@ -82,9 +80,9 @@ func (s *ScreamImpl) sendCard(ctx context.Context, jsonStr string, cardInfo supp
params["sign"] = s.generateSign(ctx, params, gojson.Json(jsonStr).Get("key").Tostring())
webClient := resty.New().SetTimeout(time.Second * 5).SetRetryCount(3)
webClient := resty.New().SetTimeout(time.Second * 5).SetRetryCount(1)
otelresty.TraceClient(webClient)
resp, err := webClient.R().SetBody(params).Post("http://writeoff.jianjiao6.com/api/order/create")
resp, err := webClient.R().SetContext(ctx).SetBody(params).Post("http://writeoff.jianjiao6.com/api/order/create")
if err != nil {
otelTrace.Logger.WithContext(ctx).Error("请求失败", zap.Error(err))
return false, "内部数据处理失败"

View File

@@ -0,0 +1,254 @@
package fingerprint
import (
"crypto/md5"
"encoding/hex"
"fmt"
"math/rand"
"strings"
"time"
)
// AudioFingerprint 音频指纹生成器
type AudioFingerprint struct {
sampleRate int
channelCount int
bufferSize int
oscillatorType string
filterType string
filterFreq float64
filterQ float64
compressor bool
dynamics bool
}
// NewAudioFingerprint 创建新的音频指纹生成器
func NewAudioFingerprint() *AudioFingerprint {
return &AudioFingerprint{
sampleRate: 44100,
channelCount: 2,
bufferSize: 4096,
oscillatorType: "sine",
filterType: "lowpass",
filterFreq: 1000.0,
filterQ: 1.0,
compressor: true,
dynamics: true,
}
}
// Generate 生成音频指纹
func (af *AudioFingerprint) Generate() string {
var fingerprint strings.Builder
// 添加基本音频配置
fingerprint.WriteString(fmt.Sprintf("SAMPLE_RATE:%d|", af.sampleRate))
fingerprint.WriteString(fmt.Sprintf("CHANNELS:%d|", af.channelCount))
fingerprint.WriteString(fmt.Sprintf("BUFFER_SIZE:%d|", af.bufferSize))
// 添加振荡器信息
fingerprint.WriteString(fmt.Sprintf("OSCILLATOR:%s|", af.oscillatorType))
// 添加滤波器信息
fingerprint.WriteString(fmt.Sprintf("FILTER:%s@%.2f,Q=%.2f|", af.filterType, af.filterFreq, af.filterQ))
// 添加效果器信息
if af.compressor {
fingerprint.WriteString("COMPRESSOR:1|")
} else {
fingerprint.WriteString("COMPRESSOR:0|")
}
if af.dynamics {
fingerprint.WriteString("DYNAMICS:1|")
} else {
fingerprint.WriteString("DYNAMICS:0|")
}
// 生成模拟音频数据
audioData := af.generateAudioData()
fingerprint.WriteString(fmt.Sprintf("AUDIO_DATA:%s", audioData))
// 计算MD5哈希
hash := md5.Sum([]byte(fingerprint.String()))
return hex.EncodeToString(hash[:])
}
// generateAudioData 生成模拟音频数据(极度简化版本)
func (af *AudioFingerprint) generateAudioData() string {
var data strings.Builder
// 极度简化的音频数据生成
maxSamples := 128 // 进一步限制样本数量
samples := maxSamples
for i := 0; i < samples; i++ {
// 使用简单的伪随机数生成
var sample float64
// 基于振荡器类型生成不同的简单模式
switch af.oscillatorType {
case "sine":
sample = af.sin(float64(i) * 0.1)
case "square":
sample = float64(i%2)*2 - 1 // 简单的方波
case "sawtooth":
sample = float64(i%32)/16 - 1 // 简单的锯齿波
case "triangle":
sample = float64((i%32-16)%16)/8 - 1 // 简单的三角波
default:
sample = af.sin(float64(i) * 0.1)
}
// 简单的振幅调制
sample *= 0.5
// 转换为16位整数并编码
sample16 := int16(sample * 16383) // 降低音量避免削波
data.WriteString(fmt.Sprintf("%04x", uint16(sample16)))
// 添加分隔符
if i > 0 && i%16 == 0 {
data.WriteString("|")
}
}
return data.String()
}
// sineWave 生成正弦波
func (af *AudioFingerprint) sineWave(t float64) float64 {
return af.wave(t, 440.0) // A4音符
}
// squareWave 生成方波
func (af *AudioFingerprint) squareWave(t float64) float64 {
sample := af.wave(t, 440.0)
if sample > 0 {
return 0.8
}
return -0.8
}
// sawtoothWave 生成锯齿波
func (af *AudioFingerprint) sawtoothWave(t float64) float64 {
freq := 440.0
phase := t * freq * 2 * 3.14159
return (2*(phase/(2*3.14159)-float64(int(phase/(2*3.14159)))) - 1) * 0.8
}
// triangleWave 生成三角波
func (af *AudioFingerprint) triangleWave(t float64) float64 {
freq := 440.0
phase := t * freq * 2 * 3.14159
saw := 2*(phase/(2*3.14159)-float64(int(phase/(2*3.14159)))) - 1
if saw > 0 {
return 2*saw - 1
}
return -2*saw - 1
}
// wave 基础波形生成
func (af *AudioFingerprint) wave(t, freq float64) float64 {
return af.amplitude(t, freq) * 0.8
}
// amplitude 幅度调制
func (af *AudioFingerprint) amplitude(t, freq float64) float64 {
// 简单的包络
envelope := 1.0
if t < 0.1 {
envelope = t / 0.1 // 攻击时间
} else if t > 0.9 {
envelope = (1.0 - t) / 0.1 // 释放时间
}
return envelope * (0.5 + 0.5*af.modulation(t, 5.0)) // 调制
}
// modulation 调制
func (af *AudioFingerprint) modulation(t, modFreq float64) float64 {
return 0.5 * (1 + af.sin(t*modFreq*2*3.14159))
}
// sin 正弦函数(简化版本)
func (af *AudioFingerprint) sin(x float64) float64 {
// 使用简化的正弦函数实现,避免复杂的泰勒级数计算
for x > 2*3.14159 {
x -= 2 * 3.14159
}
for x < 0 {
x += 2 * 3.14159
}
// 使用简单的近似计算
return x * (1 - x*x/(2*3.14159*3.14159))
}
// applyFilter 应用滤波器
func (af *AudioFingerprint) applyFilter(sample, t float64) float64 {
// 简单的低通滤波器模拟
if af.filterType == "lowpass" {
// 应用简单的滤波效果
filterAmount := 0.1
sample = sample*(1-filterAmount) + af.sin(t*af.filterFreq*2*3.14159)*filterAmount
}
return sample
}
// applyCompressor 应用压缩器
func (af *AudioFingerprint) applyCompressor(sample float64) float64 {
threshold := 0.5
ratio := 4.0
if sample > threshold {
return threshold + (sample-threshold)/ratio
}
if sample < -threshold {
return -threshold + (sample+threshold)/ratio
}
return sample
}
// applyDynamics 应用动态处理
func (af *AudioFingerprint) applyDynamics(sample float64) float64 {
// 简单的动态处理
return sample * (0.8 + 0.2*af.sin(float64(time.Now().UnixNano()/1e9)))
}
// GenerateRandomAudioFingerprint 生成随机音频指纹
func GenerateRandomAudioFingerprint() string {
af := NewAudioFingerprint()
// 随机采样率
sampleRates := []int{22050, 44100, 48000, 96000}
af.sampleRate = sampleRates[rand.Intn(len(sampleRates))]
// 随机声道数
af.channelCount = 1 + rand.Intn(2) // 1-2
// 随机缓冲区大小 - 限制大小以避免性能问题
bufferSizes := []int{256, 512, 1024} // 更小的缓冲区大小
af.bufferSize = bufferSizes[rand.Intn(len(bufferSizes))]
// 随机振荡器类型
oscillators := []string{"sine", "square", "sawtooth", "triangle"}
af.oscillatorType = oscillators[rand.Intn(len(oscillators))]
// 随机滤波器类型
filters := []string{"lowpass", "highpass", "bandpass", "notch"}
af.filterType = filters[rand.Intn(len(filters))]
// 随机滤波器频率
af.filterFreq = 200 + rand.Float64()*3800 // 200-4000 Hz
// 随机滤波器Q值
af.filterQ = 0.5 + rand.Float64()*10 // 0.5-10.5
// 随机效果器
af.compressor = rand.Intn(100) < 70 // 70%概率启用
af.dynamics = rand.Intn(100) < 60 // 60%概率启用
return af.Generate()
}

View File

@@ -0,0 +1,239 @@
package fingerprint
import (
"crypto/md5"
"encoding/hex"
"fmt"
"image"
"image/color"
"image/draw"
"math/rand"
"strings"
)
// CanvasFingerprint Canvas指纹生成器
type CanvasFingerprint struct {
text string
fontSize float64
fontFamily string
background color.Color
textColor color.Color
overlayColor color.Color
}
// NewCanvasFingerprint 创建新的Canvas指纹生成器
func NewCanvasFingerprint() *CanvasFingerprint {
return &CanvasFingerprint{
text: "奥莱卡 Outlet,com <canvas> 1.0",
fontSize: 14,
fontFamily: "Arial",
background: color.White,
textColor: color.RGBA{0, 102, 153, 255}, // #069
overlayColor: color.RGBA{102, 204, 0, 178}, // rgba(102, 204, 0, 0.7)
}
}
// Generate 生成Canvas指纹
func (cf *CanvasFingerprint) Generate() (string, error) {
// 创建画布 - 模拟原代码中的canvas元素
img := image.NewRGBA(image.Rect(0, 0, 200, 40))
// 设置背景色
draw.Draw(img, img.Bounds(), &image.Uniform{cf.background}, image.Point{}, draw.Src)
// 模拟 ctx.fillStyle = "#f60"; ctx.fillRect(120,1,62,20);
orangeRect := color.RGBA{255, 102, 0, 255} // #f60
orangeFill := &image.Uniform{orangeRect}
draw.Draw(img, image.Rect(120, 1, 182, 21), orangeFill, image.Point{}, draw.Src)
// 绘制第一层文本 - ctx.fillStyle = "#069"; ctx.fillText(txt, 2, 15);
err := cf.drawText(img, cf.textColor, 2, 15)
if err != nil {
return "", fmt.Errorf("failed to draw first text layer: %v", err)
}
// 绘制第二层文本 - ctx.fillStyle = "rgba(102, 204, 0, 0.7)"; ctx.fillText(txt, 4, 17);
err = cf.drawText(img, cf.overlayColor, 4, 17)
if err != nil {
return "", fmt.Errorf("failed to draw second text layer: %v", err)
}
// 将图像转换为数据URL (模拟canvas.toDataURL('image/png'))
hash := cf.imageToHash(img)
return hash, nil
}
// drawText 绘制文本
func (cf *CanvasFingerprint) drawText(img *image.RGBA, textColor color.Color, x, y int) error {
// 简化的文本绘制 - 使用像素模式来模拟文本
cf.drawTextPixels(img, textColor, x, y)
return nil
}
// drawTextPixels 使用像素模式绘制文本
func (cf *CanvasFingerprint) drawTextPixels(img *image.RGBA, textColor color.Color, x, y int) {
// 简单的文本像素化绘制
// 这里我们使用固定模式的像素来代表文本字符
text := cf.text
for i, char := range text {
charX := x + i*8 // 每个字符8像素宽
// 为不同字符绘制不同的像素模式
switch char {
case '奥', '莱', '卡':
cf.drawChineseChar(img, charX, y, textColor)
case 'O', 'o', 'u', 't', 'l', 'e':
cf.drawLatinChar(img, charX, y, textColor)
case '<', '>', 'c', 'a', 'n', 'v', 's', '1', '0', ',', ' ':
cf.drawSpecialChar(img, charX, y, textColor)
default:
cf.drawDefaultChar(img, charX, y, textColor)
}
}
}
// drawChineseChar 绘制中文字符
func (cf *CanvasFingerprint) drawChineseChar(img *image.RGBA, x, y int, color color.Color) {
// 简单的中文字符模式
pattern := [][]bool{
{true, true, true, true, true, true},
{true, false, false, false, false, true},
{true, true, true, true, true, true},
{false, false, true, true, false, false},
{true, true, true, true, true, true},
}
cf.drawCharPattern(img, x, y, pattern, color)
}
// drawLatinChar 绘制拉丁字符
func (cf *CanvasFingerprint) drawLatinChar(img *image.RGBA, x, y int, color color.Color) {
// 简单的拉丁字符模式
pattern := [][]bool{
{true, true, true, true, true},
{true, false, false, false, true},
{true, false, false, false, true},
{true, false, false, false, true},
{true, true, true, true, true},
}
cf.drawCharPattern(img, x, y, pattern, color)
}
// drawSpecialChar 绘制特殊字符
func (cf *CanvasFingerprint) drawSpecialChar(img *image.RGBA, x, y int, color color.Color) {
// 简单的特殊字符模式
pattern := [][]bool{
{true, true, true, true},
{false, false, false, true},
{true, true, true, true},
{true, false, false, false},
{true, true, true, true},
}
cf.drawCharPattern(img, x, y, pattern, color)
}
// drawDefaultChar 绘制默认字符
func (cf *CanvasFingerprint) drawDefaultChar(img *image.RGBA, x, y int, color color.Color) {
// 默认字符模式
pattern := [][]bool{
{true, true, true},
{true, false, true},
{true, false, true},
{true, false, true},
{true, true, true},
}
cf.drawCharPattern(img, x, y, pattern, color)
}
// drawCharPattern 绘制字符模式
func (cf *CanvasFingerprint) drawCharPattern(img *image.RGBA, x, y int, pattern [][]bool, color color.Color) {
for py, row := range pattern {
for px, pixel := range row {
if pixel {
drawX := x + px
drawY := y + py
if drawX >= 0 && drawX < img.Bounds().Dx() && drawY >= 0 && drawY < img.Bounds().Dy() {
img.Set(drawX, drawY, color)
}
}
}
}
}
// imageToHash 将图像转换为MD5哈希
func (cf *CanvasFingerprint) imageToHash(img image.Image) string {
// 直接使用像素数据生成哈希模拟canvas.toDataURL的效果
return cf.pixelDataToHash(img)
}
// pixelDataToHash 使用像素数据生成哈希
func (cf *CanvasFingerprint) pixelDataToHash(img image.Image) string {
bounds := img.Bounds()
var pixelData strings.Builder
for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
for x := bounds.Min.X; x < bounds.Max.X; x++ {
r, g, b, a := img.At(x, y).RGBA()
pixelData.WriteString(fmt.Sprintf("%02x%02x%02x%02x", r>>8, g>>8, b>>8, a>>8))
}
}
hash := md5.Sum([]byte(pixelData.String()))
return hex.EncodeToString(hash[:])
}
// SetText 设置文本内容
func (cf *CanvasFingerprint) SetText(text string) {
cf.text = text
}
// SetFontSize 设置字体大小
func (cf *CanvasFingerprint) SetFontSize(size float64) {
cf.fontSize = size
}
// SetFontFamily 设置字体
func (cf *CanvasFingerprint) SetFontFamily(family string) {
cf.fontFamily = family
}
// SetColors 设置颜色
func (cf *CanvasFingerprint) SetColors(bg, text, overlay color.Color) {
cf.background = bg
cf.textColor = text
cf.overlayColor = overlay
}
// GenerateRandomFingerprint 生成随机Canvas指纹
func GenerateRandomFingerprint() (string, error) {
cf := NewCanvasFingerprint()
// 添加随机变化
if rand.Intn(100) < 30 { // 30%概率改变文本
variations := []string{
"奥莱卡 Outlet,com <canvas> 1.0",
"奥莱卡 Outlet <canvas> 1.1",
"奥莱卡 com <canvas> 1.2",
"Outlet,com <canvas> 1.0",
"奥莱卡 <canvas> 1.3",
}
cf.SetText(variations[rand.Intn(len(variations))])
}
// 随机字体大小
if rand.Intn(100) < 20 { // 20%概率改变字体大小
cf.SetFontSize(float64(12 + rand.Intn(4))) // 12-15px
}
// 随机颜色变化
if rand.Intn(100) < 15 { // 15%概率改变颜色
cf.SetColors(
color.White,
color.RGBA{uint8(rand.Intn(255)), uint8(rand.Intn(255)), uint8(rand.Intn(255)), 255},
color.RGBA{uint8(rand.Intn(255)), uint8(rand.Intn(255)), uint8(rand.Intn(255)), uint8(178 + rand.Intn(77))},
)
}
return cf.Generate()
}

View File

@@ -0,0 +1,46 @@
// Package fingerprint 浏览器指纹生成工具包
//
// 这个包提供了完整的浏览器指纹生成功能,包括:
// - Canvas 指纹 (canvas.go)
// - WebGL 指纹 (webgl.go)
// - Audio 指纹 (audio.go)
// - Screen 指纹 (screen.go)
// - Fonts 指纹 (fonts.go)
// - Timezone 指纹 (timezone.go)
// - 完整浏览器指纹 (fingerprint.go)
//
// 使用示例:
//
// // 生成完整的浏览器指纹
// fingerprint, err := GenerateRandomBrowserFingerprint()
// if err != nil {
// log.Fatal(err)
// }
// fmt.Printf("Browser Fingerprint: %v\n", fingerprint)
//
// // 生成特定类型的指纹
// canvasFp, err := GenerateRandomFingerprint()
// if err != nil {
// log.Fatal(err)
// }
// fmt.Printf("Canvas Fingerprint: %s\n", canvasFp)
//
// // 生成JSON格式的指纹
// jsonFp, err := GenerateRandomBrowserFingerprintJSON()
// if err != nil {
// log.Fatal(err)
// }
// fmt.Printf("Fingerprint JSON: %s\n", jsonFp)
//
// 主要组件:
// - CanvasFingerprint: 基于Canvas API的指纹生成
// - WebGlFingerprint: 基于WebGL渲染的指纹生成
// - AudioFingerprint: 基于音频API的指纹生成
// - ScreenFingerprint: 基于屏幕信息的指纹生成
// - FontFingerprint: 基于字体检测的指纹生成
// - TimezoneFingerprint: 基于时区信息的指纹生成
// - BrowserFingerprint: 完整的浏览器指纹组合
//
// 注意:这个包主要用于测试和开发目的,用于模拟不同的浏览器环境。
// 在实际应用中,请确保遵守相关法律法规和隐私政策。
package fingerprint

View File

@@ -0,0 +1,396 @@
package fingerprint
import (
"encoding/json"
"fmt"
"math/rand"
"github.com/duke-git/lancet/v2/cryptor"
)
// BrowserFingerprint 完整的浏览器指纹
type BrowserFingerprint struct {
Canvas string `json:"canvas"`
WebGL string `json:"webgl"`
Audio string `json:"audio"`
Screen string `json:"screen"`
Fonts string `json:"fonts"`
Timezone string `json:"timezone"`
Plugins string `json:"plugins"`
Cookie string `json:"cookie"`
DoNotTrack string `json:"doNotTrack"`
Language string `json:"language"`
Platform string `json:"platform"`
CPU string `json:"cpu"`
Memory string `json:"memory"`
Touch string `json:"touch"`
Hardware string `json:"hardware"`
Permissions string `json:"permissions"`
Hash string `json:"hash"`
}
// PluginInfo 插件信息
type PluginInfo struct {
Name string `json:"name"`
Version string `json:"version"`
}
// NewBrowserFingerprint 创建新的浏览器指纹生成器
func NewBrowserFingerprint() *BrowserFingerprint {
return &BrowserFingerprint{}
}
// Generate 完整的浏览器指纹生成
func (bf *BrowserFingerprint) Generate() (*BrowserFingerprint, error) {
var err error
// 生成各个组件的指纹
bf.Canvas, err = GenerateRandomFingerprint()
if err != nil {
return nil, fmt.Errorf("failed to generate canvas fingerprint: %v", err)
}
bf.WebGL = GenerateRandomWebGLFingerprint()
bf.Audio = GenerateRandomAudioFingerprint()
bf.Screen = GenerateRandomScreenFingerprint()
bf.Fonts = GenerateRandomFontFingerprint()
bf.Timezone = GenerateRandomTimezoneFingerprint()
bf.Plugins = bf.generatePlugins()
bf.Cookie = bf.generateCookie()
bf.DoNotTrack = bf.generateDoNotTrack()
bf.Language = bf.generateLanguage()
bf.Platform = bf.generatePlatform()
bf.CPU = bf.generateCPU()
bf.Memory = bf.generateMemory()
bf.Touch = bf.generateTouch()
bf.Hardware = bf.generateHardware()
bf.Permissions = bf.generatePermissions()
// 生成最终哈希
bf.Hash = bf.generateHash()
return bf, nil
}
// generatePlugins 生成插件指纹
func (bf *BrowserFingerprint) generatePlugins() string {
// 常见插件列表
commonPlugins := []PluginInfo{
{"Chrome PDF Plugin", ""},
{"Chrome PDF Viewer", ""},
{"Native Client", ""},
{"Widevine Content Decryption Module", "4.10.2391.0"},
{"Microsoft Edge PDF Plugin", ""},
{"Microsoft Edge PDF Viewer", ""},
{"Adobe Acrobat", "32.0.0.238"},
{"Adobe Flash Player", "32.0 r0"},
{"Java Applet Plug-in", "1.8.0_291"},
{"QuickTime Plug-in", "7.7.9"},
{"Silverlight Plug-In", "5.1.50918.0"},
{"Shockwave Flash", "32.0 r0"},
{"VLC Web Plugin", "3.0.16"},
{"Google Update", "1.3.36.1"},
{"Microsoft Office 2013", "15.0.4569.1506"},
{"Microsoft Office 2016", "16.0.4266.1001"},
{"Google Talk Plugin", "41.0.3.0"},
{"Google Talk Plugin Video Accelerator", "41.0.3.0"},
{"DivX Web Player", "2.0.2.50"},
{"RealPlayer Plugin", "realplyg.dll"},
{"Windows Media Player Plug-in", "12.0"},
}
// 随机选择插件数量
numPlugins := 3 + rand.Intn(10) // 3-12个插件
selectedPlugins := make([]PluginInfo, 0, numPlugins)
for i := 0; i < numPlugins && i < len(commonPlugins); i++ {
plugin := commonPlugins[rand.Intn(len(commonPlugins))]
// 避免重复
duplicate := false
for _, existing := range selectedPlugins {
if existing.Name == plugin.Name {
duplicate = true
break
}
}
if !duplicate {
selectedPlugins = append(selectedPlugins, plugin)
}
}
// 生成插件字符串
pluginsJson, _ := json.Marshal(selectedPlugins)
return string(pluginsJson)
}
// generateCookie 生成Cookie指纹
func (bf *BrowserFingerprint) generateCookie() string {
cookieFeatures := map[string]bool{
"cookieEnabled": true,
"sessionStorage": true,
"localStorage": true,
"indexedDB": true,
"webSQL": false,
"serviceWorker": rand.Intn(100) < 80,
"cache": true,
"applicationCache": rand.Intn(100) < 30,
"webWorkers": true,
"sharedWorkers": rand.Intn(100) < 70,
"geolocation": rand.Intn(100) < 90,
"notification": rand.Intn(100) < 60,
"pushNotification": rand.Intn(100) < 50,
"gamepad": rand.Intn(100) < 40,
"vr": rand.Intn(100) < 20,
"battery": rand.Intn(100) < 30,
"deviceOrientation": rand.Intn(100) < 80,
"deviceMotion": rand.Intn(100) < 80,
"deviceLight": rand.Intn(100) < 10,
"deviceProximity": rand.Intn(100) < 10,
"userProximity": rand.Intn(100) < 10,
}
cookieJson, _ := json.Marshal(cookieFeatures)
return string(cookieJson)
}
// generateDoNotTrack 生成DoNotTrack指纹
func (bf *BrowserFingerprint) generateDoNotTrack() string {
options := []string{"unspecified", "enabled", "disabled"}
weights := []int{60, 20, 20} // 60%未指定20%启用20%禁用
totalWeight := 0
for _, weight := range weights {
totalWeight += weight
}
randomWeight := rand.Intn(totalWeight)
cumulativeWeight := 0
for i, weight := range weights {
cumulativeWeight += weight
if randomWeight < cumulativeWeight {
return options[i]
}
}
return options[0]
}
// generateLanguage 生成语言指纹
func (bf *BrowserFingerprint) generateLanguage() string {
languages := []string{
"en-US", "en-GB", "zh-CN", "zh-TW", "ja-JP", "ko-KR",
"fr-FR", "de-DE", "es-ES", "it-IT", "pt-BR", "pt-PT",
"ru-RU", "ar-SA", "hi-IN", "th-TH", "vi-VN", "id-ID",
"ms-MY", "fil-PH", "tr-TR", "pl-PL", "nl-NL", "sv-SE",
"da-DK", "no-NO", "fi-FI", "cs-CZ", "hu-HU", "ro-RO",
"bg-BG", "hr-HR", "sk-SK", "sl-SI", "et-EE", "lv-LV",
"lt-LT", "mt-MT", "el-GR", "he-IL", "uk-UA", "be-BY",
}
// 1-3种语言
numLanguages := 1 + rand.Intn(3)
selectedLanguages := make([]string, 0, numLanguages)
for i := 0; i < numLanguages && i < len(languages); i++ {
lang := languages[rand.Intn(len(languages))]
// 避免重复
duplicate := false
for _, existing := range selectedLanguages {
if existing == lang {
duplicate = true
break
}
}
if !duplicate {
selectedLanguages = append(selectedLanguages, lang)
}
}
// 组合成语言字符串
result := selectedLanguages[0]
for i := 1; i < len(selectedLanguages); i++ {
result += "," + selectedLanguages[i]
}
return result
}
// generatePlatform 生成平台指纹
func (bf *BrowserFingerprint) generatePlatform() string {
platforms := []string{
"Win32", "Windows", "MacIntel", "MacPPC", "Linux i686", "Linux x86_64",
"Linux armv7l", "Linux aarch64", "iPhone", "iPad", "iPod", "Android",
"X11", "FreeBSD i386", "FreeBSD amd64", "OpenBSD i386", "NetBSD i386",
}
return platforms[rand.Intn(len(platforms))]
}
// generateCPU 生成CPU指纹
func (bf *BrowserFingerprint) generateCPU() string {
cpus := []string{
"x86", "x86_64", "arm", "arm64", "mips", "mips64", "ppc", "ppc64",
"s390", "s390x", "ia64", "alpha", "sparc", "sparc64",
}
return cpus[rand.Intn(len(cpus))]
}
// generateMemory 生成内存指纹
func (bf *BrowserFingerprint) generateMemory() string {
// 常见内存大小GB
memorySizes := []int{2, 4, 8, 16, 32, 64}
memory := memorySizes[rand.Intn(len(memorySizes))]
return fmt.Sprintf("%d", memory)
}
// generateTouch 生成触摸指纹
func (bf *BrowserFingerprint) generateTouch() string {
touchFeatures := map[string]bool{
"maxTouchPoints": rand.Intn(100) < 60,
"touchSupport": rand.Intn(100) < 50,
"ontouchstart": rand.Intn(100) < 50,
"ontouchend": rand.Intn(100) < 50,
"ontouchmove": rand.Intn(100) < 50,
"ontouchcancel": rand.Intn(100) < 50,
"msPointerEnabled": rand.Intn(100) < 30,
"pointerEnabled": rand.Intn(100) < 70,
}
if touchFeatures["maxTouchPoints"] {
// 设置最大触摸点数
maxPoints := rand.Intn(11) // 0-10
touchFeatures["maxTouchPoints"] = maxPoints > 0
}
touchJson, _ := json.Marshal(touchFeatures)
return string(touchJson)
}
// generateHardware 生成硬件指纹
func (bf *BrowserFingerprint) generateHardware() string {
hardwareInfo := map[string]interface{}{
"deviceMemory": 2 + rand.Intn(8), // 2-10GB
"hardwareConcurrency": 1 + rand.Intn(16), // 1-16 cores
"maxTouchPoints": rand.Intn(11), // 0-10
"isMobile": rand.Intn(100) < 40,
"isTablet": rand.Intn(100) < 15,
"isDesktop": rand.Intn(100) < 45,
}
hardwareJson, _ := json.Marshal(hardwareInfo)
return string(hardwareJson)
}
// generatePermissions 生成权限指纹
func (bf *BrowserFingerprint) generatePermissions() string {
permissions := map[string]string{
"geolocation": bf.randomPermissionState(),
"notifications": bf.randomPermissionState(),
"push": bf.randomPermissionState(),
"midi": bf.randomPermissionState(),
"camera": bf.randomPermissionState(),
"microphone": bf.randomPermissionState(),
"speaker": bf.randomPermissionState(),
"device-info": bf.randomPermissionState(),
"background-sync": bf.randomPermissionState(),
"bluetooth": bf.randomPermissionState(),
"persistent-storage": bf.randomPermissionState(),
"ambient-light-sensor": bf.randomPermissionState(),
"accelerometer": bf.randomPermissionState(),
"gyroscope": bf.randomPermissionState(),
"magnetometer": bf.randomPermissionState(),
"clipboard": bf.randomPermissionState(),
"screen-wake-lock": bf.randomPermissionState(),
"nfc": bf.randomPermissionState(),
"payment": bf.randomPermissionState(),
}
permissionsJson, _ := json.Marshal(permissions)
return string(permissionsJson)
}
// randomPermissionState 生成随机权限状态
func (bf *BrowserFingerprint) randomPermissionState() string {
states := []string{"granted", "denied", "prompt"}
weights := []int{30, 40, 30} // 30%授权40%拒绝30%提示
totalWeight := 0
for _, weight := range weights {
totalWeight += weight
}
randomWeight := rand.Intn(totalWeight)
cumulativeWeight := 0
for i, weight := range weights {
cumulativeWeight += weight
if randomWeight < cumulativeWeight {
return states[i]
}
}
return states[0]
}
// generateHash 生成最终哈希
func (bf *BrowserFingerprint) generateHash() string {
// 组合所有指纹数据
combined := fmt.Sprintf("%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s",
bf.Canvas, bf.WebGL, bf.Audio, bf.Screen, bf.Fonts, bf.Timezone,
bf.Plugins, bf.Cookie, bf.DoNotTrack, bf.Language, bf.Platform,
bf.CPU, bf.Memory, bf.Touch, bf.Hardware, bf.Permissions)
// 生成简单的哈希(在实际应用中应该使用更安全的哈希算法)
var hash uint32 = 5381
for i := 0; i < len(combined); i++ {
hash = ((hash << 5) + hash) + uint32(combined[i])
}
return fmt.Sprintf("%x", hash)
}
// GenerateRandomBrowserFingerprint 生成随机浏览器指纹
func GenerateRandomBrowserFingerprint() (*BrowserFingerprint, error) {
bf := NewBrowserFingerprint()
return bf.Generate()
}
// GenerateRandomBrowserFingerprintJSON 生成随机浏览器指纹的JSON字符串
func GenerateRandomBrowserFingerprintJSON() (string, error) {
bf, err := GenerateRandomBrowserFingerprint()
if err != nil {
return "", err
}
result, err := json.MarshalIndent(bf, "", " ")
if err != nil {
return "", fmt.Errorf("failed to marshal fingerprint: %v", err)
}
return string(result), nil
}
func GenerateRandomBrowserFingerprintHash() string {
bf, err := GenerateRandomBrowserFingerprintJSON()
if err != nil {
return ""
}
return cryptor.Md5String(bf)
}

View File

@@ -0,0 +1,10 @@
package fingerprint
import "testing"
func TestGenerateRandomBrowserFingerprintJSON(t *testing.T) {
for range 100 {
fig := GenerateRandomBrowserFingerprintHash()
t.Log(fig)
}
}

View File

@@ -0,0 +1,206 @@
package fingerprint
import (
"crypto/md5"
"encoding/hex"
"fmt"
"math/rand"
"strings"
)
// FontFingerprint 字体指纹生成器
type FontFingerprint struct {
detectedFonts []string
defaultFonts []string
jsFonts []string
flashFonts []string
systemFonts []string
emojiSupport bool
fontSmoothing string
textRendering string
}
// NewFontFingerprint 创建新的字体指纹生成器
func NewFontFingerprint() *FontFingerprint {
return &FontFingerprint{
defaultFonts: []string{
"Arial", "Times New Roman", "Courier New", "Verdana", "Georgia",
"Palatino", "Garamond", "Bookman", "Tahoma", "Trebuchet MS",
},
jsFonts: []string{
"Arial", "Verdana", "Helvetica", "Trebuchet MS", "Times New Roman",
"Georgia", "Palatino", "Garamond", "Bookman", "Comic Sans MS",
"Courier New", "Impact", "Lucida Console", "Lucida Sans Unicode",
},
flashFonts: []string{
"Arial", "Verdana", "Times New Roman", "Courier New", "Georgia",
"Palatino", "Garamond", "Bookman", "Tahoma", "Comic Sans MS",
},
systemFonts: []string{
"Arial", "Helvetica", "Times New Roman", "Courier New", "Georgia",
"Verdana", "Palatino", "Garamond", "Bookman", "Impact",
},
emojiSupport: true,
fontSmoothing: "grayscale",
textRendering: "optimizeLegibility",
}
}
// Generate 生成字体指纹
func (ff *FontFingerprint) Generate() string {
var fingerprint strings.Builder
// 检测到的字体
fingerprint.WriteString("DETECTED_FONTS:")
for i, font := range ff.detectedFonts {
if i > 0 {
fingerprint.WriteString(",")
}
fingerprint.WriteString(font)
}
fingerprint.WriteString("|")
// 默认字体
fingerprint.WriteString("DEFAULT_FONTS:")
for i, font := range ff.defaultFonts {
if i > 0 {
fingerprint.WriteString(",")
}
fingerprint.WriteString(font)
}
fingerprint.WriteString("|")
// JS字体
fingerprint.WriteString("JS_FONTS:")
for i, font := range ff.jsFonts {
if i > 0 {
fingerprint.WriteString(",")
}
fingerprint.WriteString(font)
}
fingerprint.WriteString("|")
// Flash字体如果支持
if len(ff.flashFonts) > 0 {
fingerprint.WriteString("FLASH_FONTS:")
for i, font := range ff.flashFonts {
if i > 0 {
fingerprint.WriteString(",")
}
fingerprint.WriteString(font)
}
fingerprint.WriteString("|")
}
// 系统字体
fingerprint.WriteString("SYSTEM_FONTS:")
for i, font := range ff.systemFonts {
if i > 0 {
fingerprint.WriteString(",")
}
fingerprint.WriteString(font)
}
fingerprint.WriteString("|")
// Emoji支持
if ff.emojiSupport {
fingerprint.WriteString("EMOJI:1|")
} else {
fingerprint.WriteString("EMOJI:0|")
}
// 字体平滑
fingerprint.WriteString(fmt.Sprintf("SMOOTHING:%s|", ff.fontSmoothing))
// 文本渲染
fingerprint.WriteString(fmt.Sprintf("RENDERING:%s", ff.textRendering))
// 计算MD5哈希
hash := md5.Sum([]byte(fingerprint.String()))
return hex.EncodeToString(hash[:])
}
// detectFonts 检测可用字体(模拟)
func (ff *FontFingerprint) detectFonts() {
// 基础字体集合
allFonts := []string{
// Windows字体
"Arial", "Arial Black", "Arial Narrow", "Arial Rounded MT Bold",
"Calibri", "Cambria", "Candara", "Century Gothic",
"Comic Sans MS", "Consolas", "Constantia", "Corbel",
"Courier New", "Franklin Gothic Medium", "Gabriola",
"Georgia", "Impact", "Lucida Console", "Lucida Sans Unicode",
"Microsoft Sans Serif", "Palatino Linotype", "Segoe Print",
"Segoe Script", "Segoe UI", "Segoe UI Light", "Segoe UI Semibold",
"Segoe UI Symbol", "Tahoma", "Times New Roman", "Trebuchet MS",
"Verdana", "Webdings",
// Mac字体
"Arial", "Arial Black", "Arial Narrow", "Arial Rounded MT Bold",
"Avenir", "Avenir Next", "Baskerville", "Big Caslon",
"Chalkboard", "Chalkboard SE", "Chalkduster", "Cochin",
"Comic Sans MS", "Copperplate", "Courier", "Courier New",
"Didot", "Futura", "Geneva", "Georgia",
"Gill Sans", "Helvetica", "Helvetica Neue", "Impact",
"Lucida Grande", "Lucida Sans", "Marker Felt", "Menlo",
"Monaco", "Noteworthy", "Optima", "Palatino",
"Papyrus", "Phosphate", "Rockwell", "San Francisco",
"SignPainter", "Skia", "Snell Roundhand", "Tahoma",
"Times", "Times New Roman", "Trebuchet MS", "Verdana",
"Zapfino",
// Linux字体
"Arial", "Arial Black", "Bookman", "Century Schoolbook L",
"Courier 10 Pitch", "Courier New", "DejaVu Sans",
"DejaVu Sans Mono", "DejaVu Serif", "FreeMono", "FreeSans",
"FreeSerif", "Garuda", "Georgia", "Liberation Mono",
"Liberation Sans", "Liberation Serif", "Lucida Bright",
"Lucida Sans", "Lucida Sans Typewriter", "Luxi Mono",
"Luxi Sans", "Luxi Serif", "Nimbus Mono L", "Nimbus Roman No9 L",
"Nimbus Sans L", "Palatino", "Standard Symbols L",
"Times New Roman", "Trebuchet MS", "Utopia", "Verdana",
"WenQuanYi Micro Hei", "WenQuanYi Zen Hei",
}
// 随机选择一些字体作为"检测到"的字体
numFonts := 15 + rand.Intn(20) // 15-35个字体
ff.detectedFonts = make([]string, 0, numFonts)
for i := 0; i < numFonts && i < len(allFonts); i++ {
font := allFonts[rand.Intn(len(allFonts))]
// 避免重复
duplicate := false
for _, existing := range ff.detectedFonts {
if existing == font {
duplicate = true
break
}
}
if !duplicate {
ff.detectedFonts = append(ff.detectedFonts, font)
}
}
}
// GenerateRandomFontFingerprint 生成随机字体指纹
func GenerateRandomFontFingerprint() string {
ff := NewFontFingerprint()
// 检测字体
ff.detectFonts()
// 随机Emoji支持
ff.emojiSupport = rand.Intn(100) < 90 // 90%概率支持
// 随机字体平滑
smoothingOptions := []string{"auto", "none", "grayscale", "subpixel-antialiased"}
ff.fontSmoothing = smoothingOptions[rand.Intn(len(smoothingOptions))]
// 随机文本渲染
renderingOptions := []string{"auto", "optimizeSpeed", "optimizeLegibility", "geometricPrecision"}
ff.textRendering = renderingOptions[rand.Intn(len(renderingOptions))]
return ff.Generate()
}

View File

@@ -0,0 +1,166 @@
package fingerprint
import (
"crypto/md5"
"encoding/hex"
"fmt"
"math/rand"
"strings"
)
// ScreenFingerprint 屏幕指纹生成器
type ScreenFingerprint struct {
width int
height int
colorDepth int
pixelDepth int
orientation string
availWidth int
availHeight int
deviceScale float64
colorGamut string
forcedColor bool
contrast string
inverted bool
}
// NewScreenFingerprint 创建新的屏幕指纹生成器
func NewScreenFingerprint() *ScreenFingerprint {
return &ScreenFingerprint{
width: 1920,
height: 1080,
colorDepth: 24,
pixelDepth: 24,
orientation: "landscape-primary",
availWidth: 1920,
availHeight: 1040,
deviceScale: 1.0,
colorGamut: "srgb",
forcedColor: false,
contrast: "no-preference",
inverted: false,
}
}
// Generate 生成屏幕指纹
func (sf *ScreenFingerprint) Generate() string {
var fingerprint strings.Builder
// 屏幕分辨率
fingerprint.WriteString(fmt.Sprintf("RESOLUTION:%dx%d|", sf.width, sf.height))
// 颜色深度
fingerprint.WriteString(fmt.Sprintf("COLOR_DEPTH:%d|", sf.colorDepth))
fingerprint.WriteString(fmt.Sprintf("PIXEL_DEPTH:%d|", sf.pixelDepth))
// 可用区域
fingerprint.WriteString(fmt.Sprintf("AVAIL:%dx%d|", sf.availWidth, sf.availHeight))
// 方向
fingerprint.WriteString(fmt.Sprintf("ORIENTATION:%s|", sf.orientation))
// 设备缩放
fingerprint.WriteString(fmt.Sprintf("DEVICE_SCALE:%.2f|", sf.deviceScale))
// 色域
fingerprint.WriteString(fmt.Sprintf("COLOR_GAMUT:%s|", sf.colorGamut))
// 强制颜色
if sf.forcedColor {
fingerprint.WriteString("FORCED_COLOR:1|")
} else {
fingerprint.WriteString("FORCED_COLOR:0|")
}
// 对比度
fingerprint.WriteString(fmt.Sprintf("CONTRAST:%s|", sf.contrast))
// 颜色反转
if sf.inverted {
fingerprint.WriteString("INVERTED:1|")
} else {
fingerprint.WriteString("INVERTED:0|")
}
// 计算屏幕像素密度
dpi := sf.calculateDPI()
fingerprint.WriteString(fmt.Sprintf("DPI:%d|", dpi))
// 计算屏幕对角线
diagonal := sf.calculateDiagonal()
fingerprint.WriteString(fmt.Sprintf("DIAGONAL:%.1f", diagonal))
// 计算MD5哈希
hash := md5.Sum([]byte(fingerprint.String()))
return hex.EncodeToString(hash[:])
}
// calculateDPI 计算DPI
func (sf *ScreenFingerprint) calculateDPI() int {
// 假设标准DPI值
dpiValues := []int{72, 96, 120, 144, 168, 192, 216, 240}
return dpiValues[rand.Intn(len(dpiValues))]
}
// calculateDiagonal 计算屏幕对角线英寸数
func (sf *ScreenFingerprint) calculateDiagonal() float64 {
// 基于分辨率计算对角线
widthInches := float64(sf.width) / float64(sf.calculateDPI())
heightInches := float64(sf.height) / float64(sf.calculateDPI())
return widthInches*widthInches + heightInches*heightInches
}
// GenerateRandomScreenFingerprint 生成随机屏幕指纹
func GenerateRandomScreenFingerprint() string {
sf := NewScreenFingerprint()
// 常见分辨率
resolutions := []struct {
width int
height int
}{
{1920, 1080}, // Full HD
{1366, 768}, // 常见笔记本
{1536, 864}, // 常见笔记本
{1440, 900}, // MacBook
{2560, 1440}, // 2K
{3840, 2160}, // 4K
{1280, 720}, // HD
{1600, 900}, // HD+
{2560, 1080}, // 超宽
{3440, 1440}, // 超宽2K
}
res := resolutions[rand.Intn(len(resolutions))]
sf.width = res.width
sf.height = res.height
// 设置可用区域(减去任务栏等)
sf.availWidth = sf.width
sf.availHeight = sf.height - rand.Intn(80) // 减去0-80像素
// 随机颜色深度
sf.colorDepth = 24
sf.pixelDepth = 24
// 随机设备缩放
scales := []float64{1.0, 1.25, 1.5, 1.75, 2.0, 2.5, 3.0}
sf.deviceScale = scales[rand.Intn(len(scales))]
// 随机色域
gamuts := []string{"srgb", "p3", "rec2020"}
sf.colorGamut = gamuts[rand.Intn(len(gamuts))]
// 随机对比度
contrasts := []string{"no-preference", "high", "low", "custom"}
sf.contrast = contrasts[rand.Intn(len(contrasts))]
// 随机强制颜色(很少启用)
sf.forcedColor = rand.Intn(100) < 5 // 5%概率
// 随机颜色反转
sf.inverted = rand.Intn(100) < 3 // 3%概率
return sf.Generate()
}

View File

@@ -0,0 +1,199 @@
package fingerprint
import (
"crypto/md5"
"encoding/hex"
"fmt"
"math/rand"
"strings"
)
// TimezoneFingerprint 时区指纹生成器
type TimezoneFingerprint struct {
timezone string
offset int
dst bool
dstOffset int
timezoneName string
locale string
calendar string
dateFormat string
timeFormat string
weekStart int
firstDayWeek int
}
// NewTimezoneFingerprint 创建新的时区指纹生成器
func NewTimezoneFingerprint() *TimezoneFingerprint {
return &TimezoneFingerprint{
timezone: "UTC",
offset: 0,
dst: false,
dstOffset: 0,
timezoneName: "Coordinated Universal Time",
locale: "en-US",
calendar: "gregorian",
dateFormat: "MM/dd/yyyy",
timeFormat: "h:mm:ss a",
weekStart: 0, // Sunday
firstDayWeek: 0,
}
}
// Generate 生成时区指纹
func (tf *TimezoneFingerprint) Generate() string {
var fingerprint strings.Builder
// 时区信息
fingerprint.WriteString(fmt.Sprintf("TIMEZONE:%s|", tf.timezone))
fingerprint.WriteString(fmt.Sprintf("OFFSET:%d|", tf.offset))
// 夏令时信息
if tf.dst {
fingerprint.WriteString("DST:1|")
fingerprint.WriteString(fmt.Sprintf("DST_OFFSET:%d|", tf.dstOffset))
} else {
fingerprint.WriteString("DST:0|")
}
// 时区名称
fingerprint.WriteString(fmt.Sprintf("TZ_NAME:%s|", tf.timezoneName))
// 区域设置
fingerprint.WriteString(fmt.Sprintf("LOCALE:%s|", tf.locale))
// 日历类型
fingerprint.WriteString(fmt.Sprintf("CALENDAR:%s|", tf.calendar))
// 日期时间格式
fingerprint.WriteString(fmt.Sprintf("DATE_FORMAT:%s|", tf.dateFormat))
fingerprint.WriteString(fmt.Sprintf("TIME_FORMAT:%s|", tf.timeFormat))
// 周起始日
fingerprint.WriteString(fmt.Sprintf("WEEK_START:%d|", tf.weekStart))
fingerprint.WriteString(fmt.Sprintf("FIRST_DAY_WEEK:%d", tf.firstDayWeek))
// 计算MD5哈希
hash := md5.Sum([]byte(fingerprint.String()))
return hex.EncodeToString(hash[:])
}
// GenerateRandomTimezoneFingerprint 生成随机时区指纹
func GenerateRandomTimezoneFingerprint() string {
tf := NewTimezoneFingerprint()
// 常见时区列表
timezones := []struct {
name string
offset int
dst bool
tzName string
locales []string
}{
{"America/New_York", -300, true, "Eastern Standard Time", []string{"en-US", "en-CA"}},
{"America/Chicago", -360, true, "Central Standard Time", []string{"en-US", "en-CA"}},
{"America/Denver", -420, true, "Mountain Standard Time", []string{"en-US"}},
{"America/Los_Angeles", -480, true, "Pacific Standard Time", []string{"en-US", "en-CA"}},
{"America/Toronto", -300, true, "Eastern Standard Time", []string{"en-CA", "fr-CA"}},
{"America/Vancouver", -480, true, "Pacific Standard Time", []string{"en-CA", "fr-CA"}},
{"America/Mexico_City", -360, true, "Central Standard Time", []string{"es-MX"}},
{"America/Sao_Paulo", -180, true, "Brasilia Standard Time", []string{"pt-BR"}},
{"Europe/London", 0, true, "Greenwich Mean Time", []string{"en-GB", "en-US", "fr-FR", "de-DE"}},
{"Europe/Paris", 60, true, "Central European Time", []string{"fr-FR", "de-DE", "it-IT", "es-ES"}},
{"Europe/Berlin", 60, true, "Central European Time", []string{"de-DE", "at-AT", "ch-CH"}},
{"Europe/Moscow", 180, false, "Moscow Standard Time", []string{"ru-RU"}},
{"Europe/Madrid", 60, true, "Central European Time", []string{"es-ES", "pt-PT"}},
{"Europe/Rome", 60, true, "Central European Time", []string{"it-IT"}},
{"Asia/Shanghai", 480, false, "China Standard Time", []string{"zh-CN"}},
{"Asia/Tokyo", 540, false, "Japan Standard Time", []string{"ja-JP"}},
{"Asia/Seoul", 540, false, "Korea Standard Time", []string{"ko-KR"}},
{"Asia/Hong_Kong", 480, false, "Hong Kong Standard Time", []string{"zh-HK", "zh-CN"}},
{"Asia/Singapore", 480, false, "Singapore Standard Time", []string{"en-SG", "zh-SG"}},
{"Asia/Dubai", 240, false, "Gulf Standard Time", []string{"ar-AE", "en-AE"}},
{"Asia/Kolkata", 330, false, "India Standard Time", []string{"hi-IN", "en-IN"}},
{"Australia/Sydney", 600, true, "Australian Eastern Standard Time", []string{"en-AU"}},
{"Pacific/Auckland", 720, true, "New Zealand Standard Time", []string{"en-NZ", "mi-NZ"}},
{"UTC", 0, false, "Coordinated Universal Time", []string{"en-US", "en-GB", "fr-FR", "de-DE"}},
}
// 随机选择时区
tz := timezones[rand.Intn(len(timezones))]
tf.timezone = tz.name
tf.offset = tz.offset
tf.dst = tz.dst
tf.timezoneName = tz.tzName
// 如果有夏令时,设置夏令时偏移
if tf.dst {
// 夏令时通常比标准时间多1小时
tf.dstOffset = tf.offset + 60
}
// 随机选择区域设置
tf.locale = tz.locales[rand.Intn(len(tz.locales))]
// 根据区域设置设置格式
tf.setRegionalFormats()
// 随机周起始日
weekStarts := []int{0, 1, 6} // Sunday, Monday, Saturday
tf.weekStart = weekStarts[rand.Intn(len(weekStarts))]
// 随机每年第一周
tf.firstDayWeek = rand.Intn(7)
return tf.Generate()
}
// setRegionalFormats 根据区域设置设置格式
func (tf *TimezoneFingerprint) setRegionalFormats() {
// 根据区域设置设置日期时间格式
switch tf.locale {
case "en-US":
tf.dateFormat = "MM/dd/yyyy"
tf.timeFormat = "h:mm:ss a"
case "en-GB":
tf.dateFormat = "dd/MM/yyyy"
tf.timeFormat = "HH:mm:ss"
case "fr-FR", "de-DE", "it-IT", "es-ES":
tf.dateFormat = "dd.MM.yyyy"
tf.timeFormat = "HH:mm:ss"
case "zh-CN", "zh-HK", "zh-SG":
tf.dateFormat = "yyyy/MM/dd"
tf.timeFormat = "HH:mm:ss"
case "ja-JP":
tf.dateFormat = "yyyy/MM/dd"
tf.timeFormat = "HH:mm:ss"
case "ko-KR":
tf.dateFormat = "yyyy.MM.dd"
tf.timeFormat = "a h:mm:ss"
case "ru-RU":
tf.dateFormat = "dd.MM.yyyy"
tf.timeFormat = "HH:mm:ss"
case "pt-BR", "pt-PT":
tf.dateFormat = "dd/MM/yyyy"
tf.timeFormat = "HH:mm:ss"
case "es-MX":
tf.dateFormat = "dd/MM/yyyy"
tf.timeFormat = "hh:mm:ss a"
case "hi-IN", "en-IN":
tf.dateFormat = "dd-MM-yyyy"
tf.timeFormat = "HH:mm:ss"
case "ar-AE":
tf.dateFormat = "dd/MM/yyyy"
tf.timeFormat = "HH:mm:ss"
default:
tf.dateFormat = "yyyy-MM-dd"
tf.timeFormat = "HH:mm:ss"
}
// 根据区域设置设置周起始日
if strings.HasPrefix(tf.locale, "en-US") || strings.HasPrefix(tf.locale, "ja-JP") || strings.HasPrefix(tf.locale, "ko-KR") {
tf.weekStart = 0 // Sunday
} else if strings.HasPrefix(tf.locale, "fr-") || strings.HasPrefix(tf.locale, "de-") || strings.HasPrefix(tf.locale, "it-") || strings.HasPrefix(tf.locale, "es-") {
tf.weekStart = 1 // Monday
} else if strings.HasPrefix(tf.locale, "zh-") {
tf.weekStart = 1 // Monday
}
}

View File

@@ -0,0 +1,181 @@
package fingerprint
import (
"crypto/md5"
"encoding/hex"
"fmt"
"math/rand"
"strings"
)
// WebGlFingerprint WebGL指纹生成器
type WebGlFingerprint struct {
vendor string
renderer string
version string
shadingLang string
extensions []string
parameters map[string]interface{}
}
// NewWebGlFingerprint 创建新的WebGL指纹生成器
func NewWebGlFingerprint() *WebGlFingerprint {
return &WebGlFingerprint{
vendor: "WebKit",
renderer: "WebKit WebGL",
version: "WebGL 1.0",
shadingLang: "WebGL GLSL ES 1.0",
extensions: []string{"ANGLE_instanced_arrays", "EXT_blend_minmax", "EXT_texture_filter_anisotropic"},
parameters: map[string]interface{}{
"VERSION": "WebGL 1.0",
"SHADING_LANGUAGE_VERSION": "WebGL GLSL ES 1.0",
"VENDOR": "WebKit",
"RENDERER": "WebKit WebGL",
"MAX_TEXTURE_SIZE": 4096,
"MAX_VIEWPORT_DIMS": []int{4096, 4096},
"MAX_RENDERBUFFER_SIZE": 4096,
"ALIASED_LINE_WIDTH_RANGE": []float32{1, 1},
"ALIASED_POINT_SIZE_RANGE": []float32{1, 1},
"MAX_TEXTURE_IMAGE_UNITS": 16,
"MAX_VERTEX_TEXTURE_IMAGE_UNITS": 0,
"MAX_VARYING_VECTORS": 8,
"MAX_VERTEX_ATTRIBS": 16,
"MAX_VERTEX_UNIFORM_VECTORS": 256,
"MAX_FRAGMENT_UNIFORM_VECTORS": 224,
},
}
}
// Generate 生成WebGL指纹
func (wf *WebGlFingerprint) Generate() string {
var fingerprint strings.Builder
// 添加基本信息
fingerprint.WriteString(fmt.Sprintf("VENDOR:%s|", wf.vendor))
fingerprint.WriteString(fmt.Sprintf("RENDERER:%s|", wf.renderer))
fingerprint.WriteString(fmt.Sprintf("VERSION:%s|", wf.version))
fingerprint.WriteString(fmt.Sprintf("SHADING_LANGUAGE_VERSION:%s|", wf.shadingLang))
// 添加扩展信息
fingerprint.WriteString("EXTENSIONS:")
for i, ext := range wf.extensions {
if i > 0 {
fingerprint.WriteString(",")
}
fingerprint.WriteString(ext)
}
fingerprint.WriteString("|")
// 添加参数信息
fingerprint.WriteString("PARAMETERS:")
paramCount := 0
for key, value := range wf.parameters {
if paramCount > 0 {
fingerprint.WriteString(",")
}
fingerprint.WriteString(fmt.Sprintf("%s=%v", key, value))
paramCount++
}
// 计算MD5哈希
hash := md5.Sum([]byte(fingerprint.String()))
return hex.EncodeToString(hash[:])
}
// GenerateRandomWebGLFingerprint 生成随机WebGL指纹
func GenerateRandomWebGLFingerprint() string {
wf := NewWebGlFingerprint()
// 随机vendor
vendors := []string{
"WebKit",
"Google Inc.",
"NVIDIA Corporation",
"AMD",
"Intel Inc.",
"Microsoft",
"Mesa/X.org",
}
wf.vendor = vendors[rand.Intn(len(vendors))]
// 随机renderer
renderers := []string{
"WebKit WebGL",
"ANGLE (Intel, Intel(R) HD Graphics Direct3D11 vs_5_0 ps_5_0)",
"GeForce GTX 1660/PCIe/SSE2",
"AMD Radeon RX 580",
"Iris(R) Xe Graphics",
"Software WebRenderer",
}
wf.renderer = renderers[rand.Intn(len(renderers))]
// 随机version
versions := []string{
"WebGL 1.0",
"WebGL 2.0",
"OpenGL ES 2.0",
"OpenGL ES 3.0",
}
wf.version = versions[rand.Intn(len(versions))]
// 随机shading language version
shadingVersions := []string{
"WebGL GLSL ES 1.0",
"WebGL GLSL ES 3.0",
"OpenGL ES GLSL ES 1.0",
"OpenGL ES GLSL ES 3.0",
}
wf.shadingLang = shadingVersions[rand.Intn(len(shadingVersions))]
// 随机扩展
allExtensions := []string{
"ANGLE_instanced_arrays",
"EXT_blend_minmax",
"EXT_texture_filter_anisotropic",
"EXT_disjoint_timer_query",
"EXT_frag_depth",
"EXT_shader_texture_lod",
"EXT_sRGB",
"OES_element_index_uint",
"OES_standard_derivatives",
"OES_texture_float",
"OES_texture_half_float",
"OES_vertex_array_object",
"WEBGL_compressed_texture_s3tc",
"WEBGL_debug_renderer_info",
"WEBGL_debug_shaders",
"WEBGL_depth_texture",
"WEBGL_lose_context",
}
// 随机选择8-15个扩展
numExtensions := 8 + rand.Intn(8)
randExtensions := make([]string, 0, numExtensions)
for i := 0; i < numExtensions && i < len(allExtensions); i++ {
ext := allExtensions[rand.Intn(len(allExtensions))]
// 避免重复
duplicate := false
for _, existing := range randExtensions {
if existing == ext {
duplicate = true
break
}
}
if !duplicate {
randExtensions = append(randExtensions, ext)
}
}
wf.extensions = randExtensions
// 随机化一些参数
wf.parameters["MAX_TEXTURE_SIZE"] = 2048 + rand.Intn(4096) // 2048-6144
wf.parameters["MAX_VIEWPORT_DIMS"] = []int{wf.parameters["MAX_TEXTURE_SIZE"].(int), wf.parameters["MAX_TEXTURE_SIZE"].(int)}
wf.parameters["MAX_RENDERBUFFER_SIZE"] = wf.parameters["MAX_TEXTURE_SIZE"].(int)
// 随机化纹理单元数量
wf.parameters["MAX_TEXTURE_IMAGE_UNITS"] = 8 + rand.Intn(16) // 8-24
wf.parameters["MAX_FRAGMENT_UNIFORM_VECTORS"] = 128 + rand.Intn(256) // 128-384
return wf.Generate()
}

View File

@@ -0,0 +1,371 @@
package useragent
import (
"fmt"
"math/rand"
"time"
)
// PlatformType 定义平台类型
type PlatformType string
const (
PlatformPC PlatformType = "pc"
PlatformMac PlatformType = "mac"
PlatformIPad PlatformType = "ipad"
PlatformIPhone PlatformType = "iphone"
PlatformAndroid PlatformType = "android"
PlatformPhone PlatformType = "phone"
PlatformAll PlatformType = "all"
)
// BrowserType 定义浏览器类型
type BrowserType string
const (
BrowserChrome BrowserType = "chrome"
BrowserFirefox BrowserType = "firefox"
BrowserSafari BrowserType = "safari"
BrowserEdge BrowserType = "edge"
)
// BrowserVersion 浏览器版本信息
type BrowserVersion struct {
Major int
Minor int
Patch int
}
// UserAgentComponent User-Agent组件
type UserAgentComponent struct {
Platform PlatformType
Browser BrowserType
Version BrowserVersion
OSVersion string
Device string
Engine string
EngineVersion string
}
// 浏览器版本范围 - 更新到最新版本
var chromeVersions = []BrowserVersion{
{140, 0, 7339}, {139, 0, 7278}, {138, 0, 7156}, {137, 0, 7012},
{136, 0, 6842}, {135, 0, 6721}, {134, 0, 6523}, {133, 0, 6413},
{132, 0, 6234}, {131, 0, 6128}, {130, 0, 5921}, {129, 0, 5845},
{128, 0, 5734}, {127, 0, 5618}, {126, 0, 5523}, {125, 0, 5421},
{124, 0, 5328}, {123, 0, 5214}, {122, 0, 5129}, {121, 0, 5018},
{120, 0, 4912}, {119, 0, 4823}, {118, 0, 4734}, {117, 0, 4621},
{116, 0, 4512}, {115, 0, 4423}, {114, 0, 4318}, {113, 0, 4217},
{112, 0, 4123}, {111, 0, 4028}, {110, 0, 3921}, {109, 0, 3814},
}
var firefoxVersions = []BrowserVersion{
{140, 0, 0}, {139, 0, 0}, {138, 0, 0}, {137, 0, 0}, {136, 0, 0},
{135, 0, 0}, {134, 0, 0}, {133, 0, 0}, {132, 0, 0}, {131, 0, 0},
{130, 0, 0}, {129, 0, 0}, {128, 0, 0}, {127, 0, 0}, {126, 0, 0},
{125, 0, 0}, {124, 0, 0}, {123, 0, 0}, {122, 0, 0}, {121, 0, 0},
{120, 0, 0}, {119, 0, 0}, {118, 0, 0}, {117, 0, 0}, {116, 0, 0},
{115, 0, 0}, {114, 0, 0}, {113, 0, 0}, {112, 0, 0}, {111, 0, 0},
{110, 0, 0}, {109, 0, 0},
}
var safariVersions = []BrowserVersion{
{18, 2, 0}, {18, 1, 0}, {18, 0, 0}, {17, 6, 0}, {17, 5, 0},
{17, 4, 0}, {17, 3, 0}, {17, 2, 0}, {17, 1, 0}, {17, 0, 0},
{16, 6, 0}, {16, 5, 0}, {16, 4, 0}, {16, 3, 0}, {16, 2, 0},
{16, 1, 0}, {16, 0, 0}, {15, 6, 0}, {15, 5, 0}, {15, 4, 0},
}
var edgeVersions = []BrowserVersion{
{140, 0, 7339}, {139, 0, 7278}, {138, 0, 7156}, {137, 0, 7012},
{136, 0, 6842}, {135, 0, 6721}, {134, 0, 6523}, {133, 0, 6413},
{132, 0, 6234}, {131, 0, 6128}, {130, 0, 5921}, {129, 0, 5845},
{128, 0, 5734}, {127, 0, 5618}, {126, 0, 5523}, {125, 0, 5421},
{124, 0, 5328}, {123, 0, 5214}, {122, 0, 5129}, {121, 0, 5018},
{120, 0, 4912}, {119, 0, 4823}, {118, 0, 4734}, {117, 0, 4621},
{116, 0, 4512}, {115, 0, 4423}, {114, 0, 4318}, {113, 0, 4217},
}
// 平台和设备信息
var windowsVersions = []string{
"Windows NT 10.0", "Windows NT 11.0",
}
var macVersions = []string{
"Mac OS X 10_15_7", "Mac OS X 11_7_10", "Mac OS X 12_6_8", "Mac OS X 13_5_2",
}
var iosVersions = []string{
"17_4", "17_3", "17_2", "17_1", "17_0", "16_6", "16_5", "16_4", "16_3", "16_2",
}
var androidVersions = []string{
"14", "13", "12", "11", "10",
}
var androidDevices = []string{
"SM-G998B", "SM-G991B", "SM-G975F", "SM-S906N", "Pixel 7", "Pixel 6", "Pixel 5",
"OnePlus 9", "OnePlus 8", "Xiaomi 12", "Xiaomi 11", "OPPO Find X5", "vivo X80",
}
var iphoneDevices = []string{
"iPhone15,3", "iPhone15,2", "iPhone14,7", "iPhone14,6", "iPhone14,5", "iPhone14,4",
"iPhone14,3", "iPhone14,2", "iPhone13,4", "iPhone13,3", "iPhone13,2", "iPhone13,1",
"iPhone12,8", "iPhone12,5", "iPhone12,3", "iPhone12,1", "iPhone11,8", "iPhone11,6",
"iPhone11,4", "iPhone11,2", "iPhone10,6", "iPhone10,5", "iPhone10,4", "iPhone10,3",
"iPhone10,2", "iPhone9,4", "iPhone9,3", "iPhone9,2", "iPhone9,1", "iPhone8,4",
}
// Initialize random seed
func init() {
rand.Seed(time.Now().UnixNano())
}
// GetRandomBrowserVersion 获取随机浏览器版本
func GetRandomBrowserVersion(browser BrowserType) BrowserVersion {
switch browser {
case BrowserChrome:
return chromeVersions[rand.Intn(len(chromeVersions))]
case BrowserFirefox:
return firefoxVersions[rand.Intn(len(firefoxVersions))]
case BrowserSafari:
return safariVersions[rand.Intn(len(safariVersions))]
case BrowserEdge:
return edgeVersions[rand.Intn(len(edgeVersions))]
default:
return chromeVersions[rand.Intn(len(chromeVersions))]
}
}
// FormatVersion 格式化版本号
func FormatVersion(version BrowserVersion) string {
if version.Patch > 0 {
return fmt.Sprintf("%d.%d.%d", version.Major, version.Minor, version.Patch)
}
return fmt.Sprintf("%d.%d", version.Major, version.Minor)
}
// GetRandomPlatformInfo 获取随机平台信息
func GetRandomPlatformInfo(platform PlatformType) string {
switch platform {
case PlatformPC:
return windowsVersions[rand.Intn(len(windowsVersions))]
case PlatformMac:
return macVersions[rand.Intn(len(macVersions))]
case PlatformIPad:
return iosVersions[rand.Intn(len(iosVersions))]
case PlatformIPhone:
return iosVersions[rand.Intn(len(iosVersions))]
case PlatformAndroid:
return androidVersions[rand.Intn(len(androidVersions))]
default:
return windowsVersions[0]
}
}
// GetRandomDevice 获取随机设备信息
func GetRandomDevice(platform PlatformType) string {
switch platform {
case PlatformAndroid:
return androidDevices[rand.Intn(len(androidDevices))]
case PlatformIPhone:
return iphoneDevices[rand.Intn(len(iphoneDevices))]
default:
return ""
}
}
// BuildChromeUserAgent 构建Chrome User-Agent
func BuildChromeUserAgent(component UserAgentComponent) string {
switch component.Platform {
case PlatformPC:
return fmt.Sprintf("Mozilla/5.0 (%s; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Safari/537.36",
component.OSVersion, FormatVersion(component.Version))
case PlatformMac:
return fmt.Sprintf("Mozilla/5.0 (Macintosh; Intel Mac OS X %s) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Safari/537.36",
component.OSVersion, FormatVersion(component.Version))
case PlatformAndroid:
return fmt.Sprintf("Mozilla/5.0 (Linux; Android %s; %s) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Mobile Safari/537.36",
component.OSVersion, component.Device, FormatVersion(component.Version))
case PlatformIPhone:
return fmt.Sprintf("Mozilla/5.0 (iPhone; CPU iPhone OS %s like Mac OS X) AppleWebKit/537.36 (KHTML, like Gecko) CriOS/%s Mobile Safari/537.36",
component.OSVersion, FormatVersion(component.Version))
default:
return fmt.Sprintf("Mozilla/5.0 (Unknown) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Safari/537.36",
FormatVersion(component.Version))
}
}
// BuildFirefoxUserAgent 构建Firefox User-Agent
func BuildFirefoxUserAgent(component UserAgentComponent) string {
switch component.Platform {
case PlatformPC:
return fmt.Sprintf("Mozilla/5.0 (%s; Win64; x64; rv:%d.0) Gecko/20100101 Firefox/%d.0",
component.OSVersion, component.Version.Major, component.Version.Major)
case PlatformMac:
return fmt.Sprintf("Mozilla/5.0 (Macintosh; Intel Mac OS X %s; rv:%d.0) Gecko/20100101 Firefox/%d.0",
component.OSVersion, component.Version.Major, component.Version.Major)
case PlatformAndroid:
return fmt.Sprintf("Mozilla/5.0 (Android %s; Mobile; rv:%d.0) Gecko/%d.0 Firefox/%d.0",
component.OSVersion, component.Version.Major, component.Version.Major, component.Version.Major)
case PlatformIPhone:
return fmt.Sprintf("Mozilla/5.0 (iPhone; CPU iPhone OS %s like Mac OS X; rv:%d.0) Gecko/%d.0 Firefox/%d.0",
component.OSVersion, component.Version.Major, component.Version.Major, component.Version.Major)
default:
return fmt.Sprintf("Mozilla/5.0 (Unknown; rv:%d.0) Gecko/20100101 Firefox/%d.0",
component.Version.Major, component.Version.Major)
}
}
// BuildSafariUserAgent 构建Safari User-Agent
func BuildSafariUserAgent(component UserAgentComponent) string {
switch component.Platform {
case PlatformMac:
return fmt.Sprintf("Mozilla/5.0 (Macintosh; Intel Mac OS X %s) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/%s Safari/605.1.15",
component.OSVersion, FormatVersion(component.Version))
case PlatformIPad:
return fmt.Sprintf("Mozilla/5.0 (iPad; CPU OS %s like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/%s Mobile/15E148 Safari/604.1",
component.OSVersion, FormatVersion(component.Version))
case PlatformIPhone:
return fmt.Sprintf("Mozilla/5.0 (iPhone; CPU iPhone OS %s like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/%s Mobile/15E148 Safari/604.1",
component.OSVersion, FormatVersion(component.Version))
default:
return fmt.Sprintf("Mozilla/5.0 (Unknown) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/%s Safari/605.1.15",
FormatVersion(component.Version))
}
}
// BuildEdgeUserAgent 构建Edge User-Agent
func BuildEdgeUserAgent(component UserAgentComponent) string {
switch component.Platform {
case PlatformPC:
return fmt.Sprintf("Mozilla/5.0 (%s; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Safari/537.36 Edg/%s",
component.OSVersion, FormatVersion(component.Version), FormatVersion(component.Version))
case PlatformMac:
return fmt.Sprintf("Mozilla/5.0 (Macintosh; Intel Mac OS X %s) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Safari/537.36 Edg/%s",
component.OSVersion, FormatVersion(component.Version), FormatVersion(component.Version))
default:
return fmt.Sprintf("Mozilla/5.0 (Unknown) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Safari/537.36 Edg/%s",
FormatVersion(component.Version), FormatVersion(component.Version))
}
}
// BuildUserAgent 构建User-Agent字符串
func BuildUserAgent(component UserAgentComponent) string {
switch component.Browser {
case BrowserChrome:
return BuildChromeUserAgent(component)
case BrowserFirefox:
return BuildFirefoxUserAgent(component)
case BrowserSafari:
return BuildSafariUserAgent(component)
case BrowserEdge:
return BuildEdgeUserAgent(component)
default:
return BuildChromeUserAgent(component)
}
}
// GetRandomBrowser 获取随机浏览器类型
func GetRandomBrowser() BrowserType {
browsers := []BrowserType{BrowserChrome, BrowserFirefox, BrowserSafari, BrowserEdge}
return browsers[rand.Intn(len(browsers))]
}
// GetRandomUserAgent 获取指定平台的随机User-Agent
func GetRandomUserAgent(platform PlatformType) string {
component := UserAgentComponent{
Platform: platform,
Browser: GetRandomBrowser(),
OSVersion: GetRandomPlatformInfo(platform),
Device: GetRandomDevice(platform),
}
component.Version = GetRandomBrowserVersion(component.Browser)
return BuildUserAgent(component)
}
// GetRandomUserAgentFromPlatforms 从指定平台列表中获取随机User-Agent
func GetRandomUserAgentFromPlatforms(platforms []PlatformType) string {
if len(platforms) == 0 {
return GetRandomUserAgent(PlatformPC)
}
selectedPlatform := platforms[rand.Intn(len(platforms))]
return GetRandomUserAgent(selectedPlatform)
}
// GetAllRandomUserAgent 获取所有平台的随机User-Agent按权重分配
func GetAllRandomUserAgent() string {
platforms := []PlatformType{PlatformPC, PlatformMac, PlatformIPad, PlatformIPhone, PlatformAndroid}
// 权重分配PC 35%, Mac 25%, iPad 10%, iPhone 10%, Android 20%
weights := []int{35, 25, 10, 10, 20}
totalWeight := 0
for _, weight := range weights {
totalWeight += weight
}
randomWeight := rand.Intn(totalWeight)
cumulativeWeight := 0
for i, weight := range weights {
cumulativeWeight += weight
if randomWeight < cumulativeWeight {
return GetRandomUserAgent(platforms[i])
}
}
return GetRandomUserAgent(PlatformPC)
}
// GetUserAgentByPlatform 根据平台枚举获取User-Agent
func GetUserAgentByPlatform(platform PlatformType) string {
switch platform {
case PlatformPC:
return GetRandomUserAgent(PlatformPC)
case PlatformMac:
return GetRandomUserAgent(PlatformMac)
case PlatformIPad:
return GetRandomUserAgent(PlatformIPad)
case PlatformAndroid:
return GetRandomUserAgent(PlatformAndroid)
case PlatformPhone:
if rand.Int() > 50 {
return GetRandomUserAgent(PlatformIPhone)
} else {
return GetRandomUserAgent(PlatformAndroid)
}
default:
return GetAllRandomUserAgent()
}
}
// GetUserAgentByBrowser 根据浏览器类型获取随机User-Agent
func GetUserAgentByBrowser(browser BrowserType) string {
platforms := []PlatformType{PlatformPC, PlatformMac, PlatformIPad, PlatformIPhone, PlatformAndroid}
// 根据浏览器类型排除不兼容的平台
var compatiblePlatforms []PlatformType
switch browser {
case BrowserSafari:
compatiblePlatforms = []PlatformType{PlatformMac, PlatformIPad, PlatformIPhone}
case BrowserEdge:
compatiblePlatforms = []PlatformType{PlatformPC, PlatformMac}
default:
compatiblePlatforms = platforms
}
selectedPlatform := compatiblePlatforms[rand.Intn(len(compatiblePlatforms))]
component := UserAgentComponent{
Platform: selectedPlatform,
Browser: browser,
OSVersion: GetRandomPlatformInfo(selectedPlatform),
Device: GetRandomDevice(selectedPlatform),
Version: GetRandomBrowserVersion(browser),
}
return BuildUserAgent(component)
}