From cccb31cbc5244ecbbf5741279c5beb42bf1056c9 Mon Sep 17 00:00:00 2001 From: YGXB_net Date: Mon, 13 Oct 2025 21:32:02 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=88=9D=E6=AD=A5=E5=AE=8C=E6=88=90GUI?= =?UTF-8?q?=E7=95=8C=E9=9D=A2=E6=B7=BB=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 3 +- api/api.go | 9 +- config/config.go | 63 +++++++++ go.mod | 31 ++++- go.sum | 68 +++++++++- main.go | 235 +++++++++++++++++++++++++--------- uploader/uploader.go | 185 ++++++++++++++++++++++++++ utils/folder/folder.go | 20 +++ utils/folder/windows_dialog.c | 56 ++++++++ utils/folder/windows_dialog.h | 11 ++ 10 files changed, 607 insertions(+), 74 deletions(-) create mode 100644 config/config.go create mode 100644 uploader/uploader.go create mode 100644 utils/folder/folder.go create mode 100644 utils/folder/windows_dialog.c create mode 100644 utils/folder/windows_dialog.h diff --git a/.gitignore b/.gitignore index 12bc0dc..f47152e 100644 --- a/.gitignore +++ b/.gitignore @@ -27,4 +27,5 @@ go.work.sum /.idea config.toml -/*.txt \ No newline at end of file +/*.txt +/tmp/ \ No newline at end of file diff --git a/api/api.go b/api/api.go index 3add33e..6a6e376 100644 --- a/api/api.go +++ b/api/api.go @@ -1,20 +1,19 @@ package api import ( + "dypid-client/config" "io" "net/http" "net/url" "strings" - - "github.com/spf13/viper" ) -func uploadDataToServer(httpClient *http.Client, data string) error { +func UploadDataToServer(httpClient *http.Client, data string) error { params := url.Values{} - params.Set("token", viper.GetString("token")) + params.Set("token", config.APPConfig.Token) params.Set("data", data) - resp, err := httpClient.Post(viper.GetString("url")+"/api/data?"+params.Encode(), "application/x-www-form-urlencoded", strings.NewReader("")) + resp, err := httpClient.Post(config.APPConfig.Url+"/api/data?"+params.Encode(), "application/x-www-form-urlencoded", strings.NewReader("")) if err != nil { return err } diff --git a/config/config.go b/config/config.go new file mode 100644 index 0000000..87e5428 --- /dev/null +++ b/config/config.go @@ -0,0 +1,63 @@ +package config + +import ( + "fmt" + + "github.com/fsnotify/fsnotify" + "github.com/spf13/viper" +) + +type Config struct { + Url string `mapstructure:"url"` + Token string `mapstructure:"token"` + ThreadCount int `mapstructure:"thread-count"` + IsRunOnStart bool `mapstructure:"is-run-on-start"` + LookingPath string `mapstructure:"looking-path"` +} + +var APPConfig Config + +func InitConfig() { + // 设置默认配置 + defaultConfig := Config{ + Url: "http://127.0.0.1:8080", + Token: "", + ThreadCount: 10, + IsRunOnStart: false, + LookingPath: "", + } + viper.SetDefault("url", defaultConfig.Url) + viper.SetDefault("token", defaultConfig.Token) + viper.SetDefault("thread-count", defaultConfig.ThreadCount) + viper.SetDefault("is-run-on-start", defaultConfig.IsRunOnStart) + viper.SetDefault("looking-path", defaultConfig.LookingPath) + + //设置配置文件名和路径 ./config.toml + viper.AddConfigPath(".") + viper.SetConfigName("config") + viper.SetConfigType("toml") + viper.SafeWriteConfig() //安全写入默认配置 + + //读取配置文件 + if err := viper.ReadInConfig(); err != nil { + fmt.Errorf("无法读取配置文件: %w", err) + } + if err := viper.Unmarshal(&APPConfig); err != nil { + fmt.Errorf("无法解析配置: %w", err) + } + + viper.WatchConfig() + viper.OnConfigChange(func(e fsnotify.Event) { + if err := viper.ReadInConfig(); err != nil { + fmt.Errorf("无法读取配置文件: %w", err) + } + if err := viper.Unmarshal(&APPConfig); err != nil { + fmt.Errorf("无法解析配置: %w", err) + } + }) +} + +func WriteConfig(key string, value any) { + viper.Set(key, value) + viper.WriteConfig() +} diff --git a/go.mod b/go.mod index 7eb0c32..4cd06ec 100644 --- a/go.mod +++ b/go.mod @@ -3,21 +3,50 @@ module dypid-client go 1.25.1 require ( + fyne.io/fyne/v2 v2.6.3 github.com/fsnotify/fsnotify v1.9.0 github.com/spf13/viper v1.21.0 golang.org/x/sync v0.17.0 ) require ( + fyne.io/systray v1.11.0 // indirect + github.com/BurntSushi/toml v1.4.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/fredbi/uri v1.1.0 // indirect + github.com/fyne-io/gl-js v0.2.0 // indirect + github.com/fyne-io/glfw-js v0.3.0 // indirect + github.com/fyne-io/image v0.1.1 // indirect + github.com/fyne-io/oksvg v0.1.0 // indirect + github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71 // indirect + github.com/go-gl/glfw/v3.3/glfw v0.0.0-20240506104042-037f3cc74f2a // indirect + github.com/go-text/render v0.2.0 // indirect + github.com/go-text/typesetting v0.2.1 // indirect github.com/go-viper/mapstructure/v2 v2.4.0 // indirect + github.com/godbus/dbus/v5 v5.1.0 // indirect + github.com/hack-pad/go-indexeddb v0.3.2 // indirect + github.com/hack-pad/safejs v0.1.0 // indirect + github.com/jeandeaual/go-locale v0.0.0-20250612000132-0ef82f21eade // indirect + github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25 // indirect + github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect + github.com/nicksnyder/go-i18n/v2 v2.5.1 // indirect github.com/pelletier/go-toml/v2 v2.2.4 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/rymdport/portal v0.4.1 // indirect github.com/sagikazarmark/locafero v0.11.0 // indirect github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect github.com/spf13/afero v1.15.0 // indirect github.com/spf13/cast v1.10.0 // indirect github.com/spf13/pflag v1.0.10 // indirect + github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c // indirect + github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef // indirect + github.com/stretchr/testify v1.11.1 // indirect github.com/subosito/gotenv v1.6.0 // indirect + github.com/yuin/goldmark v1.7.8 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/sys v0.29.0 // indirect + golang.org/x/image v0.24.0 // indirect + golang.org/x/net v0.35.0 // indirect + golang.org/x/sys v0.30.0 // indirect golang.org/x/text v0.28.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 8eb0135..562c7c1 100644 --- a/go.sum +++ b/go.sum @@ -1,23 +1,73 @@ +fyne.io/fyne/v2 v2.6.3 h1:cvtM2KHeRuH+WhtHiA63z5wJVBkQ9+Ay0UMl9PxFHyA= +fyne.io/fyne/v2 v2.6.3/go.mod h1:NGSurpRElVoI1G3h+ab2df3O5KLGh1CGbsMMcX0bPIs= +fyne.io/systray v1.11.0 h1:D9HISlxSkx+jHSniMBR6fCFOUjk1x/OOOJLa9lJYAKg= +fyne.io/systray v1.11.0/go.mod h1:RVwqP9nYMo7h5zViCBHri2FgjXF7H2cub7MAq4NSoLs= +github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= +github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= 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/felixge/fgprof v0.9.3 h1:VvyZxILNuCiUCSXtPtYmmtGvb65nqXh2QFWc0Wpf2/g= +github.com/felixge/fgprof v0.9.3/go.mod h1:RdbpDgzqYVh/T9fPELJyV7EYJuHB55UTEULNun8eiPw= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/fredbi/uri v1.1.0 h1:OqLpTXtyRg9ABReqvDGdJPqZUxs8cyBDOMXBbskCaB8= +github.com/fredbi/uri v1.1.0/go.mod h1:aYTUoAXBOq7BLfVJ8GnKmfcuURosB1xyHDIfWeC/iW4= github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/fyne-io/gl-js v0.2.0 h1:+EXMLVEa18EfkXBVKhifYB6OGs3HwKO3lUElA0LlAjs= +github.com/fyne-io/gl-js v0.2.0/go.mod h1:ZcepK8vmOYLu96JoxbCKJy2ybr+g1pTnaBDdl7c3ajI= +github.com/fyne-io/glfw-js v0.3.0 h1:d8k2+Y7l+zy2pc7wlGRyPfTgZoqDf3AI4G+2zOWhWUk= +github.com/fyne-io/glfw-js v0.3.0/go.mod h1:Ri6te7rdZtBgBpxLW19uBpp3Dl6K9K/bRaYdJ22G8Jk= +github.com/fyne-io/image v0.1.1 h1:WH0z4H7qfvNUw5l4p3bC1q70sa5+YWVt6HCj7y4VNyA= +github.com/fyne-io/image v0.1.1/go.mod h1:xrfYBh6yspc+KjkgdZU/ifUC9sPA5Iv7WYUBzQKK7JM= +github.com/fyne-io/oksvg v0.1.0 h1:7EUKk3HV3Y2E+qypp3nWqMXD7mum0hCw2KEGhI1fnBw= +github.com/fyne-io/oksvg v0.1.0/go.mod h1:dJ9oEkPiWhnTFNCmRgEze+YNprJF7YRbpjgpWS4kzoI= +github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71 h1:5BVwOaUSBTlVZowGO6VZGw2H/zl9nrd3eCZfYV+NfQA= +github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71/go.mod h1:9YTyiznxEY1fVinfM7RvRcjRHbw2xLBJ3AAGIT0I4Nw= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20240506104042-037f3cc74f2a h1:vxnBhFDDT+xzxf1jTJKMKZw3H0swfWk9RpWbBbDK5+0= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20240506104042-037f3cc74f2a/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-text/render v0.2.0 h1:LBYoTmp5jYiJ4NPqDc2pz17MLmA3wHw1dZSVGcOdeAc= +github.com/go-text/render v0.2.0/go.mod h1:CkiqfukRGKJA5vZZISkjSYrcdtgKQWRa2HIzvwNN5SU= +github.com/go-text/typesetting v0.2.1 h1:x0jMOGyO3d1qFAPI0j4GSsh7M0Q3Ypjzr4+CEVg82V8= +github.com/go-text/typesetting v0.2.1/go.mod h1:mTOxEwasOFpAMBjEQDhdWRckoLLeI/+qrQeBCTGEt6M= +github.com/go-text/typesetting-utils v0.0.0-20241103174707-87a29e9e6066 h1:qCuYC+94v2xrb1PoS4NIDe7DGYtLnU2wWiQe9a1B1c0= +github.com/go-text/typesetting-utils v0.0.0-20241103174707-87a29e9e6066/go.mod h1:DDxDdQEnB70R8owOx3LVpEFvpMK9eeH1o2r0yZhFI9o= github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= +github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/pprof v0.0.0-20211214055906-6f57359322fd h1:1FjCyPC+syAzJ5/2S8fqdZK1R22vvA0J7JZKcuOIQ7Y= +github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg= +github.com/hack-pad/go-indexeddb v0.3.2 h1:DTqeJJYc1usa45Q5r52t01KhvlSN02+Oq+tQbSBI91A= +github.com/hack-pad/go-indexeddb v0.3.2/go.mod h1:QvfTevpDVlkfomY498LhstjwbPW6QC4VC/lxYb0Kom0= +github.com/hack-pad/safejs v0.1.0 h1:qPS6vjreAqh2amUqj4WNG1zIw7qlRQJ9K10eDKMCnE8= +github.com/hack-pad/safejs v0.1.0/go.mod h1:HdS+bKF1NrE72VoXZeWzxFOVQVUSqZJAG0xNCnb+Tio= +github.com/jeandeaual/go-locale v0.0.0-20250612000132-0ef82f21eade h1:FmusiCI1wHw+XQbvL9M+1r/C3SPqKrmBaIOYwVfQoDE= +github.com/jeandeaual/go-locale v0.0.0-20250612000132-0ef82f21eade/go.mod h1:ZDXo8KHryOWSIqnsb/CiDq7hQUYryCgdVnxbj8tDG7o= +github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25 h1:YLvr1eE6cdCqjOe972w/cYF+FjW34v27+9Vo5106B4M= +github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25/go.mod h1:kLgvv7o6UM+0QSf0QjAse3wReFDsb9qbZJdfexWlrQw= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ= +github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= +github.com/nicksnyder/go-i18n/v2 v2.5.1 h1:IxtPxYsR9Gp60cGXjfuR/llTqV8aYMsC472zD0D1vHk= +github.com/nicksnyder/go-i18n/v2 v2.5.1/go.mod h1:DrhgsSDZxoAfvVrBVLXoxZn/pN5TXqaDbq7ju94viiQ= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= +github.com/pkg/profile v1.7.0 h1:hnbDkaNWPCLMO9wGLdBFTIZvzDrDfBM2072E1S9gJkA= +github.com/pkg/profile v1.7.0/go.mod h1:8Uer0jas47ZQMJ7VD+OHknK4YDY07LPUC6dEvqDjvNo= 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/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/rymdport/portal v0.4.1 h1:2dnZhjf5uEaeDjeF/yBIeeRo6pNI2QAKm7kq1w/kbnA= +github.com/rymdport/portal v0.4.1/go.mod h1:kFF4jslnJ8pD5uCi17brj/ODlfIidOxlgUDTO5ncnC4= github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc= github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik= github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw= @@ -30,20 +80,30 @@ github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU= github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY= +github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c h1:km8GpoQut05eY3GiYWEedbTT0qnSxrCjsVbb7yKY1KE= +github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c/go.mod h1:cNQ3dwVJtS5Hmnjxy6AgTPd0Inb3pW05ftPSX7NZO7Q= +github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef h1:Ch6Q+AZUxDBCVqdkI8FSpFyZDtCVBc2VmejdNrm5rRQ= +github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef/go.mod h1:nXTWP6+gD5+LUJ8krVhhoeHjvHTutPxMYl5SvkcnJNE= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +github.com/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic= +github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= +golang.org/x/image v0.24.0 h1:AN7zRgVsbvmTfNyqIbbOraYL8mSwcKncEj8ofjgzcMQ= +golang.org/x/image v0.24.0/go.mod h1:4b/ITuLfqYq1hqZcjofwctIhi7sZh2WaCjvsBNjjya8= +golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= +golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= -golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= -golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= +golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/main.go b/main.go index b252c34..5825c8a 100644 --- a/main.go +++ b/main.go @@ -3,21 +3,164 @@ package main import ( "bufio" "context" + "dypid-client/api" + "dypid-client/config" + "dypid-client/utils/folder" "fmt" - "io" "net/http" - "net/url" "os" "path/filepath" + "strconv" "strings" "sync" "time" - "github.com/fsnotify/fsnotify" - "github.com/spf13/viper" + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/app" + "fyne.io/fyne/v2/container" + "fyne.io/fyne/v2/layout" + "fyne.io/fyne/v2/widget" "golang.org/x/sync/errgroup" ) +var isRun = false +var logText = widget.NewMultiLineEntry() + +func main() { + config.InitConfig() + + myApp := app.New() + myWindow := myApp.NewWindow("抖音数据上传工具") + myWindow.Resize(fyne.NewSize(900, 550)) + + logText.Scroll = container.ScrollVerticalOnly + + // 创建界面组件 + urlEntry := widget.NewEntry() + urlEntry.SetPlaceHolder("http://127.0.0.1:8080") + urlEntry.Text = config.APPConfig.Url + urlEntry.OnChanged = func(s string) { + config.WriteConfig("url", urlEntry.Text) + } + + tokenEntry := widget.NewEntry() + tokenEntry.SetPlaceHolder("请输入Token") + tokenEntry.Text = config.APPConfig.Token + tokenEntry.OnChanged = func(s string) { + config.WriteConfig("token", tokenEntry.Text) + } + + selectedDirLabel := widget.NewEntry() + selectedDirLabel.SetPlaceHolder("未选择目录(默认为程序运行目录)") + selectedDirLabel.Text = config.APPConfig.LookingPath + selectedDirLabel.OnChanged = func(s string) { + config.WriteConfig("looking-path", selectedDirLabel.Text) + } + + threadCountLabel := widget.NewEntry() + threadCountLabel.SetPlaceHolder("10") + threadCountLabel.Text = strconv.Itoa(config.APPConfig.ThreadCount) + threadCountLabel.OnChanged = func(s string) { + i, err := strconv.Atoi(threadCountLabel.Text) + if err != nil { + AddLog("输入上传线程错误") + } + config.WriteConfig("thread-count", i) + } + + isRunOnStartWidget := widget.NewCheck("启动程序时启动上传程序", func(b bool) { + config.WriteConfig("is-run-on-start", b) + }) + isRunOnStartWidget.Checked = config.APPConfig.IsRunOnStart + + // 使用Windows原生目录选择按钮 + selectDirBtn := widget.NewButton("选择检测目录", func() { + // 调用CGO实现的Windows原生对话框 + selectedPath := folder.OpenFolderDialog() + if selectedPath == "" { + return + } + selectedDirLabel.SetText(selectedPath) + config.WriteConfig("looking-path", selectedPath) + }) + + // 开始运行按钮 + startBtn := widget.NewButton("开始运行", func() { + if strings.TrimSpace(tokenEntry.Text) == "" { + AddLog("错误:请输入Token") + return + } + + AddLog(fmt.Sprintf("Token:%s", tokenEntry.Text)) + AddLog(fmt.Sprintf("检测目录:%s", selectedDirLabel.Text)) + AddLog("===============================") + + isRun = true + go StartLooking(selectedDirLabel.Text) + }) + + // 清除日志按钮 + clearLogBtn := widget.NewButton("清除日志", func() { + logText.SetText("") + AddLog("日志已清除") + }) + + // 组装左侧面板 + leftPanel := container.NewBorder( + nil, + // 底部 - 放置按钮 + container.NewVBox( + isRunOnStartWidget, + startBtn, + clearLogBtn, + ), + nil, + nil, + // 中间内容 + container.NewVBox( + widget.NewLabelWithStyle("服务器地址:", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}), + urlEntry, + widget.NewSeparator(), + widget.NewLabelWithStyle("Token:", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}), + tokenEntry, + widget.NewSeparator(), + widget.NewLabelWithStyle("检测目录:", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}), + selectedDirLabel, + selectDirBtn, + widget.NewSeparator(), + container.NewHBox( + widget.NewLabelWithStyle("单文件上传线程:", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}), + threadCountLabel, + ), + widget.NewSeparator(), + layout.NewSpacer(), // 添加一个弹性空间,将内容向上推 + ), + ) + // 组装右侧面板(日志显示) + rightPanel := container.NewBorder( + widget.NewLabelWithStyle("运行日志", fyne.TextAlignCenter, fyne.TextStyle{Bold: true}), + nil, nil, nil, + container.NewScroll(logText), + ) + + // 使用HSplit容器创建可调整大小的左右分割布局 + splitContainer := container.NewHSplit(leftPanel, rightPanel) + splitContainer.SetOffset(0.35) // 左侧占35%宽度 + + myWindow.SetContent(splitContainer) + myWindow.ShowAndRun() +} + +func AddLog(message string) { + fyne.Do(func() { + logText.Append(message + "\n") + // 自动滚动到底部 + logText.CursorRow = len(logText.Text) + }) +} + +// 上传数据代码 + var httpClient = &http.Client{ Transport: &http.Transport{ MaxIdleConns: 200, @@ -32,26 +175,28 @@ type Task struct { FileLines int } -func main() { - initConfig() - - //检测./upload - fmt.Println("程序启动成功,正在检测txt文件") +func StartLooking(lookingPath string) { + //检测./ + AddLog("程序启动成功,正在检测txt文件") for { - files, err := getTxtFiles("./") + var path = "./" + if lookingPath != "" { + path = lookingPath + } + files, err := getTxtFiles(path) if err != nil { - fmt.Println(err) + AddLog(err.Error()) continue } if files != nil { start := time.Now() fileLines := make(map[string]int) - fmt.Println("正在统计", len(files), "个文件行数") + AddLog(fmt.Sprintf("正在统计 %v 个文件行数", len(files))) for _, filePath := range files { file, err := os.Open(filePath) if err != nil { - fmt.Println("打开文件失败:", err) + AddLog("打开文件失败:" + err.Error()) } // 使用 bufio.Scanner 逐行读取 scanner := bufio.NewScanner(file) @@ -64,7 +209,7 @@ func main() { continue } fileLines[filepath.Base(filePath)] = lineCount - fmt.Println(filepath.Base(filePath), "文件行数:", lineCount) + AddLog(fmt.Sprintf("%s 文件行数:%v", filepath.Base(filePath), lineCount)) } @@ -84,11 +229,11 @@ func main() { case <-ctx.Done(): return ctx.Err() default: - fmt.Println("正在上传文件:", filepath.Base(task.FilePath)) + AddLog("正在上传文件:" + filepath.Base(task.FilePath)) processFile(task.FilePath, task.FileLines) err := os.Truncate(task.FilePath, 0) if err != nil { - fmt.Println("清空文件失败:", err) + AddLog("清空文件失败:" + err.Error()) } return nil } @@ -97,52 +242,16 @@ func main() { // 等待所有任务完成 if err := g.Wait(); err != nil { - fmt.Printf("任务执行出错: %v\n", err) + AddLog(fmt.Sprintf("任务执行出错: %v", err)) } else { - fmt.Println("所有任务执行完成!") + AddLog("所有任务执行完成!") } - fmt.Printf("上传完成,耗时:%s\n", time.Since(start)) + AddLog(fmt.Sprintf("上传完成,耗时:%s\n", time.Since(start))) } time.Sleep(time.Minute) } -} - -func initConfig() { - //程序配置 - viper.SetDefault("url", "http://localhost:8080") - viper.SetDefault("token", "") - viper.SetDefault("thread-count", 10) - //设置配置文件名和路径 ./config.toml - viper.AddConfigPath(".") - viper.SetConfigName("config") - viper.SetConfigType("toml") - viper.SafeWriteConfig() //安全写入默认配置 - //读取配置文件 - if err := viper.ReadInConfig(); err != nil { - fmt.Errorf("无法读取配置文件: %w", err) - } - viper.WatchConfig() - viper.OnConfigChange(func(e fsnotify.Event) { - if err := viper.ReadInConfig(); err != nil { - fmt.Errorf("无法读取配置文件: %w", err) - } - }) -} - -func uploadDataToServer(data string) error { - params := url.Values{} - params.Set("token", viper.GetString("token")) - params.Set("data", data) - - resp, err := httpClient.Post(viper.GetString("url")+"/api/data?"+params.Encode(), "application/x-www-form-urlencoded", strings.NewReader("")) - if err != nil { - return err - } - if resp != nil { - _, _ = io.Copy(io.Discard, resp.Body) - } - return err + AddLog("上传程序已退出") } // 获取目录中的所有txt文件 @@ -173,7 +282,7 @@ func processFile(filePath string, fileLines int) { // 打开文件 file, err := os.Open(filePath) if err != nil { - fmt.Printf("无法打开文件 %s: %v\n", filePath, err) + AddLog(fmt.Sprintf("无法打开文件 %s: %v\n", filePath, err)) return } defer file.Close() @@ -182,7 +291,7 @@ func processFile(filePath string, fileLines int) { lines := make(chan string, 100) // 创建10个worker处理文件上传 - for i := 0; i < viper.GetInt("thread-count"); i++ { + for i := 0; i < config.APPConfig.ThreadCount; i++ { wg.Add(1) go func(workerID int) { defer wg.Done() @@ -197,7 +306,7 @@ func processFile(filePath string, fileLines int) { lines <- scanner.Text() lineCount++ if lineCount%10000 == 0 { - fmt.Printf("文件【%s】处理进度:%.2f%%\n", filePath, float64(lineCount)/float64(fileLines)*100) + AddLog(fmt.Sprintf("文件【%s】处理进度:%.2f%%\n", filePath, float64(lineCount)/float64(fileLines)*100)) } } @@ -205,11 +314,11 @@ func processFile(filePath string, fileLines int) { wg.Wait() if err := scanner.Err(); err != nil { - fmt.Printf("读取文件 %s 错误: %v\n", filePath, err) + AddLog(fmt.Sprintf("读取文件 %s 错误: %v\n", filePath, err)) return } - fmt.Printf("文件【%s】处理完成,共处理 %d 行数据\n", filePath, lineCount) + AddLog(fmt.Sprintf("文件【%s】处理完成,共处理 %d 行数据\n", filePath, lineCount)) } func processLines(lines <-chan string, workerID int, filePath string) { @@ -219,8 +328,8 @@ func processLines(lines <-chan string, workerID int, filePath string) { continue } // 上传数据 - if err := uploadDataToServer(line); err != nil { - fmt.Printf("Worker %d (文件 %s): 上传失败: %v\n", workerID, filePath, err) + if err := api.UploadDataToServer(httpClient, line); err != nil { + AddLog(fmt.Sprintf("Worker %d (文件 %s): 上传失败: %v\n", workerID, filePath, err)) } } } diff --git a/uploader/uploader.go b/uploader/uploader.go new file mode 100644 index 0000000..8c28d6a --- /dev/null +++ b/uploader/uploader.go @@ -0,0 +1,185 @@ +package uploader + +import ( + "bufio" + "context" + "dypid-client/api" + "fmt" + "net/http" + "os" + "path/filepath" + "strings" + "sync" + "time" + + "github.com/spf13/viper" + "golang.org/x/sync/errgroup" +) + +var httpClient = &http.Client{ + Transport: &http.Transport{ + MaxIdleConns: 200, + MaxIdleConnsPerHost: 100, + IdleConnTimeout: 30 * time.Second, + }, + Timeout: 30 * time.Second, +} + +type Task struct { + FilePath string + FileLines int +} + +func StartLooking(lookingPath string) { + //检测./ + fmt.Println("程序启动成功,正在检测txt文件") + for { + files, err := getTxtFiles("./") + if err != nil { + fmt.Println(err) + continue + } + if files != nil { + start := time.Now() + + fileLines := make(map[string]int) + fmt.Println("正在统计", len(files), "个文件行数") + for _, filePath := range files { + file, err := os.Open(filePath) + if err != nil { + fmt.Println("打开文件失败:", err) + } + // 使用 bufio.Scanner 逐行读取 + scanner := bufio.NewScanner(file) + lineCount := 0 + for scanner.Scan() { + lineCount++ + } + file.Close() + if lineCount == 0 { + continue + } + fileLines[filepath.Base(filePath)] = lineCount + fmt.Println(filepath.Base(filePath), "文件行数:", lineCount) + + } + + var tasks []Task + for k, v := range fileLines { + tasks = append(tasks, Task{FilePath: k, FileLines: v}) + } + + // 使用errgroup控制并发 + g, ctx := errgroup.WithContext(context.Background()) + g.SetLimit(50) // 设置最大并发数为50 + // 执行所有任务 + for _, task := range tasks { + task := task // 创建局部变量 + g.Go(func() error { + select { + case <-ctx.Done(): + return ctx.Err() + default: + fmt.Println("正在上传文件:", filepath.Base(task.FilePath)) + processFile(task.FilePath, task.FileLines) + err := os.Truncate(task.FilePath, 0) + if err != nil { + fmt.Println("清空文件失败:", err) + } + return nil + } + }) + } + + // 等待所有任务完成 + if err := g.Wait(); err != nil { + fmt.Printf("任务执行出错: %v\n", err) + } else { + fmt.Println("所有任务执行完成!") + } + + fmt.Printf("上传完成,耗时:%s\n", time.Since(start)) + } + time.Sleep(time.Minute) + } +} + +// 获取目录中的所有txt文件 +func getTxtFiles(dir string) (txtFiles []string, err error) { + err = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + + // 只处理普通文件,跳过目录 + if !info.Mode().IsRegular() { + return nil + } + + // 检查文件扩展名是否为.txt + if strings.ToLower(filepath.Ext(path)) == ".txt" { + txtFiles = append(txtFiles, path) + } + + return nil + }) + + return txtFiles, err +} + +func processFile(filePath string, fileLines int) { + var wg sync.WaitGroup + // 打开文件 + file, err := os.Open(filePath) + if err != nil { + fmt.Printf("无法打开文件 %s: %v\n", filePath, err) + return + } + defer file.Close() + + // 创建行通道 + lines := make(chan string, 100) + + // 创建10个worker处理文件上传 + for i := 0; i < viper.GetInt("thread-count"); i++ { + wg.Add(1) + go func(workerID int) { + defer wg.Done() + processLines(lines, workerID, filePath) + }(i) + } + + // 读取文件并发送到通道 + scanner := bufio.NewScanner(file) + lineCount := 0 + for scanner.Scan() { + lines <- scanner.Text() + lineCount++ + if lineCount%10000 == 0 { + fmt.Printf("文件【%s】处理进度:%.2f%%\n", filePath, float64(lineCount)/float64(fileLines)*100) + } + } + + close(lines) + wg.Wait() + + if err := scanner.Err(); err != nil { + fmt.Printf("读取文件 %s 错误: %v\n", filePath, err) + return + } + + fmt.Printf("文件【%s】处理完成,共处理 %d 行数据\n", filePath, lineCount) +} + +func processLines(lines <-chan string, workerID int, filePath string) { + for line := range lines { + // 跳过空行 + if strings.TrimSpace(line) == "" { + continue + } + // 上传数据 + if err := api.UploadDataToServer(httpClient, line); err != nil { + fmt.Printf("Worker %d (文件 %s): 上传失败: %v\n", workerID, filePath, err) + } + } +} diff --git a/utils/folder/folder.go b/utils/folder/folder.go new file mode 100644 index 0000000..e3433ee --- /dev/null +++ b/utils/folder/folder.go @@ -0,0 +1,20 @@ +package folder + +/* +#cgo windows LDFLAGS: -lole32 -luuid + +#include "windows_dialog.h" +*/ +import "C" + +// OpenFolderDialog 调用Windows原生文件夹选择对话框 +// 返回选择的文件夹路径,如果用户取消选择则返回空字符串 +func OpenFolderDialog() string { + cPath := C.OpenFolderDialog() + if cPath == nil { + return "" + } + defer C.FreeMemory(cPath) + + return C.GoString(cPath) +} diff --git a/utils/folder/windows_dialog.c b/utils/folder/windows_dialog.c new file mode 100644 index 0000000..56a2ee1 --- /dev/null +++ b/utils/folder/windows_dialog.c @@ -0,0 +1,56 @@ +#include "windows_dialog.h" +#include +#include + +char* OpenFolderDialog(void) { + IFileOpenDialog *pFileOpen = NULL; + IShellItem *pItem = NULL; + PWSTR pszFilePath = NULL; + char* result = NULL; + + // 初始化COM库 + HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE); + if (FAILED(hr)) { + return NULL; + } + + // 创建FileOpenDialog对象 + hr = CoCreateInstance(&CLSID_FileOpenDialog, NULL, CLSCTX_ALL, &IID_IFileOpenDialog, (void**)&pFileOpen); + if (SUCCEEDED(hr)) { + // 设置为选择文件夹模式 + DWORD options; + pFileOpen->lpVtbl->GetOptions(pFileOpen, &options); + pFileOpen->lpVtbl->SetOptions(pFileOpen, options | FOS_PICKFOLDERS); + + // 显示对话框 + hr = pFileOpen->lpVtbl->Show(pFileOpen, NULL); + if (SUCCEEDED(hr)) { + // 获取结果 + hr = pFileOpen->lpVtbl->GetResult(pFileOpen, &pItem); + if (SUCCEEDED(hr)) { + // 获取文件夹路径 + hr = pItem->lpVtbl->GetDisplayName(pItem, SIGDN_FILESYSPATH, &pszFilePath); + if (SUCCEEDED(hr)) { + // 计算所需缓冲区大小并转换宽字符串到多字节字符串 + int size_needed = WideCharToMultiByte(CP_UTF8, 0, pszFilePath, -1, NULL, 0, NULL, NULL); + result = (char*)malloc(size_needed); + if (result) { + WideCharToMultiByte(CP_UTF8, 0, pszFilePath, -1, result, size_needed, NULL, NULL); + } + CoTaskMemFree(pszFilePath); + } + pItem->lpVtbl->Release(pItem); + } + } + pFileOpen->lpVtbl->Release(pFileOpen); + } + + CoUninitialize(); + return result; +} + +void FreeMemory(char* ptr) { + if (ptr) { + free(ptr); + } +} \ No newline at end of file diff --git a/utils/folder/windows_dialog.h b/utils/folder/windows_dialog.h new file mode 100644 index 0000000..24cf8a9 --- /dev/null +++ b/utils/folder/windows_dialog.h @@ -0,0 +1,11 @@ +#ifndef WINDOWS_DIALOG_H +#define WINDOWS_DIALOG_H + +#include +#include + +// 导出函数声明 +char* OpenFolderDialog(void); +void FreeMemory(char* ptr); + +#endif \ No newline at end of file