init
This commit is contained in:
17
.gitignore
vendored
Normal file
17
.gitignore
vendored
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
.DS_Store
|
||||||
|
node_modules/
|
||||||
|
/dist/
|
||||||
|
/user-data/
|
||||||
|
/log/
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
config.yaml
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.idea
|
||||||
|
.vscode
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
134
api/task.go
Normal file
134
api/task.go
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"go-account-register/libs"
|
||||||
|
"go-account-register/task"
|
||||||
|
paramsTypes "go-account-register/types"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Task struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreateRequestParams struct {
|
||||||
|
ID string `form:"id" json:"id" uri:"id" xml:"id"`
|
||||||
|
Name string `form:"name" json:"name" uri:"name" xml:"name"`
|
||||||
|
RunStatus int `form:"runStatus" json:"runStatus" uri:"runStatus" xml:"runStatus" binding:"required"`
|
||||||
|
Type int `form:"type" json:"type" uri:"type" xml:"type" binding:"required"`
|
||||||
|
AccountClassify string `form:"accountClassify" json:"accountClassify" uri:"accountClassify" xml:"accountClassify" binding:"required"`
|
||||||
|
ClassifyId []int `form:"classifyId" json:"classifyId" uri:"classifyId" xml:"classifyId"`
|
||||||
|
Keyword string `form:"keyword" json:"keyword" uri:"keyword" xml:"keyword"`
|
||||||
|
TwitterId string `form:"twitterId" json:"twitterId" uri:"twitterId" xml:"twitterId"`
|
||||||
|
Compensate int `form:"compensate" json:"compensate" uri:"compensate" xml:"compensate"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type StartRequestParams struct {
|
||||||
|
TaskId string `form:"taskId" json:"taskId" uri:"taskId" xml:"taskId" binding:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建任务
|
||||||
|
func (*Task) Create(ctx *gin.Context) {
|
||||||
|
// 参数
|
||||||
|
var res *libs.ErrorInfo
|
||||||
|
var paramsJson CreateRequestParams
|
||||||
|
if err := ctx.ShouldBindJSON(¶msJson); err != nil {
|
||||||
|
res = libs.ErrorCode["ParamsError"]
|
||||||
|
fmt.Println(err)
|
||||||
|
ctx.JSON(http.StatusBadRequest, gin.H{
|
||||||
|
"code": res.Code,
|
||||||
|
"msg": res.Msg,
|
||||||
|
"data": res.Data,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var scheduler = task.GetScheduler()
|
||||||
|
var status string
|
||||||
|
if paramsJson.RunStatus == 0 {
|
||||||
|
status = "stop"
|
||||||
|
} else {
|
||||||
|
status = "pending"
|
||||||
|
}
|
||||||
|
taskId := scheduler.AddTask(¶msTypes.TaskParams{
|
||||||
|
ID: paramsJson.ID,
|
||||||
|
Name: paramsJson.Name,
|
||||||
|
Status: status,
|
||||||
|
Type: paramsJson.Type,
|
||||||
|
AccountClassify: paramsJson.AccountClassify,
|
||||||
|
DataClassify: paramsJson.ClassifyId,
|
||||||
|
Keyword: paramsJson.Keyword,
|
||||||
|
TwitterId: paramsJson.TwitterId,
|
||||||
|
Compensate: paramsJson.Compensate,
|
||||||
|
})
|
||||||
|
ctx.JSON(http.StatusBadRequest, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "成功",
|
||||||
|
"data": taskId,
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// 开始任务
|
||||||
|
func (*Task) Start(ctx *gin.Context) {
|
||||||
|
// 参数
|
||||||
|
var res *libs.ErrorInfo
|
||||||
|
var paramsJson StartRequestParams
|
||||||
|
if err := ctx.ShouldBindJSON(¶msJson); err != nil {
|
||||||
|
res = libs.ErrorCode["ParamsError"]
|
||||||
|
fmt.Println(err)
|
||||||
|
ctx.JSON(http.StatusBadRequest, gin.H{
|
||||||
|
"code": res.Code,
|
||||||
|
"msg": res.Msg,
|
||||||
|
"data": res.Data,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 暂停任务
|
||||||
|
func (*Task) Stop(ctx *gin.Context) {
|
||||||
|
// 参数
|
||||||
|
var res *libs.ErrorInfo
|
||||||
|
var paramsJson StartRequestParams
|
||||||
|
if err := ctx.ShouldBindJSON(¶msJson); err != nil {
|
||||||
|
res = libs.ErrorCode["ParamsError"]
|
||||||
|
fmt.Println(err)
|
||||||
|
ctx.JSON(http.StatusBadRequest, gin.H{
|
||||||
|
"code": res.Code,
|
||||||
|
"msg": res.Msg,
|
||||||
|
"data": res.Data,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var scheduler = task.GetScheduler()
|
||||||
|
scheduler.StopTask(paramsJson.TaskId)
|
||||||
|
ctx.JSON(http.StatusBadRequest, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "成功",
|
||||||
|
"data": "",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除任务
|
||||||
|
func (*Task) Delete(ctx *gin.Context) {
|
||||||
|
// 参数
|
||||||
|
var res *libs.ErrorInfo
|
||||||
|
var paramsJson StartRequestParams
|
||||||
|
if err := ctx.ShouldBindJSON(¶msJson); err != nil {
|
||||||
|
res = libs.ErrorCode["ParamsError"]
|
||||||
|
fmt.Println(err)
|
||||||
|
ctx.JSON(http.StatusBadRequest, gin.H{
|
||||||
|
"code": res.Code,
|
||||||
|
"msg": res.Msg,
|
||||||
|
"data": res.Data,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取任务信息
|
||||||
|
func (*Task) GetTaskInfo(ctx *gin.Context) {
|
||||||
|
|
||||||
|
}
|
||||||
43
cmd/main.go
Normal file
43
cmd/main.go
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"go-account-register/config"
|
||||||
|
"go-account-register/router"
|
||||||
|
"go-account-register/utils"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"strconv"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
appConfig, err := config.LoadConfig()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
fmt.Println("读取配置失败")
|
||||||
|
}
|
||||||
|
app := gin.Default()
|
||||||
|
// 注册路由
|
||||||
|
router.Init(app)
|
||||||
|
utils.Connect()
|
||||||
|
go handleShutdownSignals()
|
||||||
|
|
||||||
|
app.Run(":" + strconv.Itoa(appConfig.Port))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理关闭信号的函数
|
||||||
|
func handleShutdownSignals() {
|
||||||
|
sigChan := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
|
||||||
|
|
||||||
|
// 阻塞等待信号
|
||||||
|
sig := <-sigChan
|
||||||
|
log.Printf("接收到信号: %v, 关闭所有浏览器...", sig)
|
||||||
|
|
||||||
|
// 强制退出程序
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
57
config/index.go
Normal file
57
config/index.go
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DatabaseConfig struct {
|
||||||
|
Username string `yaml:"username"`
|
||||||
|
Password string `yaml:"password"`
|
||||||
|
Host string `yaml:"host"`
|
||||||
|
Port int `yaml:"port"`
|
||||||
|
Database string `yaml:"database"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Limit struct {
|
||||||
|
CommentNumMin int `yaml:"commentNumMin"`
|
||||||
|
CommentNumMax int `yaml:"commentNumMax"`
|
||||||
|
LikeNumMin int `yaml:"likeNumMin"`
|
||||||
|
LikeNumMax int `yaml:"likeNumMax"`
|
||||||
|
FollowNumMin int `yaml:"followNumMin"`
|
||||||
|
FollowNumMax int `yaml:"followNumMax"`
|
||||||
|
ViweTimeMax int `yaml:"viweTimeMax"`
|
||||||
|
ViweTimeMin int `yaml:"viweTimeMin"`
|
||||||
|
BrowserRunMax int `yaml:"browserRunMax"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Clash struct {
|
||||||
|
Host string `yaml:"host"`
|
||||||
|
Port int `yaml:"port"`
|
||||||
|
Secret string `yaml:"secret"`
|
||||||
|
ExePath string `yaml:"exePath"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AppConfig struct {
|
||||||
|
Port int `yaml:"port"`
|
||||||
|
Database DatabaseConfig `yaml:"database"`
|
||||||
|
Limit Limit `yaml:"limit"`
|
||||||
|
Clash Clash `yaml:"clash"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func LoadConfig() (*AppConfig, error) {
|
||||||
|
// 读取文件内容
|
||||||
|
data, err := os.ReadFile("config.yaml")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析 YAML
|
||||||
|
var cfg AppConfig
|
||||||
|
if err := yaml.Unmarshal(data, &cfg); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &cfg, nil
|
||||||
|
}
|
||||||
BIN
fonts/AlibabaPuHuiTi-3-115-Black.ttf
Normal file
BIN
fonts/AlibabaPuHuiTi-3-115-Black.ttf
Normal file
Binary file not shown.
57
go.mod
Normal file
57
go.mod
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
module go-account-register
|
||||||
|
|
||||||
|
go 1.24.3
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/fatih/structs v1.1.0
|
||||||
|
github.com/gin-gonic/gin v1.10.1
|
||||||
|
github.com/go-resty/resty/v2 v2.16.5
|
||||||
|
github.com/go-rod/rod v0.116.2
|
||||||
|
github.com/go-rod/stealth v0.4.9
|
||||||
|
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0
|
||||||
|
github.com/google/uuid v1.6.0
|
||||||
|
github.com/redis/go-redis/v9 v9.14.0
|
||||||
|
golang.org/x/image v0.31.0
|
||||||
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
|
gorm.io/driver/mysql v1.6.0
|
||||||
|
gorm.io/gorm v1.31.0
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
filippo.io/edwards25519 v1.1.0 // indirect
|
||||||
|
github.com/bytedance/sonic v1.11.6 // indirect
|
||||||
|
github.com/bytedance/sonic/loader v0.1.1 // indirect
|
||||||
|
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||||
|
github.com/cloudwego/base64x v0.1.4 // indirect
|
||||||
|
github.com/cloudwego/iasm v0.2.0 // indirect
|
||||||
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
||||||
|
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||||
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
|
github.com/go-playground/validator/v10 v10.20.0 // indirect
|
||||||
|
github.com/go-sql-driver/mysql v1.8.1 // indirect
|
||||||
|
github.com/goccy/go-json v0.10.2 // indirect
|
||||||
|
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||||
|
github.com/jinzhu/now v1.1.5 // indirect
|
||||||
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
|
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
|
||||||
|
github.com/leodido/go-urn v1.4.0 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
|
||||||
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
|
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||||
|
github.com/ysmood/fetchup v0.2.3 // indirect
|
||||||
|
github.com/ysmood/goob v0.4.0 // indirect
|
||||||
|
github.com/ysmood/got v0.40.0 // indirect
|
||||||
|
github.com/ysmood/gson v0.7.3 // indirect
|
||||||
|
github.com/ysmood/leakless v0.9.0 // indirect
|
||||||
|
golang.org/x/arch v0.8.0 // indirect
|
||||||
|
golang.org/x/crypto v0.31.0 // indirect
|
||||||
|
golang.org/x/net v0.33.0 // indirect
|
||||||
|
golang.org/x/sys v0.28.0 // indirect
|
||||||
|
golang.org/x/text v0.29.0 // indirect
|
||||||
|
google.golang.org/protobuf v1.34.1 // indirect
|
||||||
|
)
|
||||||
145
go.sum
Normal file
145
go.sum
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||||
|
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||||
|
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
||||||
|
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
|
||||||
|
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
|
||||||
|
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
|
||||||
|
github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
|
||||||
|
github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
|
||||||
|
github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
|
||||||
|
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||||
|
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||||
|
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
|
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
|
||||||
|
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
||||||
|
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
|
||||||
|
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||||
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||||
|
github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
|
||||||
|
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
|
||||||
|
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||||
|
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||||
|
github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ=
|
||||||
|
github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
||||||
|
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||||
|
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||||
|
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||||
|
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||||
|
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||||
|
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||||
|
github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8=
|
||||||
|
github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
||||||
|
github.com/go-resty/resty/v2 v2.16.5 h1:hBKqmWrr7uRc3euHVqmh1HTHcKn99Smr7o5spptdhTM=
|
||||||
|
github.com/go-resty/resty/v2 v2.16.5/go.mod h1:hkJtXbA2iKHzJheXYvQ8snQES5ZLGKMwQ07xAwp/fiA=
|
||||||
|
github.com/go-rod/rod v0.113.0/go.mod h1:aiedSEFg5DwG/fnNbUOTPMTTWX3MRj6vIs/a684Mthw=
|
||||||
|
github.com/go-rod/rod v0.116.2 h1:A5t2Ky2A+5eD/ZJQr1EfsQSe5rms5Xof/qj296e+ZqA=
|
||||||
|
github.com/go-rod/rod v0.116.2/go.mod h1:H+CMO9SCNc2TJ2WfrG+pKhITz57uGNYU43qYHh438Mg=
|
||||||
|
github.com/go-rod/stealth v0.4.9 h1:X2PmQk4DUF2wzw6GOsWjW/glb8K5ebnftbEvLh7MlZ4=
|
||||||
|
github.com/go-rod/stealth v0.4.9/go.mod h1:eAzyvw8c0iAd5nJJsSWeh0fQ5z94vCIfdi1hUmYDimc=
|
||||||
|
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
|
||||||
|
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
||||||
|
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||||
|
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||||
|
github.com/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/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
||||||
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||||
|
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||||
|
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||||
|
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||||
|
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
|
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||||
|
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
|
||||||
|
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||||
|
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
||||||
|
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||||
|
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||||
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||||
|
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/redis/go-redis/v9 v9.14.0 h1:u4tNCjXOyzfgeLN+vAZaW1xUooqWDqVEsZN0U01jfAE=
|
||||||
|
github.com/redis/go-redis/v9 v9.14.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
|
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||||
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
|
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||||
|
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
|
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||||
|
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||||
|
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
|
||||||
|
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||||
|
github.com/ysmood/fetchup v0.2.3 h1:ulX+SonA0Vma5zUFXtv52Kzip/xe7aj4vqT5AJwQ+ZQ=
|
||||||
|
github.com/ysmood/fetchup v0.2.3/go.mod h1:xhibcRKziSvol0H1/pj33dnKrYyI2ebIvz5cOOkYGns=
|
||||||
|
github.com/ysmood/goob v0.4.0 h1:HsxXhyLBeGzWXnqVKtmT9qM7EuVs/XOgkX7T6r1o1AQ=
|
||||||
|
github.com/ysmood/goob v0.4.0/go.mod h1:u6yx7ZhS4Exf2MwciFr6nIM8knHQIE22lFpWHnfql18=
|
||||||
|
github.com/ysmood/gop v0.0.2/go.mod h1:rr5z2z27oGEbyB787hpEcx4ab8cCiPnKxn0SUHt6xzk=
|
||||||
|
github.com/ysmood/gop v0.2.0 h1:+tFrG0TWPxT6p9ZaZs+VY+opCvHU8/3Fk6BaNv6kqKg=
|
||||||
|
github.com/ysmood/gop v0.2.0/go.mod h1:rr5z2z27oGEbyB787hpEcx4ab8cCiPnKxn0SUHt6xzk=
|
||||||
|
github.com/ysmood/got v0.34.1/go.mod h1:yddyjq/PmAf08RMLSwDjPyCvHvYed+WjHnQxpH851LM=
|
||||||
|
github.com/ysmood/got v0.40.0 h1:ZQk1B55zIvS7zflRrkGfPDrPG3d7+JOza1ZkNxcc74Q=
|
||||||
|
github.com/ysmood/got v0.40.0/go.mod h1:W7DdpuX6skL3NszLmAsC5hT7JAhuLZhByVzHTq874Qg=
|
||||||
|
github.com/ysmood/gotrace v0.6.0 h1:SyI1d4jclswLhg7SWTL6os3L1WOKeNn/ZtzVQF8QmdY=
|
||||||
|
github.com/ysmood/gotrace v0.6.0/go.mod h1:TzhIG7nHDry5//eYZDYcTzuJLYQIkykJzCRIo4/dzQM=
|
||||||
|
github.com/ysmood/gson v0.7.3 h1:QFkWbTH8MxyUTKPkVWAENJhxqdBa4lYTQWqZCiLG6kE=
|
||||||
|
github.com/ysmood/gson v0.7.3/go.mod h1:3Kzs5zDl21g5F/BlLTNcuAGAYLKt2lV5G8D1zF3RNmg=
|
||||||
|
github.com/ysmood/leakless v0.8.0/go.mod h1:R8iAXPRaG97QJwqxs74RdwzcRHT1SWCGTNqY8q0JvMQ=
|
||||||
|
github.com/ysmood/leakless v0.9.0 h1:qxCG5VirSBvmi3uynXFkcnLMzkphdh3xx5FtrORwDCU=
|
||||||
|
github.com/ysmood/leakless v0.9.0/go.mod h1:R8iAXPRaG97QJwqxs74RdwzcRHT1SWCGTNqY8q0JvMQ=
|
||||||
|
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||||
|
golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
|
||||||
|
golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
||||||
|
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
|
||||||
|
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||||
|
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/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
|
||||||
|
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
||||||
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
|
||||||
|
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
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/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
|
||||||
|
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gorm.io/driver/mysql v1.6.0 h1:eNbLmNTpPpTOVZi8MMxCi2aaIm0ZpInbORNXDwyLGvg=
|
||||||
|
gorm.io/driver/mysql v1.6.0/go.mod h1:D/oCC2GWK3M/dqoLxnOlaNKmXz8WNTfcS9y5ovaSqKo=
|
||||||
|
gorm.io/gorm v1.31.0 h1:0VlycGreVhK7RF/Bwt51Fk8v0xLiiiFdbGDPIZQ7mJY=
|
||||||
|
gorm.io/gorm v1.31.0/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs=
|
||||||
|
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
||||||
|
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
||||||
65
libs/browser-fingerprint/canvas.go
Normal file
65
libs/browser-fingerprint/canvas.go
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
package browserfingerprint
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
func GetChangeCanvasJavaScript(rgba [4]int) string {
|
||||||
|
return fmt.Sprintf(`
|
||||||
|
const getImageData = CanvasRenderingContext2D.prototype.getImageData;
|
||||||
|
|
||||||
|
const noisify = (canvas, context) => {
|
||||||
|
if (context) {
|
||||||
|
const shift = {
|
||||||
|
r: %d,
|
||||||
|
g: %d,
|
||||||
|
b: %d,
|
||||||
|
a: %d,
|
||||||
|
};
|
||||||
|
const width = canvas.width;
|
||||||
|
const height = canvas.height;
|
||||||
|
|
||||||
|
if (width && height) {
|
||||||
|
const imageData = getImageData.apply(context, [0, 0, width, height]);
|
||||||
|
|
||||||
|
for (let i = 0; i < height; i++)
|
||||||
|
for (let j = 0; j < width; j++) {
|
||||||
|
const n = i * (width * 4) + j * 4;
|
||||||
|
imageData.data[n + 0] = imageData.data[n + 0] + shift.r;
|
||||||
|
imageData.data[n + 1] = imageData.data[n + 1] + shift.g;
|
||||||
|
imageData.data[n + 2] = imageData.data[n + 2] + shift.b;
|
||||||
|
imageData.data[n + 3] = imageData.data[n + 3] + shift.a;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
context.putImageData(imageData, 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
HTMLCanvasElement.prototype.toBlob = new Proxy(HTMLCanvasElement.prototype.toBlob, {
|
||||||
|
apply(target, self, args) {
|
||||||
|
noisify(self, self.getContext('2d'));
|
||||||
|
|
||||||
|
return Reflect.apply(target, self, args);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
HTMLCanvasElement.prototype.toDataURL = new Proxy(HTMLCanvasElement.prototype.toDataURL, {
|
||||||
|
apply(target, self, args) {
|
||||||
|
noisify(self, self.getContext('2d'));
|
||||||
|
|
||||||
|
return Reflect.apply(target, self, args);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
CanvasRenderingContext2D.prototype.getImageData = new Proxy(
|
||||||
|
CanvasRenderingContext2D.prototype.getImageData,
|
||||||
|
{
|
||||||
|
apply(target, self, args) {
|
||||||
|
noisify(self.canvas, self);
|
||||||
|
|
||||||
|
return Reflect.apply(target, self, args);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
`, rgba[0], rgba[1], rgba[2], rgba[3])
|
||||||
|
}
|
||||||
42
libs/browser-fingerprint/index.go
Normal file
42
libs/browser-fingerprint/index.go
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
package browserfingerprint
|
||||||
|
|
||||||
|
import (
|
||||||
|
paramsTypes "go-account-register/types"
|
||||||
|
"math/rand"
|
||||||
|
|
||||||
|
"github.com/go-rod/rod"
|
||||||
|
)
|
||||||
|
|
||||||
|
type BrowserFingerprint struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BrowserFingerprint) SetBrowserFingerprint(page *rod.Page, params *paramsTypes.BrowserFingerprintParams) *rod.Page {
|
||||||
|
var p *rod.Page = page
|
||||||
|
if params.Canvas {
|
||||||
|
p = setCanvas(p)
|
||||||
|
}
|
||||||
|
if params.TimeZone != "" {
|
||||||
|
p = setTimezoneAndLangAndGeo(p, params)
|
||||||
|
}
|
||||||
|
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func setCanvas(page *rod.Page) *rod.Page {
|
||||||
|
// 注入噪声脚本
|
||||||
|
// 生成随机 RGBA 偏移值 (Go 端控制)
|
||||||
|
rgba := [4]int{
|
||||||
|
rand.Intn(10) - 5, // R: -5 ~ +4
|
||||||
|
rand.Intn(10) - 5, // G
|
||||||
|
rand.Intn(10) - 5, // B
|
||||||
|
rand.Intn(10) - 5, // A
|
||||||
|
}
|
||||||
|
// 使用更隐蔽的 Canvas 指纹修改方法
|
||||||
|
page.MustEvalOnNewDocument(GetChangeCanvasJavaScript(rgba))
|
||||||
|
return page
|
||||||
|
}
|
||||||
|
|
||||||
|
func setTimezoneAndLangAndGeo(page *rod.Page, params *paramsTypes.BrowserFingerprintParams) *rod.Page {
|
||||||
|
page.MustEvalOnNewDocument(GetChangeTimezoneJavaScript(params.TimeZone, params.Language, params.GeoLocation))
|
||||||
|
return page
|
||||||
|
}
|
||||||
98
libs/browser-fingerprint/timezone.go
Normal file
98
libs/browser-fingerprint/timezone.go
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
package browserfingerprint
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
_ "time/tzdata"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetChangeTimezoneJavaScript(timezone string, lang string, geo string) string {
|
||||||
|
return fmt.Sprintf(`
|
||||||
|
(() => {
|
||||||
|
// 设置全局时区配置
|
||||||
|
window.__rodTZConfig = {
|
||||||
|
timeZone: '%s',
|
||||||
|
language: '%s',
|
||||||
|
geo: '%s'
|
||||||
|
};
|
||||||
|
|
||||||
|
// 覆盖 Date 对象
|
||||||
|
const OriginalDate = window.Date;
|
||||||
|
window.Date = function(...args) {
|
||||||
|
if (new.target) {
|
||||||
|
// 作为构造函数
|
||||||
|
if (args.length === 0) {
|
||||||
|
// 无参数调用时应用时区偏移
|
||||||
|
const now = new OriginalDate();
|
||||||
|
const targetOffset = -480; // %s 的 UTC 偏移(分钟)
|
||||||
|
return new OriginalDate(now.getTime() + (targetOffset - now.getTimezoneOffset()) * 60000);
|
||||||
|
}
|
||||||
|
return new OriginalDate(...args);
|
||||||
|
}
|
||||||
|
// 作为函数调用
|
||||||
|
return OriginalDate();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 复制静态方法和属性
|
||||||
|
Object.defineProperties(Date, Object.getOwnPropertyDescriptors(OriginalDate));
|
||||||
|
|
||||||
|
// 覆盖 Date.now()
|
||||||
|
const origDateNow = Date.now;
|
||||||
|
Date.now = function() {
|
||||||
|
return origDateNow() + (%d * 60000); // 添加时区偏移
|
||||||
|
};
|
||||||
|
|
||||||
|
// 覆盖 getTimezoneOffset
|
||||||
|
const origGetTimezoneOffset = Date.prototype.getTimezoneOffset;
|
||||||
|
Date.prototype.getTimezoneOffset = function() {
|
||||||
|
return %d; // %s 的 UTC 偏移(分钟)
|
||||||
|
};
|
||||||
|
|
||||||
|
// 覆盖 Intl.DateTimeFormat
|
||||||
|
const origDateTimeFormat = Intl.DateTimeFormat;
|
||||||
|
Intl.DateTimeFormat = function(locales, options) {
|
||||||
|
options = options || {};
|
||||||
|
options.timeZone = options.timeZone || window.__rodTZConfig.timeZone;
|
||||||
|
return new origDateTimeFormat(locales, options);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 覆盖 navigator 属性
|
||||||
|
Object.defineProperty(navigator, 'language', {
|
||||||
|
get: () => window.__rodTZConfig.language,
|
||||||
|
configurable: false
|
||||||
|
});
|
||||||
|
|
||||||
|
Object.defineProperty(navigator, 'languages', {
|
||||||
|
get: () => [window.__rodTZConfig.language],
|
||||||
|
configurable: false
|
||||||
|
});
|
||||||
|
|
||||||
|
// 覆盖控制台时区
|
||||||
|
if (console._timeZone) {
|
||||||
|
console._timeZone = window.__rodTZConfig.timeZone;
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
`,
|
||||||
|
timezone,
|
||||||
|
lang,
|
||||||
|
geo,
|
||||||
|
timezone, // 用于注释
|
||||||
|
getUTCOffsetMinutes(timezone), // 时区偏移分钟数
|
||||||
|
getUTCOffsetMinutes(timezone), // 时区偏移分钟数
|
||||||
|
timezone)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getUTCOffsetMinutes 获取时区的UTC偏移分钟数
|
||||||
|
func getUTCOffsetMinutes(timeZone string) int {
|
||||||
|
// 使用时区名称获取实际偏移
|
||||||
|
loc, err := time.LoadLocation(timeZone)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("无法加载时区 %s: %v, 使用默认值 -480", timeZone, err)
|
||||||
|
return -480 // 默认太平洋时间
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取当前时间的偏移
|
||||||
|
_, offset := time.Now().In(loc).Zone()
|
||||||
|
return -offset / 60 // 转换为分钟并取反
|
||||||
|
}
|
||||||
95
libs/browser.go
Normal file
95
libs/browser.go
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
package libs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/go-rod/rod"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Browser struct{}
|
||||||
|
|
||||||
|
type BrowserInfo struct {
|
||||||
|
Id int
|
||||||
|
Browser *rod.Browser
|
||||||
|
Url string
|
||||||
|
Page *rod.Page
|
||||||
|
}
|
||||||
|
|
||||||
|
// 浏览器指纹数据
|
||||||
|
type Fingerprint struct {
|
||||||
|
Viewport []struct {
|
||||||
|
Height int
|
||||||
|
Width int
|
||||||
|
}
|
||||||
|
Proxy string
|
||||||
|
Lang string
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
BrowserPool = make(map[int]*BrowserInfo) // 初始化map
|
||||||
|
poolMutex sync.RWMutex // 添加读写锁保证并发安全
|
||||||
|
)
|
||||||
|
|
||||||
|
var ChromeExample Chrome
|
||||||
|
|
||||||
|
func (*Browser) GetBrowser(id int, params *Fingerprint) *BrowserInfo {
|
||||||
|
poolMutex.RLock()
|
||||||
|
value, ok := BrowserPool[id]
|
||||||
|
poolMutex.RUnlock()
|
||||||
|
|
||||||
|
if ok {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
// 未找到时获取写锁(互斥)
|
||||||
|
poolMutex.Lock()
|
||||||
|
defer poolMutex.Unlock()
|
||||||
|
|
||||||
|
// 双检查:避免在等待锁期间其他goroutine已创建
|
||||||
|
if value, ok := BrowserPool[id]; ok {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建新实例(补充CancelFunc)
|
||||||
|
var ChromeExample Chrome
|
||||||
|
browser, url, page := ChromeExample.Create(id, params)
|
||||||
|
browserInfo := &BrowserInfo{
|
||||||
|
Id: id,
|
||||||
|
Browser: browser,
|
||||||
|
Url: url,
|
||||||
|
Page: page,
|
||||||
|
}
|
||||||
|
BrowserPool[id] = browserInfo
|
||||||
|
return browserInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*Browser) GetNewBrowser(params *Fingerprint) *BrowserInfo {
|
||||||
|
// 创建新实例(补充CancelFunc)
|
||||||
|
var ChromeExample Chrome
|
||||||
|
browser, url, page := ChromeExample.TemporaryCreate(params)
|
||||||
|
browserInfo := &BrowserInfo{
|
||||||
|
Browser: browser,
|
||||||
|
Url: url,
|
||||||
|
Page: page,
|
||||||
|
}
|
||||||
|
return browserInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*Browser) GetAllBrowser() map[int]*BrowserInfo {
|
||||||
|
return BrowserPool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*Browser) CancelAllBrowser() {
|
||||||
|
for _, v := range BrowserPool {
|
||||||
|
v.Browser.MustClose()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*Browser) CancelBrowser(id int) {
|
||||||
|
browserInfo := BrowserPool[id]
|
||||||
|
if browserInfo == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
browserInfo.Browser.MustClose()
|
||||||
|
delete(BrowserPool, id)
|
||||||
|
}
|
||||||
204
libs/chrome.go
Normal file
204
libs/chrome.go
Normal file
@@ -0,0 +1,204 @@
|
|||||||
|
package libs
|
||||||
|
|
||||||
|
import (
|
||||||
|
browserfingerprint "go-account-register/libs/browser-fingerprint"
|
||||||
|
paramsTypes "go-account-register/types"
|
||||||
|
"go-account-register/utils/external"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/go-rod/rod"
|
||||||
|
"github.com/go-rod/rod/lib/launcher"
|
||||||
|
"github.com/go-rod/rod/lib/proto"
|
||||||
|
"github.com/go-rod/stealth"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Chrome struct{}
|
||||||
|
|
||||||
|
func (c *Chrome) Create(accountId int, params *Fingerprint) (*rod.Browser, string, *rod.Page) {
|
||||||
|
var accountIdStr = strconv.Itoa(accountId)
|
||||||
|
userDataDir := "user-data/" + accountIdStr
|
||||||
|
absPath, _ := filepath.Abs(userDataDir)
|
||||||
|
log.Printf("用户数据目录: %s", absPath)
|
||||||
|
path, _ := launcher.LookPath() // 获取当前系统的浏览器目录
|
||||||
|
// 判断是否传递代理
|
||||||
|
// var proxyHandle utils.ProxyHandle
|
||||||
|
// protocol, username, password, ip, port, errProxy := proxyHandle.ParseProxy(params.Proxy)
|
||||||
|
|
||||||
|
ipInfo := external.GetIpInfo(strings.TrimSpace(string(params.Proxy)))
|
||||||
|
|
||||||
|
l := launcher.New().
|
||||||
|
Bin(path).
|
||||||
|
// Proxy("210.51.27.121:50003"). // 直接传入完整认证URL
|
||||||
|
Delete("use-mock-keychain"). // delete flag "--use-mock-keychain"
|
||||||
|
Set("disable-blink-features", "AutomationControlled"). // 绕过自动化检测
|
||||||
|
// Set("incognito"). // 无痕模式
|
||||||
|
Set("user-data-dir", absPath). // 数据持久化目录
|
||||||
|
Set("window-size", "1920,1480"). // 窗口尺寸
|
||||||
|
Set("disable-infobars", "true"). // 隐藏自动化提示栏
|
||||||
|
Set("no-sandbox", "true").
|
||||||
|
Set("excludeSwitches", "enable-automation").
|
||||||
|
Set("enable-gpu"). // 启用 GPU 加速
|
||||||
|
Set("ignore-certificate-errors"). // 忽略证书错误
|
||||||
|
Set("use-fake-ui-for-media-stream"). // 允许媒体流
|
||||||
|
Set("autoplay-policy", "no-user-gesture-required"). // 自动播放
|
||||||
|
Set("ignore-certificate-errors").
|
||||||
|
Set("disable-application-cache").
|
||||||
|
Set("disable-dev-shm-usage").
|
||||||
|
// 禁用webrtc
|
||||||
|
Set("disable-webrtc", "true"). // 核心禁用参数
|
||||||
|
Set("disable-features", "WebRtcHideLocalIpsWithMdns"). // 隐藏本地IP
|
||||||
|
Set("webrtc-ip-handling-policy", "disable_non_proxied_udp"). // 禁用非代理UDP
|
||||||
|
Set("force-webrtc-ip-handling-policy").
|
||||||
|
Headless(false) // 关闭无头模式
|
||||||
|
|
||||||
|
// 修改语言
|
||||||
|
l.Set("lang", "en-US")
|
||||||
|
l.Set("accept-lang", "en-US")
|
||||||
|
|
||||||
|
// 修改时区
|
||||||
|
l.Set("timezone", ipInfo.Timezone)
|
||||||
|
l.Set("disable-geolocation", "true") // 启用地理定位API
|
||||||
|
l.Set("disable-web-security", "true") // 允许跨域(某些网站需要)
|
||||||
|
l.Set("disable-notifications", "true")
|
||||||
|
|
||||||
|
// 设置环境变量 - 核心时区设置
|
||||||
|
l.Env(append(os.Environ(), "TZ="+ipInfo.Timezone)...)
|
||||||
|
l.
|
||||||
|
Set("timezone", ipInfo.Timezone).
|
||||||
|
Set("geolocation", ipInfo.Lat+","+ipInfo.Lon)
|
||||||
|
|
||||||
|
// if errProxy == nil {
|
||||||
|
// if strings.Contains(protocol, "http") {
|
||||||
|
// log.Println(protocol + "://" + ip + ":" + port)
|
||||||
|
// l = l.Proxy(ip + ":" + port)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
uri := l.MustLaunch()
|
||||||
|
|
||||||
|
browser := rod.New().
|
||||||
|
ControlURL(uri).
|
||||||
|
MustConnect().NoDefaultDevice()
|
||||||
|
browser.MustIgnoreCertErrors(true)
|
||||||
|
// if errProxy == nil {
|
||||||
|
// if strings.Contains(protocol, "http") {
|
||||||
|
// log.Println(username, password)
|
||||||
|
// go browser.MustHandleAuth(username, password)()
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
page := stealth.MustPage(browser)
|
||||||
|
// page := browser.MustPage()
|
||||||
|
|
||||||
|
go browser.EachEvent(func(e *proto.TargetTargetCreated) {
|
||||||
|
if e.TargetInfo.Type != proto.TargetTargetInfoTypePage {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
browser.MustPageFromTargetID(e.TargetInfo.TargetID).MustEvalOnNewDocument(stealth.JS)
|
||||||
|
})()
|
||||||
|
|
||||||
|
// 在页面加载前注入时区覆盖代码
|
||||||
|
var browserFingerprint browserfingerprint.BrowserFingerprint
|
||||||
|
page = browserFingerprint.SetBrowserFingerprint(page, ¶msTypes.BrowserFingerprintParams{
|
||||||
|
Canvas: true,
|
||||||
|
TimeZone: ipInfo.Timezone, // 太平洋时间 (UTC-8)
|
||||||
|
Language: "en-US",
|
||||||
|
GeoLocation: ipInfo.Lat + "," + ipInfo.Lon, // 洛杉矶坐标
|
||||||
|
})
|
||||||
|
page.MustSetViewport(1920, 1480, 1.0, false)
|
||||||
|
|
||||||
|
return browser, uri, page
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Chrome) TemporaryCreate(params *Fingerprint) (*rod.Browser, string, *rod.Page) {
|
||||||
|
path, _ := launcher.LookPath() // 获取当前系统的浏览器目录
|
||||||
|
// 判断是否传递代理
|
||||||
|
// var proxyHandle utils.ProxyHandle
|
||||||
|
// protocol, username, password, ip, port, errProxy := proxyHandle.ParseProxy(params.Proxy)
|
||||||
|
|
||||||
|
ipInfo := external.GetIpInfo(strings.TrimSpace(string(params.Proxy)))
|
||||||
|
|
||||||
|
l := launcher.New().
|
||||||
|
Bin(path).
|
||||||
|
// Proxy("210.51.27.121:50003"). // 直接传入完整认证URL
|
||||||
|
Delete("use-mock-keychain"). // delete flag "--use-mock-keychain"
|
||||||
|
Set("disable-blink-features", "AutomationControlled"). // 绕过自动化检测
|
||||||
|
// Set("incognito"). // 无痕模式
|
||||||
|
Set("window-size", "1920,1480"). // 窗口尺寸
|
||||||
|
Set("disable-infobars", "true"). // 隐藏自动化提示栏
|
||||||
|
Set("no-sandbox", "true").
|
||||||
|
Set("excludeSwitches", "enable-automation").
|
||||||
|
Set("enable-gpu"). // 启用 GPU 加速
|
||||||
|
Set("ignore-certificate-errors"). // 忽略证书错误
|
||||||
|
Set("use-fake-ui-for-media-stream"). // 允许媒体流
|
||||||
|
Set("autoplay-policy", "no-user-gesture-required"). // 自动播放
|
||||||
|
Set("ignore-certificate-errors").
|
||||||
|
Set("disable-application-cache").
|
||||||
|
Set("disable-dev-shm-usage").
|
||||||
|
// 禁用webrtc
|
||||||
|
Set("disable-webrtc", "true"). // 核心禁用参数
|
||||||
|
Set("disable-features", "WebRtcHideLocalIpsWithMdns"). // 隐藏本地IP
|
||||||
|
Set("webrtc-ip-handling-policy", "disable_non_proxied_udp"). // 禁用非代理UDP
|
||||||
|
Set("force-webrtc-ip-handling-policy").
|
||||||
|
Headless(false) // 关闭无头模式
|
||||||
|
|
||||||
|
// 修改语言
|
||||||
|
l.Set("lang", "en-US")
|
||||||
|
l.Set("accept-lang", "en-US")
|
||||||
|
|
||||||
|
// 修改时区
|
||||||
|
l.Set("timezone", ipInfo.Timezone)
|
||||||
|
l.Set("disable-geolocation", "true") // 启用地理定位API
|
||||||
|
l.Set("disable-web-security", "true") // 允许跨域(某些网站需要)
|
||||||
|
l.Set("disable-notifications", "true")
|
||||||
|
|
||||||
|
// 设置环境变量 - 核心时区设置
|
||||||
|
l.Env(append(os.Environ(), "TZ="+ipInfo.Timezone)...)
|
||||||
|
l.
|
||||||
|
Set("timezone", ipInfo.Timezone).
|
||||||
|
Set("geolocation", ipInfo.Lat+","+ipInfo.Lon)
|
||||||
|
|
||||||
|
// if errProxy == nil {
|
||||||
|
// if strings.Contains(protocol, "http") {
|
||||||
|
// log.Println(protocol + "://" + ip + ":" + port)
|
||||||
|
// l = l.Proxy(ip + ":" + port)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
uri := l.MustLaunch()
|
||||||
|
|
||||||
|
browser := rod.New().
|
||||||
|
ControlURL(uri).
|
||||||
|
MustConnect().NoDefaultDevice()
|
||||||
|
browser.MustIgnoreCertErrors(true)
|
||||||
|
// if errProxy == nil {
|
||||||
|
// if strings.Contains(protocol, "http") {
|
||||||
|
// log.Println(username, password)
|
||||||
|
// go browser.MustHandleAuth(username, password)()
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
page := stealth.MustPage(browser)
|
||||||
|
// page := browser.MustPage()
|
||||||
|
|
||||||
|
go browser.EachEvent(func(e *proto.TargetTargetCreated) {
|
||||||
|
if e.TargetInfo.Type != proto.TargetTargetInfoTypePage {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
browser.MustPageFromTargetID(e.TargetInfo.TargetID).MustEvalOnNewDocument(stealth.JS)
|
||||||
|
})()
|
||||||
|
|
||||||
|
// 在页面加载前注入时区覆盖代码
|
||||||
|
var browserFingerprint browserfingerprint.BrowserFingerprint
|
||||||
|
page = browserFingerprint.SetBrowserFingerprint(page, ¶msTypes.BrowserFingerprintParams{
|
||||||
|
Canvas: true,
|
||||||
|
TimeZone: ipInfo.Timezone, // 太平洋时间 (UTC-8)
|
||||||
|
Language: "en-US",
|
||||||
|
GeoLocation: ipInfo.Lat + "," + ipInfo.Lon, // 洛杉矶坐标
|
||||||
|
})
|
||||||
|
page.MustSetViewport(1920, 1480, 1.0, false)
|
||||||
|
|
||||||
|
return browser, uri, page
|
||||||
|
}
|
||||||
33
libs/error_code.go
Normal file
33
libs/error_code.go
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
package libs
|
||||||
|
|
||||||
|
type ErrorInfo struct {
|
||||||
|
Code int
|
||||||
|
Data any
|
||||||
|
Msg string
|
||||||
|
}
|
||||||
|
|
||||||
|
var ErrorCode = map[string]*ErrorInfo{
|
||||||
|
"LoginFailed": {Code: 0, Data: "", Msg: "账号异常"},
|
||||||
|
"LoginSuccessful": {Code: 200, Data: "", Msg: "登录成功"},
|
||||||
|
"NetworkTimeout": {Code: 1, Data: "", Msg: "网络超时"},
|
||||||
|
"AccountHasBeenTakenOffline": {Code: 2, Data: "", Msg: "账号已下线,请重新登录"},
|
||||||
|
"OtherError": {Code: 3, Data: "", Msg: "其他错误"},
|
||||||
|
"ChangeSuccessful": {Code: 200, Data: "", Msg: "修改成功"},
|
||||||
|
"ChangeFailed": {Code: 0, Data: "", Msg: "修改失败"},
|
||||||
|
"SendPostSuccessful": {Code: 200, Data: "", Msg: "发推成功"},
|
||||||
|
"SendPostFailed": {Code: 200, Data: "", Msg: "发推失败"},
|
||||||
|
"SimulationSuccessful": {Code: 200, Data: "", Msg: "模拟成功"},
|
||||||
|
"FollowSuccessful": {Code: 200, Data: "", Msg: "关注成功"},
|
||||||
|
"FollowFailed": {Code: 200, Data: "", Msg: "关注失败"},
|
||||||
|
"CommentSuccessful": {Code: 200, Data: "", Msg: "评论成功"},
|
||||||
|
"CommentFailed": {Code: 200, Data: "", Msg: "评论失败"},
|
||||||
|
"ParamsError": {Code: 4, Data: "", Msg: "参数错误"},
|
||||||
|
"SearchSuccessful": {Code: 200, Data: "", Msg: "搜索成功"},
|
||||||
|
"SearchFailed": {Code: 200, Data: "", Msg: "搜索失败"},
|
||||||
|
"ChangeLangSuccessful": {Code: 200, Data: "", Msg: "修改语言成功"},
|
||||||
|
"ChangeLangFailed": {Code: 0, Data: "", Msg: "修改语言失败"},
|
||||||
|
"MaintainAccountSuccessful": {Code: 200, Data: "", Msg: "养号运行成功"},
|
||||||
|
"MaintainAccountFailed": {Code: 0, Data: "", Msg: "养号运行失败"},
|
||||||
|
"RegisterFailed": {Code: 0, Data: "", Msg: "注册失败"},
|
||||||
|
"RegisterSuccessful": {Code: 0, Data: "", Msg: "注册成功"},
|
||||||
|
}
|
||||||
36
libs/file.go
Normal file
36
libs/file.go
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
package libs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type File struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
// 下载图片到临时文件
|
||||||
|
func (*File) DownloadImage(url string) (string, error) {
|
||||||
|
resp, err := http.Get(url)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
// 创建临时文件
|
||||||
|
tempDir := os.TempDir()
|
||||||
|
tempFile := filepath.Join(tempDir, fmt.Sprintf("upload-%d.jpg", time.Now().UnixNano()))
|
||||||
|
|
||||||
|
out, err := os.Create(tempFile)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer out.Close()
|
||||||
|
|
||||||
|
// 复制文件内容
|
||||||
|
_, err = io.Copy(out, resp.Body)
|
||||||
|
return tempFile, err
|
||||||
|
}
|
||||||
34
models/ip.go
Normal file
34
models/ip.go
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
type Ip struct {
|
||||||
|
ID int64 `gorm:"id;primary_key;auto_increment"`
|
||||||
|
As string `gorm:"as"`
|
||||||
|
Asname string `gorm:"asname"`
|
||||||
|
City string `gorm:"city"`
|
||||||
|
Continent string `gorm:"continent"`
|
||||||
|
ContinentCode string `gorm:"continent_code"`
|
||||||
|
Country string `gorm:"country"`
|
||||||
|
CountryCode string `gorm:"country_code"`
|
||||||
|
Currency string `gorm:"currency"`
|
||||||
|
District string `gorm:"district"`
|
||||||
|
Hosting int64 `gorm:"hosting"`
|
||||||
|
Isp string `gorm:"isp"`
|
||||||
|
Lat string `gorm:"lat"`
|
||||||
|
Lon string `gorm:"lon"`
|
||||||
|
Mobile int64 `gorm:"mobile"`
|
||||||
|
Offset string `gorm:"offset"`
|
||||||
|
Org string `gorm:"org"`
|
||||||
|
Proxy int64 `gorm:"proxy"`
|
||||||
|
Query string `gorm:"query"`
|
||||||
|
Region string `gorm:"region"`
|
||||||
|
RegionName string `gorm:"region_name"`
|
||||||
|
Timezone string `gorm:"timezone"`
|
||||||
|
Zip string `gorm:"zip"`
|
||||||
|
UpdateTime int64 `gorm:"update_time"`
|
||||||
|
CreateTime int64 `gorm:"create_time"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 实现 TableName 方法指定表名
|
||||||
|
func (Ip) TableName() string {
|
||||||
|
return "j_ip"
|
||||||
|
}
|
||||||
14
router/index.go
Normal file
14
router/index.go
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
package router
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
var task Task
|
||||||
|
|
||||||
|
type Router struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func Init(app *gin.Engine) {
|
||||||
|
task.TaskRouterInit(app)
|
||||||
|
}
|
||||||
19
router/task.go
Normal file
19
router/task.go
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
package router
|
||||||
|
|
||||||
|
import (
|
||||||
|
"go-account-register/api"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Task struct{}
|
||||||
|
|
||||||
|
var apiTask api.Task
|
||||||
|
|
||||||
|
func (t *Task) TaskRouterInit(app *gin.Engine) {
|
||||||
|
task := app.Group("/task")
|
||||||
|
task.POST("/create", apiTask.Create)
|
||||||
|
task.POST("/start", apiTask.Start)
|
||||||
|
task.POST("/stop", apiTask.Stop)
|
||||||
|
task.POST("/delete", apiTask.Delete)
|
||||||
|
}
|
||||||
206
task/scheduler.go
Normal file
206
task/scheduler.go
Normal file
@@ -0,0 +1,206 @@
|
|||||||
|
package task
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"log"
|
||||||
|
"sort"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
paramsTypes "go-account-register/types"
|
||||||
|
|
||||||
|
"github.com/fatih/structs"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Scheduler struct {
|
||||||
|
TaskQueue map[string]*paramsTypes.Task // 待处理的任务
|
||||||
|
mu sync.Mutex
|
||||||
|
ActiveProcesses map[string]*paramsTypes.Task // 正在运行的任务
|
||||||
|
PausedTasks map[string]*paramsTypes.Task // 暂停的任务
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
StopStatus string = "stop" // 暂停状态
|
||||||
|
PendingStatus string = "pending" // 运行状态
|
||||||
|
)
|
||||||
|
|
||||||
|
var MaxTaskNum int = 10
|
||||||
|
|
||||||
|
// 全局单例实例
|
||||||
|
var globalScheduler *Scheduler
|
||||||
|
var once sync.Once
|
||||||
|
|
||||||
|
// 获取全局调度器实例
|
||||||
|
func GetScheduler() *Scheduler {
|
||||||
|
once.Do(func() {
|
||||||
|
globalScheduler = &Scheduler{
|
||||||
|
TaskQueue: make(map[string]*paramsTypes.Task),
|
||||||
|
ActiveProcesses: make(map[string]*paramsTypes.Task),
|
||||||
|
PausedTasks: make(map[string]*paramsTypes.Task),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return globalScheduler
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加任务
|
||||||
|
func (s *Scheduler) AddTask(params *paramsTypes.TaskParams) string {
|
||||||
|
s.mu.Lock()
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
var uuidV4 string
|
||||||
|
is := structs.New(params)
|
||||||
|
if is.Field("ID").IsZero() {
|
||||||
|
uuidV4 = uuid.NewString()
|
||||||
|
} else {
|
||||||
|
uuidV4 = params.ID
|
||||||
|
}
|
||||||
|
task := ¶msTypes.Task{
|
||||||
|
ID: uuidV4,
|
||||||
|
Name: params.Name,
|
||||||
|
Status: params.Status,
|
||||||
|
Cancel: cancel,
|
||||||
|
Ctx: ctx,
|
||||||
|
Message: make(chan string, 1),
|
||||||
|
Type: params.Type,
|
||||||
|
Data: ¶msTypes.TaskData{
|
||||||
|
AccountClassify: params.AccountClassify,
|
||||||
|
DataClassify: params.DataClassify,
|
||||||
|
Keyword: params.Keyword,
|
||||||
|
TwitterId: params.TwitterId,
|
||||||
|
Compensate: params.Compensate,
|
||||||
|
}}
|
||||||
|
switch params.Status {
|
||||||
|
case StopStatus:
|
||||||
|
s.PausedTasks[uuidV4] = task
|
||||||
|
case PendingStatus:
|
||||||
|
s.TaskQueue[uuidV4] = task
|
||||||
|
default:
|
||||||
|
s.TaskQueue[uuidV4] = task
|
||||||
|
}
|
||||||
|
log.Println("创建任务:" + uuidV4)
|
||||||
|
log.Println("状态:" + params.Status)
|
||||||
|
s.mu.Unlock()
|
||||||
|
s.processQueue()
|
||||||
|
return uuidV4
|
||||||
|
}
|
||||||
|
|
||||||
|
// 运行任务
|
||||||
|
func (s *Scheduler) RunTask(id string) {
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
// 查询暂停的任务列表中是否包含
|
||||||
|
v, ok := s.PausedTasks[id]
|
||||||
|
if ok {
|
||||||
|
s.TaskQueue[id] = v
|
||||||
|
delete(s.PausedTasks, id)
|
||||||
|
}
|
||||||
|
s.processQueue()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 暂停任务
|
||||||
|
func (s *Scheduler) StopTask(id string) {
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
// 查询任务列表中是否包含
|
||||||
|
v, ok := s.TaskQueue[id]
|
||||||
|
if ok {
|
||||||
|
s.PausedTasks[id] = v
|
||||||
|
v.Status = StopStatus
|
||||||
|
v.Cancel() // 停止上下文
|
||||||
|
// v.Message <- "Error"
|
||||||
|
delete(s.TaskQueue, id)
|
||||||
|
}
|
||||||
|
// 查询运行中的任务列表中是否包含
|
||||||
|
value, ok1 := s.ActiveProcesses[id]
|
||||||
|
if ok1 {
|
||||||
|
s.PausedTasks[id] = value
|
||||||
|
value.Status = StopStatus
|
||||||
|
log.Println("向通道中发送消息")
|
||||||
|
value.Cancel() // 停止上下文
|
||||||
|
// value.Message <- "Error"
|
||||||
|
delete(s.ActiveProcesses, id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除任务
|
||||||
|
func (s *Scheduler) RemoveTask(id string) {
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
// 查询任务列表中是否包含
|
||||||
|
_, ok := s.TaskQueue[id]
|
||||||
|
if ok {
|
||||||
|
delete(s.TaskQueue, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询运行中的任务列表中是否包含
|
||||||
|
_, ok1 := s.ActiveProcesses[id]
|
||||||
|
if ok1 {
|
||||||
|
delete(s.ActiveProcesses, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询暂停中的任务列表中是否包含
|
||||||
|
_, ok2 := s.PausedTasks[id]
|
||||||
|
if ok2 {
|
||||||
|
delete(s.PausedTasks, id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理任务
|
||||||
|
func (s *Scheduler) processQueue() {
|
||||||
|
|
||||||
|
s.mu.Lock()
|
||||||
|
log.Println("开始处理任务")
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
for len(s.ActiveProcesses) < MaxTaskNum && len(s.TaskQueue) > 0 {
|
||||||
|
keys := make([]string, 0, len(s.TaskQueue))
|
||||||
|
for k := range s.TaskQueue {
|
||||||
|
keys = append(keys, k)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 排序键
|
||||||
|
sort.Strings(keys)
|
||||||
|
// 获取第一个键值对
|
||||||
|
firstKey := keys[0]
|
||||||
|
go s.executeTask(s.TaskQueue[firstKey])
|
||||||
|
s.ActiveProcesses[firstKey] = s.TaskQueue[firstKey]
|
||||||
|
delete(s.TaskQueue, firstKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Scheduler) executeTask(task *paramsTypes.Task) {
|
||||||
|
// 根据类型判断任务
|
||||||
|
// switch task.Type {
|
||||||
|
// case 6:
|
||||||
|
// var taskNurturingClashPlugin plugin.TaskNurturingClashPlugin
|
||||||
|
// go taskNurturingClashPlugin.Run(task)
|
||||||
|
// case 1:
|
||||||
|
// var taskChangeAccoutnClashPlugin plugin.TaskChangeAccoutnClashPlugin
|
||||||
|
// go taskChangeAccoutnClashPlugin.Run(task)
|
||||||
|
// case 3:
|
||||||
|
// var taskFollowClashPlugin plugin.TaskFollowClashPlugin
|
||||||
|
// go taskFollowClashPlugin.Run(task)
|
||||||
|
// case 7:
|
||||||
|
// var taskSendGifPlugin plugin.TaskSendGifPlugin
|
||||||
|
// go taskSendGifPlugin.Run(task)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// 创建一个阻塞获取当前任务
|
||||||
|
select {
|
||||||
|
case message := <-task.Message:
|
||||||
|
if message == "Success" {
|
||||||
|
log.Println("任务成功")
|
||||||
|
// 运行结束
|
||||||
|
s.PausedTasks[task.ID] = task
|
||||||
|
delete(s.ActiveProcesses, task.ID)
|
||||||
|
}
|
||||||
|
if message == "Error" {
|
||||||
|
log.Println("任务失败")
|
||||||
|
// 运行结束
|
||||||
|
s.PausedTasks[task.ID] = task
|
||||||
|
delete(s.ActiveProcesses, task.ID)
|
||||||
|
}
|
||||||
|
case <-task.Ctx.Done():
|
||||||
|
// 运行结束
|
||||||
|
s.PausedTasks[task.ID] = task
|
||||||
|
delete(s.ActiveProcesses, task.ID)
|
||||||
|
}
|
||||||
|
}
|
||||||
9
types/fingerprint_type.go
Normal file
9
types/fingerprint_type.go
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
type BrowserFingerprintParams struct {
|
||||||
|
Canvas bool // 是否随机指纹
|
||||||
|
TimeZone string // 时区名称,如 "America/New_York"
|
||||||
|
Language string // 语言,如 "en-US"
|
||||||
|
UserAgent string // 用户代理字符串
|
||||||
|
GeoLocation string // 地理位置坐标,如 "40.7128,-74.0060"
|
||||||
|
}
|
||||||
37
types/task_type.go
Normal file
37
types/task_type.go
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
import "context"
|
||||||
|
|
||||||
|
// 任务信息
|
||||||
|
type Task struct {
|
||||||
|
ID string
|
||||||
|
Name string
|
||||||
|
Status string
|
||||||
|
Message chan string
|
||||||
|
Cancel context.CancelFunc
|
||||||
|
Ctx context.Context
|
||||||
|
Type int
|
||||||
|
Data *TaskData
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加任务传递的参数
|
||||||
|
type TaskParams struct {
|
||||||
|
ID string
|
||||||
|
Name string
|
||||||
|
Status string
|
||||||
|
Type int
|
||||||
|
AccountClassify string
|
||||||
|
DataClassify []int
|
||||||
|
Keyword string
|
||||||
|
TwitterId string
|
||||||
|
Compensate int
|
||||||
|
}
|
||||||
|
|
||||||
|
// 任务携带的数据
|
||||||
|
type TaskData struct {
|
||||||
|
AccountClassify string
|
||||||
|
DataClassify []int
|
||||||
|
Keyword string
|
||||||
|
TwitterId string
|
||||||
|
Compensate int
|
||||||
|
}
|
||||||
94
utils/ai_request.go
Normal file
94
utils/ai_request.go
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/go-resty/resty/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AiReqeust struct{}
|
||||||
|
|
||||||
|
type DeepseekResponse struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Object string `json:"object"`
|
||||||
|
Created int64 `json:"created"`
|
||||||
|
Model string `json:"model"`
|
||||||
|
Choices []struct {
|
||||||
|
Index int `json:"index"`
|
||||||
|
Message struct {
|
||||||
|
Role string `json:"role"`
|
||||||
|
Content string `json:"content"`
|
||||||
|
} `json:"message"`
|
||||||
|
Logprobs interface{} `json:"logprobs"` // 可能是null或复杂结构
|
||||||
|
FinishReason string `json:"finish_reason"`
|
||||||
|
} `json:"choices"`
|
||||||
|
Usage struct {
|
||||||
|
PromptTokens int `json:"prompt_tokens"`
|
||||||
|
CompletionTokens int `json:"completion_tokens"`
|
||||||
|
TotalTokens int `json:"total_tokens"`
|
||||||
|
PromptTokensDetails struct {
|
||||||
|
CachedTokens int `json:"cached_tokens"`
|
||||||
|
} `json:"prompt_tokens_details"`
|
||||||
|
PromptCacheHitTokens int `json:"prompt_cache_hit_tokens"`
|
||||||
|
PromptCacheMissTokens int `json:"prompt_cache_miss_tokens"`
|
||||||
|
} `json:"usage"`
|
||||||
|
SystemFingerprint string `json:"system_fingerprint"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*AiReqeust) Deepseek(text string) string {
|
||||||
|
// 定义结构体
|
||||||
|
type Message struct {
|
||||||
|
Role string `json:"role"`
|
||||||
|
Content string `json:"content"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChatRequest struct {
|
||||||
|
Model string `json:"model"`
|
||||||
|
Messages []Message `json:"messages"`
|
||||||
|
}
|
||||||
|
// 创建 resty 客户端
|
||||||
|
client := resty.New()
|
||||||
|
|
||||||
|
// 目标URL
|
||||||
|
url := "https://api.deepseek.com/v1/chat/completions"
|
||||||
|
|
||||||
|
request := ChatRequest{
|
||||||
|
Model: "deepseek-chat",
|
||||||
|
Messages: []Message{
|
||||||
|
{
|
||||||
|
Role: "user",
|
||||||
|
Content: text,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送 POST 请求
|
||||||
|
resp, err := client.R().
|
||||||
|
SetHeader("Content-Type", "application/json"). // 必填头
|
||||||
|
SetHeader("Authorization", "Bearer sk-9ca769cb45c249559276b979a2e1a2cd"). // 认证头
|
||||||
|
SetBody(request). // 设置JSON体
|
||||||
|
Post(url)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("请求失败:", err)
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析响应
|
||||||
|
var response DeepseekResponse
|
||||||
|
if err := json.Unmarshal(resp.Body(), &response); err != nil {
|
||||||
|
fmt.Println("解析失败:", err)
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提取关键信息
|
||||||
|
if len(response.Choices) > 0 {
|
||||||
|
firstChoice := response.Choices[0]
|
||||||
|
// fmt.Printf("\nAI回复 (%s):\n%s\n",
|
||||||
|
// firstChoice.Message.Role,
|
||||||
|
// firstChoice.Message.Content)
|
||||||
|
return firstChoice.Message.Content
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
60
utils/db.go
Normal file
60
utils/db.go
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"go-account-register/config"
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gorm.io/driver/mysql"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
var DB *gorm.DB
|
||||||
|
|
||||||
|
func Connect() (*gorm.DB, error) {
|
||||||
|
appConfig, dberr := config.LoadConfig()
|
||||||
|
if dberr != nil {
|
||||||
|
fmt.Println("读取配置失败")
|
||||||
|
}
|
||||||
|
|
||||||
|
dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local",
|
||||||
|
appConfig.Database.Username,
|
||||||
|
appConfig.Database.Password,
|
||||||
|
appConfig.Database.Host,
|
||||||
|
appConfig.Database.Port,
|
||||||
|
appConfig.Database.Database)
|
||||||
|
|
||||||
|
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to connect to database: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
sqlDB, err := db.DB()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get sql.DB: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
sqlDB.SetMaxIdleConns(10)
|
||||||
|
sqlDB.SetMaxOpenConns(100)
|
||||||
|
sqlDB.SetConnMaxLifetime(time.Hour)
|
||||||
|
|
||||||
|
log.Println("Database connection established")
|
||||||
|
DB = db
|
||||||
|
return db, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetDB() *gorm.DB {
|
||||||
|
if DB == nil {
|
||||||
|
panic("database connection is not initialized")
|
||||||
|
}
|
||||||
|
return DB
|
||||||
|
}
|
||||||
|
|
||||||
|
func Close() error {
|
||||||
|
sqlDB, err := DB.DB()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return sqlDB.Close()
|
||||||
|
}
|
||||||
69
utils/external/index.go
vendored
Normal file
69
utils/external/index.go
vendored
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
package external
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"go-account-register/models"
|
||||||
|
"go-account-register/utils"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetIpInfo(ip string) models.Ip {
|
||||||
|
db := utils.GetDB()
|
||||||
|
var ipInfo models.Ip
|
||||||
|
if ip == "" {
|
||||||
|
var ipRequest utils.IpRequest
|
||||||
|
res, err := ipRequest.IpApi(ip)
|
||||||
|
if err != nil {
|
||||||
|
}
|
||||||
|
resIp := db.Model(&models.Ip{}).Where("query = ?", res.Query).First(&ipInfo)
|
||||||
|
if resIp.Error != nil {
|
||||||
|
createIp(res)
|
||||||
|
db.Model(&models.Ip{}).Where("query = ?", res.Query).First(&ipInfo)
|
||||||
|
return ipInfo
|
||||||
|
}
|
||||||
|
return ipInfo
|
||||||
|
}
|
||||||
|
resIp := db.Model(&models.Ip{}).Where("query = ?", ip).First(&ipInfo)
|
||||||
|
if resIp.Error != nil {
|
||||||
|
var ipRequest utils.IpRequest
|
||||||
|
res, err := ipRequest.IpApi(ip)
|
||||||
|
if err != nil {
|
||||||
|
}
|
||||||
|
createIp(res)
|
||||||
|
db.Model(&models.Ip{}).Where("query = ?", ip).First(&ipInfo)
|
||||||
|
return ipInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
return ipInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
func createIp(res utils.IPAPIResponse) {
|
||||||
|
db := utils.GetDB()
|
||||||
|
|
||||||
|
db.Model(&models.Ip{}).Create(&models.Ip{
|
||||||
|
As: res.As,
|
||||||
|
Asname: res.Asname,
|
||||||
|
City: res.City,
|
||||||
|
Continent: res.Continent,
|
||||||
|
ContinentCode: res.ContinentCode,
|
||||||
|
Country: res.Country,
|
||||||
|
CountryCode: res.CountryCode,
|
||||||
|
Currency: res.Currency,
|
||||||
|
District: res.District,
|
||||||
|
Hosting: utils.BoolToInt(res.Hosting),
|
||||||
|
Isp: res.Isp,
|
||||||
|
Lat: fmt.Sprint(res.Lat),
|
||||||
|
Lon: fmt.Sprint(res.Lon),
|
||||||
|
Mobile: utils.BoolToInt(res.Mobile),
|
||||||
|
Offset: fmt.Sprint(res.Offset),
|
||||||
|
Org: res.Org,
|
||||||
|
Proxy: utils.BoolToInt(res.Proxy),
|
||||||
|
Query: res.Query,
|
||||||
|
Region: res.Region,
|
||||||
|
RegionName: res.RegionName,
|
||||||
|
Timezone: res.Timezone,
|
||||||
|
Zip: res.Zip,
|
||||||
|
UpdateTime: time.Now().Unix(),
|
||||||
|
CreateTime: time.Now().Unix(),
|
||||||
|
})
|
||||||
|
}
|
||||||
75
utils/index.go
Normal file
75
utils/index.go
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/rand"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RandDateParams struct {
|
||||||
|
Year int
|
||||||
|
Month int
|
||||||
|
Day int
|
||||||
|
}
|
||||||
|
|
||||||
|
const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||||||
|
const (
|
||||||
|
letterBits = 6 // 52个字母需6位(2^6=64)
|
||||||
|
letterMask = 1<<letterBits - 1 // 二进制掩码(63个1)
|
||||||
|
)
|
||||||
|
|
||||||
|
func BoolToInt(b bool) int64 {
|
||||||
|
if b {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func RandString(n int) string {
|
||||||
|
b := make([]byte, n)
|
||||||
|
for i := 0; i < n; {
|
||||||
|
if idx := int(rand.Int63() & letterMask); idx < len(letters) {
|
||||||
|
b[i] = letters[idx]
|
||||||
|
i++
|
||||||
|
} // 丢弃无效索引,重试
|
||||||
|
}
|
||||||
|
return string(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func RandDate(minAge, maxAge int) RandDateParams {
|
||||||
|
// 创建本地随机生成器(避免全局 rand.Seed 弃用问题)
|
||||||
|
r := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||||
|
|
||||||
|
// 计算年份范围(当前年-100岁 到 当前年-18岁)
|
||||||
|
currentTime := time.Now()
|
||||||
|
currentYear := currentTime.Year()
|
||||||
|
minYear := currentYear - maxAge
|
||||||
|
maxYear := currentYear - minAge
|
||||||
|
|
||||||
|
// 生成随机年份
|
||||||
|
year := minYear + r.Intn(maxYear-minYear+1)
|
||||||
|
|
||||||
|
// 生成随机月份(1-12)
|
||||||
|
month := r.Intn(12) + 1
|
||||||
|
|
||||||
|
// 动态计算当月最大天数(考虑闰年)
|
||||||
|
daysInMonth := 31
|
||||||
|
switch month {
|
||||||
|
case 2: // 二月特殊处理
|
||||||
|
if (year%4 == 0 && year%100 != 0) || year%400 == 0 {
|
||||||
|
daysInMonth = 29 // 闰年
|
||||||
|
} else {
|
||||||
|
daysInMonth = 28 // 平年
|
||||||
|
}
|
||||||
|
case 4, 6, 9, 11: // 30 天的月份
|
||||||
|
daysInMonth = 30
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成随机日期(1 ~ daysInMonth)
|
||||||
|
day := r.Intn(daysInMonth) + 1
|
||||||
|
|
||||||
|
return RandDateParams{
|
||||||
|
Year: year,
|
||||||
|
Month: month,
|
||||||
|
Day: day,
|
||||||
|
}
|
||||||
|
}
|
||||||
84
utils/ip_request.go
Normal file
84
utils/ip_request.go
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/go-resty/resty/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type IpRequest struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
type IPAPIResponse struct {
|
||||||
|
As string `json:"as"`
|
||||||
|
Asname string `json:"asname"`
|
||||||
|
City string `json:"city"`
|
||||||
|
Continent string `json:"continent"`
|
||||||
|
ContinentCode string `json:"continentCode"`
|
||||||
|
Country string `json:"country"`
|
||||||
|
CountryCode string `json:"countryCode"`
|
||||||
|
Currency string `json:"currency"`
|
||||||
|
District string `json:"district"`
|
||||||
|
Hosting bool `json:"hosting"`
|
||||||
|
Isp string `json:"isp"`
|
||||||
|
Lat float64 `json:"lat"`
|
||||||
|
Lon float64 `json:"lon"`
|
||||||
|
Mobile bool `json:"mobile"`
|
||||||
|
Offset float64 `json:"offset"`
|
||||||
|
Org string `json:"org"`
|
||||||
|
Proxy bool `json:"proxy"`
|
||||||
|
Query string `json:"query"`
|
||||||
|
Region string `json:"region"`
|
||||||
|
RegionName string `json:"regionName"`
|
||||||
|
Timezone string `json:"timezone"`
|
||||||
|
Zip string `json:"zip"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*IpRequest) IpApi(targetIP string) (IPAPIResponse, error) {
|
||||||
|
// 创建Resty客户端
|
||||||
|
client := resty.New()
|
||||||
|
|
||||||
|
// 构建请求URL
|
||||||
|
// targetIP := "103.142.140.235"
|
||||||
|
var url string = "http://demo.ip-api.com/json?fields=66842623&lang=en"
|
||||||
|
if targetIP != "" {
|
||||||
|
url = fmt.Sprintf("http://demo.ip-api.com/json/%s?fields=66842623&lang=en", targetIP)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建响应结构体实例
|
||||||
|
var result IPAPIResponse
|
||||||
|
|
||||||
|
// 发送GET请求(不设置任何请求头)
|
||||||
|
resp, err := client.R().
|
||||||
|
SetResult(&result). // 自动解析JSON到结构体
|
||||||
|
Get(url)
|
||||||
|
|
||||||
|
// 错误处理
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("请求失败: %v\n", err)
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查HTTP状态码
|
||||||
|
if resp.StatusCode() != 200 {
|
||||||
|
fmt.Printf("API返回错误状态码: %d\n", resp.StatusCode())
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*IpRequest) GetIPAddress() {
|
||||||
|
// 创建Resty客户端
|
||||||
|
client := resty.New()
|
||||||
|
var url string = "http://icanhazip.com/"
|
||||||
|
// 发送GET请求(不设置任何请求头)
|
||||||
|
resp, err := client.R().
|
||||||
|
Get(url)
|
||||||
|
// 错误处理
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("请求失败: %v\n", err)
|
||||||
|
}
|
||||||
|
log.Println(resp)
|
||||||
|
}
|
||||||
42
utils/proxy_handle.go
Normal file
42
utils/proxy_handle.go
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ProxyHandle struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ProxyHandle) ParseProxy(proxyStr string) (protocol, username, password, ip, port string, err error) {
|
||||||
|
// 清理字符串中的多余字符(如末尾的 \n)
|
||||||
|
proxyStr = strings.TrimSpace(proxyStr)
|
||||||
|
|
||||||
|
// 解析 URL
|
||||||
|
proxyURL, err := url.Parse(proxyStr)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", "", "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提取协议(http/https)
|
||||||
|
protocol = proxyURL.Scheme
|
||||||
|
|
||||||
|
// 提取 IP 和端口
|
||||||
|
host := proxyURL.Host
|
||||||
|
if strings.Contains(host, ":") {
|
||||||
|
parts := strings.Split(host, ":")
|
||||||
|
ip = parts[0]
|
||||||
|
port = parts[1]
|
||||||
|
} else {
|
||||||
|
ip = host
|
||||||
|
port = "8080" // 默认端口
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提取用户名和密码
|
||||||
|
if proxyURL.User != nil {
|
||||||
|
username = proxyURL.User.Username()
|
||||||
|
password, _ = proxyURL.User.Password()
|
||||||
|
}
|
||||||
|
|
||||||
|
return protocol, username, password, ip, port, nil
|
||||||
|
}
|
||||||
34
utils/redis.go
Normal file
34
utils/redis.go
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/redis/go-redis/v9"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Redis 封装结构体
|
||||||
|
type Redis struct {
|
||||||
|
rdb *redis.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化Redis连接
|
||||||
|
func InitRedis() (*Redis, error) {
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
// 创建 Redis 客户端
|
||||||
|
rdb := redis.NewClient(&redis.Options{
|
||||||
|
Addr: "localhost:6379", // Redis 服务器地址
|
||||||
|
Password: "", // 密码,没有则为空
|
||||||
|
DB: 0, // 默认数据库
|
||||||
|
})
|
||||||
|
|
||||||
|
// 测试连接
|
||||||
|
if _, err := rdb.Ping(ctx).Result(); err != nil {
|
||||||
|
return nil, fmt.Errorf("redis连接失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println("✅ Redis连接成功")
|
||||||
|
return &Redis{rdb: rdb}, nil
|
||||||
|
}
|
||||||
85
utils/verify_image_plugin.go
Normal file
85
utils/verify_image_plugin.go
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image"
|
||||||
|
"image/color"
|
||||||
|
"image/draw"
|
||||||
|
_ "image/jpeg"
|
||||||
|
"image/png"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/golang/freetype"
|
||||||
|
"golang.org/x/image/font"
|
||||||
|
)
|
||||||
|
|
||||||
|
type VerifyImagePlugin struct{}
|
||||||
|
|
||||||
|
func (*VerifyImagePlugin) DrawText(originalImage, newImage, text, fontPath string) (string, error) {
|
||||||
|
srcFile, err := os.Open(originalImage) // 替换为你的图片路径
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("图片打开失败:", err)
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer srcFile.Close()
|
||||||
|
|
||||||
|
// 解码原始图片
|
||||||
|
img, _, err := image.Decode(srcFile)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("图片解码失败,只支持jpg、png:", err)
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
rgba := image.NewRGBA(img.Bounds())
|
||||||
|
draw.Draw(rgba, rgba.Bounds(), img, image.Point{}, draw.Src)
|
||||||
|
var fontStr string = fontPath
|
||||||
|
if fontPath == "" {
|
||||||
|
fontStr = "fonts/AlibabaPuHuiTi-3-115-Black.ttf"
|
||||||
|
}
|
||||||
|
fontBytes, err := os.ReadFile(fontStr) // 替换为你的字体路径
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("字体加载失败:", err)
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
fontObj, err := freetype.ParseFont(fontBytes)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("字体解析失败:", err)
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
c := freetype.NewContext()
|
||||||
|
c.SetDPI(72) // 屏幕分辨率
|
||||||
|
c.SetFont(fontObj) // 设置中文字体
|
||||||
|
c.SetFontSize(40) // 字体大小(磅)
|
||||||
|
c.SetClip(rgba.Bounds()) // 绘制区域
|
||||||
|
c.SetDst(rgba) // 目标画布
|
||||||
|
c.SetSrc(image.NewUniform(color.RGBA{ // 文字颜色
|
||||||
|
R: 255,
|
||||||
|
G: 255,
|
||||||
|
B: 255,
|
||||||
|
A: 255,
|
||||||
|
}))
|
||||||
|
c.SetHinting(font.HintingFull) // 增强小字清晰度
|
||||||
|
|
||||||
|
x := 150 // 水平起始位置
|
||||||
|
y := 250 // 垂直起始位置
|
||||||
|
yBase := y + int(c.PointToFixed(40)>>6) // 关键:修正垂直偏移
|
||||||
|
pt := freetype.Pt(x, yBase)
|
||||||
|
|
||||||
|
if _, err := c.DrawString(text, pt); err != nil {
|
||||||
|
log.Fatal("文字绘制失败:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
outFile, err := os.Create(newImage)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("输出文件创建失败:", err)
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer outFile.Close()
|
||||||
|
if err := png.Encode(outFile, rgba); err != nil {
|
||||||
|
log.Fatal("图片保存失败:", err)
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
log.Println("成功生成:output.png")
|
||||||
|
return newImage, nil
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user