A:增加照片任务管理

This commit is contained in:
jimmy committed 2025-06-28 11:13:52 +08:00
1 parent f1d9764d1d
commit 25cc429091
11 files changed
+512 -126

No files matched your search

+2 -9
View File
@@ -6,6 +6,7 @@ require (
github.com/gin-contrib/cors v1.7.5 github.com/gin-contrib/cors v1.7.5
github.com/gin-contrib/pprof v1.5.3 github.com/gin-contrib/pprof v1.5.3
github.com/gin-gonic/gin v1.10.1 github.com/gin-gonic/gin v1.10.1
github.com/google/uuid v1.6.0
go.uber.org/zap v1.27.0 go.uber.org/zap v1.27.0
gopkg.in/natefinch/lumberjack.v2 v2.2.1 gopkg.in/natefinch/lumberjack.v2 v2.2.1
gorm.io/driver/mysql v1.6.0 gorm.io/driver/mysql v1.6.0
@@ -19,17 +20,13 @@ require (
github.com/bytedance/sonic v1.13.3 // indirect github.com/bytedance/sonic v1.13.3 // indirect
github.com/bytedance/sonic/loader v0.2.4 // indirect github.com/bytedance/sonic/loader v0.2.4 // indirect
github.com/cloudwego/base64x v0.1.5 // indirect github.com/cloudwego/base64x v0.1.5 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/gabriel-vasile/mimetype v1.4.9 // indirect github.com/gabriel-vasile/mimetype v1.4.9 // indirect
github.com/gin-contrib/sse v1.1.0 // indirect github.com/gin-contrib/sse v1.1.0 // indirect
github.com/glebarez/go-sqlite v1.21.2 // indirect
github.com/glebarez/sqlite v1.11.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.26.0 // indirect github.com/go-playground/validator/v10 v10.26.0 // indirect
github.com/go-sql-driver/mysql v1.8.1 // indirect github.com/go-sql-driver/mysql v1.8.1 // indirect
github.com/goccy/go-json v0.10.5 // indirect github.com/goccy/go-json v0.10.5 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect github.com/jinzhu/now v1.1.5 // indirect
github.com/json-iterator/go v1.1.12 // indirect github.com/json-iterator/go v1.1.12 // indirect
@@ -40,7 +37,6 @@ require (
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.14 // indirect github.com/ugorji/go/codec v1.2.14 // indirect
go.uber.org/multierr v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect
@@ -49,10 +45,7 @@ require (
golang.org/x/net v0.41.0 // indirect golang.org/x/net v0.41.0 // indirect
golang.org/x/sys v0.33.0 // indirect golang.org/x/sys v0.33.0 // indirect
golang.org/x/text v0.26.0 // indirect golang.org/x/text v0.26.0 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
google.golang.org/protobuf v1.36.6 // indirect google.golang.org/protobuf v1.36.6 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
modernc.org/libc v1.22.5 // indirect
modernc.org/mathutil v1.5.0 // indirect
modernc.org/memory v1.5.0 // indirect
modernc.org/sqlite v1.23.1 // indirect
) )
+5 -23
View File
@@ -11,8 +11,6 @@ github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQ
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY= github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY=
github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok= github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok=
github.com/gin-contrib/cors v1.7.5 h1:cXC9SmofOrRg0w9PigwGlHG3ztswH6bqq4vJVXnvYMk= github.com/gin-contrib/cors v1.7.5 h1:cXC9SmofOrRg0w9PigwGlHG3ztswH6bqq4vJVXnvYMk=
@@ -23,10 +21,6 @@ github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ= 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/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
github.com/glebarez/go-sqlite v1.21.2 h1:3a6LFC4sKahUunAmynQKLZceZCOzUthkRkEAl9gAXWo=
github.com/glebarez/go-sqlite v1.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k=
github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw=
github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= 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/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 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
@@ -42,11 +36,10 @@ github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PU
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= 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/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/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/inflection v1.0.0+incompatible/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= 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 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= 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 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
@@ -74,9 +67,6 @@ github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 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/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@@ -110,8 +100,8 @@ golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M= golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@@ -128,12 +118,4 @@ gorm.io/driver/sqlite v1.6.0 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ=
gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8= gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8=
gorm.io/gorm v1.30.0 h1:qbT5aPv1UH8gI99OsRlvDToLxW5zR7FzS9acZDOZcgs= gorm.io/gorm v1.30.0 h1:qbT5aPv1UH8gI99OsRlvDToLxW5zR7FzS9acZDOZcgs=
gorm.io/gorm v1.30.0/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE= gorm.io/gorm v1.30.0/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE=
modernc.org/libc v1.22.5 h1:91BNch/e5B0uPbJFgqbxXuOnxBQjlS//icfQEGmvyjE=
modernc.org/libc v1.22.5/go.mod h1:jj+Z7dTNX8fBScMVNRAYZ/jF91K8fdT2hYMThc3YjBY=
modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ=
modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
modernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds=
modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
modernc.org/sqlite v1.23.1 h1:nrSBg4aRQQwq59JpvGEQ15tNxoO5pX/kUjcRNwSAGQM=
modernc.org/sqlite v1.23.1/go.mod h1:OrDj17Mggn6MhE+iPbBNf7RGKODDE9NFT0f3EwDzJqk=
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
+17
View File
@@ -0,0 +1,17 @@
package container
import (
"rear/internal/workflow"
)
type TaskContainer struct {
// 照片任务处理管理
ImgTaskManager *workflow.ImgTaskManager
// 其他服务...
}
func NewTaskContainer() *TaskContainer {
return &TaskContainer{
ImgTaskManager: workflow.NewImgTaskManager(10),
}
}
+55 -3
View File
@@ -6,17 +6,19 @@ import (
"net/http" "net/http"
"rear/internal/container" "rear/internal/container"
"rear/internal/model" "rear/internal/model"
"rear/pkg/utils"
"strings" "strings"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
type LibraryHandler struct { type LibraryHandler struct {
container *container.Container container *container.Container
imgContain *container.TaskContainer
} }
func NewLibraryHandler(container *container.Container) *LibraryHandler { func NewLibraryHandler(container *container.Container, imgContain *container.TaskContainer) *LibraryHandler {
return &LibraryHandler{container: container} return &LibraryHandler{container: container, imgContain: imgContain}
} }
// GetLibrary 获取存储 // GetLibrary 获取存储
@@ -57,6 +59,7 @@ func (h *LibraryHandler) AddLibrary(c *gin.Context) {
// 验证路径 // 验证路径
path := strings.TrimSpace(req.Path) path := strings.TrimSpace(req.Path)
log.Printf("Add library path: %s", path)
if path == "" { if path == "" {
c.JSON(http.StatusBadRequest, model.Response{ c.JSON(http.StatusBadRequest, model.Response{
Code: http.StatusBadRequest, Code: http.StatusBadRequest,
@@ -139,3 +142,52 @@ func (h *LibraryHandler) DeleteLibrary(c *gin.Context) {
Message: "Library deleted successfully", Message: "Library deleted successfully",
}) })
} }
// LibraryIndex 开始图片检索【缩略图生成】
func (h *LibraryHandler) LibraryIndex(c *gin.Context) {
// 获取所有已添加路径
library, err := h.container.LibraryRepo.GetAllLibrary()
if err != nil {
c.JSON(http.StatusInternalServerError, model.Response{
Code: http.StatusInternalServerError,
Message: err.Error(),
})
return
}
if len(library) == 0 {
c.JSON(http.StatusInternalServerError, model.Response{
Code: http.StatusOK,
Message: "无可用路径, 任务取消!",
})
return
}
// 存在可用路径,检索开始
var dirs []string // 创建一个空的 string 切片
for i := range library {
path := library[i]
isDir := utils.FileUtils.IsDir(path.ImgPath)
log.Printf(path.ImgPath)
if isDir {
dirs = append(dirs, path.ImgPath)
}
}
if len(dirs) == 0 {
c.JSON(http.StatusInternalServerError, model.Response{
Code: http.StatusOK,
Message: "无可用路径, 任务结束!",
})
return
}
// 获取可用路径下所有的照片【指定类型】
//utils.FileUtils.GetAllFiles()
// 启动后台任务,开始处理
//h.imgContain.ImgTaskManager.AddTask()
c.JSON(http.StatusOK, model.Response{
Code: http.StatusOK,
Message: "索引任务已启动",
})
}
+4 -2
View File
@@ -8,7 +8,7 @@ import (
) )
// SetupRoutes 设置路由 // SetupRoutes 设置路由
func SetupRoutes(r *gin.Engine, contain *container.Container) { func SetupRoutes(r *gin.Engine, contain *container.Container, imgContain *container.TaskContainer) {
// 默认访问 // 默认访问
r.GET("/", handler.BasicResponse) r.GET("/", handler.BasicResponse)
@@ -16,7 +16,7 @@ func SetupRoutes(r *gin.Engine, contain *container.Container) {
r.GET("/health", handler.HealthCheck) r.GET("/health", handler.HealthCheck)
// 资料库处理 // 资料库处理
libraryHandler := handler.NewLibraryHandler(contain) libraryHandler := handler.NewLibraryHandler(contain, imgContain)
devImageHandler := handler.NewDevImageHandler(contain) devImageHandler := handler.NewDevImageHandler(contain)
// API版本组 // API版本组
v1 := r.Group("/api/v1") v1 := r.Group("/api/v1")
@@ -38,6 +38,8 @@ func SetupRoutes(r *gin.Engine, contain *container.Container) {
library.POST("", libraryHandler.AddLibrary) library.POST("", libraryHandler.AddLibrary)
library.PUT("", libraryHandler.UpdateLibrary) library.PUT("", libraryHandler.UpdateLibrary)
library.DELETE("", libraryHandler.DeleteLibrary) library.DELETE("", libraryHandler.DeleteLibrary)
// 执行检索任务
library.POST("indexed", libraryHandler.LibraryIndex)
} }
} }
// 开发组 // 开发组
+1 -1
View File
@@ -84,7 +84,7 @@ func NewTaskScheduler(workers int, queueSize int) *TaskScheduler {
ts.workerPool <- struct{}{} ts.workerPool <- struct{}{}
} }
// 启动worker // 启动 worker
ts.startWorkers() ts.startWorkers()
// 启动结果处理器 // 启动结果处理器
+327
View File
@@ -0,0 +1,327 @@
package workflow
import (
"bytes"
"context"
"crypto/sha1"
"encoding/hex"
"errors"
"fmt"
"github.com/google/uuid"
"os"
"os/exec"
"runtime"
"sync"
"time"
)
// --- 状态定义 ---
type TaskStatus string
const (
StatusPending TaskStatus = "pending"
StatusRunning TaskStatus = "running"
StatusPaused TaskStatus = "paused"
StatusFailed TaskStatus = "failed"
StatusDone TaskStatus = "done"
)
// --- PictureTask ---
type PictureTask struct {
ID string
Path string
Hash string
ImageBuf []byte
ExifData map[string]string
Status TaskStatus
Progress float64
Error error
ctx context.Context
cancel context.CancelFunc
mu sync.Mutex
pauseCh chan struct{}
resumeCh chan struct{}
}
func NewPictureTask(path string) *PictureTask {
ctx, cancel := context.WithCancel(context.Background())
return &PictureTask{
ID: uuid.New().String(),
Path: path,
Status: StatusPending,
ctx: ctx,
cancel: cancel,
pauseCh: make(chan struct{}, 1),
resumeCh: make(chan struct{}, 1),
}
}
func (pt *PictureTask) waitIfPaused() {
select {
case <-pt.pauseCh:
pt.setStatus(StatusPaused)
<-pt.resumeCh
pt.setStatus(StatusRunning)
default:
}
}
func (pt *PictureTask) Pause() {
select {
case pt.pauseCh <- struct{}{}:
default:
}
}
func (pt *PictureTask) Resume() {
select {
case pt.resumeCh <- struct{}{}:
default:
}
}
func (pt *PictureTask) Run() {
pt.setStatus(StatusRunning)
if !fileExists(pt.Path) {
pt.setError(errors.New("file does not exist"))
return
}
pt.waitIfPaused()
data, err := os.ReadFile(pt.Path)
if err != nil {
pt.setError(err)
return
}
pt.ImageBuf = data
pt.Hash = calcHash(data)
pt.waitIfPaused()
compressed, err := compressImage(data)
if err != nil {
pt.setError(err)
return
}
pt.ImageBuf = compressed
pt.waitIfPaused()
exif, err := readExifViaCmd(pt.Path)
if err != nil {
pt.setError(err)
return
}
pt.ExifData = exif
pt.waitIfPaused()
err = saveToDB(pt.Hash, exif)
if err != nil {
pt.setError(err)
return
}
pt.setDone()
}
func (pt *PictureTask) setError(err error) {
pt.mu.Lock()
defer pt.mu.Unlock()
pt.Status = StatusFailed
pt.Error = err
}
func (pt *PictureTask) setDone() {
pt.mu.Lock()
defer pt.mu.Unlock()
pt.Status = StatusDone
pt.Progress = 1.0
}
func (pt *PictureTask) setStatus(s TaskStatus) {
pt.mu.Lock()
defer pt.mu.Unlock()
pt.Status = s
}
// --- ImgTaskManager ---
type ImgTaskManager struct {
tasks map[string]*PictureTask
queue chan *PictureTask
workerPool chan struct{}
workerLimit int
mu sync.RWMutex
globalPause chan struct{}
globalResume chan struct{}
poolMu sync.Mutex
doneCount int
autoAdjust bool
}
func NewImgTaskManager(concurrency int) *ImgTaskManager {
tm := &ImgTaskManager{
tasks: make(map[string]*PictureTask),
queue: make(chan *PictureTask, 100),
workerPool: make(chan struct{}, concurrency),
workerLimit: concurrency,
globalPause: make(chan struct{}, 1),
globalResume: make(chan struct{}, 1),
autoAdjust: true,
}
go tm.run()
go tm.monitorCPU()
return tm
}
func (tm *ImgTaskManager) SetConcurrency(n int) {
tm.poolMu.Lock()
defer tm.poolMu.Unlock()
if n <= 0 || n == tm.workerLimit {
return
}
newPool := make(chan struct{}, n)
tm.mu.Lock()
used := len(tm.workerPool)
for i := 0; i < used && i < n; i++ {
newPool <- struct{}{}
}
tm.workerPool = newPool
tm.workerLimit = n
tm.mu.Unlock()
}
func (tm *ImgTaskManager) AddTask(path string) string {
task := NewPictureTask(path)
tm.mu.Lock()
tm.tasks[task.ID] = task
tm.mu.Unlock()
tm.queue <- task
return task.ID
}
func (tm *ImgTaskManager) PauseAll() {
select {
case tm.globalPause <- struct{}{}:
default:
}
}
func (tm *ImgTaskManager) ResumeAll() {
select {
case tm.globalResume <- struct{}{}:
default:
}
}
func (tm *ImgTaskManager) applyGlobalPauseResume(task *PictureTask) {
go func() {
for {
select {
case <-tm.globalPause:
task.Pause()
case <-tm.globalResume:
task.Resume()
}
}
}()
}
func (tm *ImgTaskManager) run() {
for task := range tm.queue {
tm.workerPool <- struct{}{}
tm.applyGlobalPauseResume(task)
go func(t *PictureTask) {
defer func() { <-tm.workerPool }()
t.Run()
tm.mu.Lock()
if t.Status == StatusDone {
tm.doneCount++
}
tm.mu.Unlock()
}(task)
}
}
func (tm *ImgTaskManager) GetStatus(id string) TaskStatus {
tm.mu.RLock()
defer tm.mu.RUnlock()
if task, ok := tm.tasks[id]; ok {
return task.Status
}
return "not_found"
}
func (tm *ImgTaskManager) DoneCount() int {
tm.mu.RLock()
defer tm.mu.RUnlock()
return tm.doneCount
}
func (tm *ImgTaskManager) RemainingCount() int {
tm.mu.RLock()
defer tm.mu.RUnlock()
return len(tm.tasks) - tm.doneCount
}
func (tm *ImgTaskManager) monitorCPU() {
ticker := time.NewTicker(5 * time.Second)
for range ticker.C {
if !tm.autoAdjust {
continue
}
cpuPercent := getCurrentCPUUsage()
if cpuPercent > 80 {
tm.SetConcurrency(tm.workerLimit / 2)
} else if cpuPercent < 40 {
tm.SetConcurrency(tm.workerLimit + 1)
}
}
}
// 模拟 CPU 使用率函数(替换为真实库,如 gopsutil)
func getCurrentCPUUsage() int {
return runtime.NumGoroutine() * 5 // 简单估算
}
// --- 工具函数 ---
func fileExists(path string) bool {
_, err := os.Stat(path)
return !os.IsNotExist(err)
}
func calcHash(data []byte) string {
h := sha1.Sum(data)
return hex.EncodeToString(h[:])
}
func compressImage(data []byte) ([]byte, error) {
time.Sleep(100 * time.Millisecond) // 模拟耗时
return data, nil
}
func readExifViaCmd(filePath string) (map[string]string, error) {
cmd := exec.Command("exiftool", filePath)
out, err := cmd.Output()
if err != nil {
return nil, err
}
result := make(map[string]string)
lines := bytes.Split(out, []byte("\n"))
for _, line := range lines {
kv := bytes.SplitN(line, []byte(":"), 2)
if len(kv) == 2 {
key := string(bytes.TrimSpace(kv[0]))
val := string(bytes.TrimSpace(kv[1]))
result[key] = val
}
}
return result, nil
}
func saveToDB(hash string, exif map[string]string) error {
fmt.Printf("Saving %s to DB with %d exif fields\n", hash, len(exif))
return nil
}
+7 -4
View File
@@ -52,6 +52,9 @@ func main() {
// 初始化基础服务(启动写操作处理协程) // 初始化基础服务(启动写操作处理协程)
repositories.InitBaseService() repositories.InitBaseService()
// 初始化照片管理任务
newTaskContainer := container.NewTaskContainer()
// 创建运行目录 // 创建运行目录
execPath, err := os.Executable() execPath, err := os.Executable()
execDir := filepath.Dir(execPath) execDir := filepath.Dir(execPath)
@@ -61,7 +64,7 @@ func main() {
srcDir := `.\tools\windows_amd64\exiftool\exiftool` // 源目录 srcDir := `.\tools\windows_amd64\exiftool\exiftool` // 源目录
// 复制整个目录 // 复制整个目录
if err := utils.CopyDir(srcDir, join); err != nil { if err := utils.FileUtils.CopyDir(srcDir, join); err != nil {
fmt.Printf("复制失败: %v\n", err) fmt.Printf("复制失败: %v\n", err)
return return
} }
@@ -72,10 +75,10 @@ func main() {
// 依赖注入 // 依赖注入
// 启动 http // 启动 http
startHttp(newContainer) startHttp(newContainer, newTaskContainer)
} }
func startHttp(con *container.Container) { func startHttp(con *container.Container, imgContain *container.TaskContainer) {
// 配置加载 // 配置加载
netConfig := config.InitConfig() netConfig := config.InitConfig()
// 设置Gin模式 // 设置Gin模式
@@ -103,7 +106,7 @@ func startHttp(con *container.Container) {
} }
// 设置路由 // 设置路由
router.SetupRoutes(r, con) router.SetupRoutes(r, con, imgContain)
// 创建HTTP服务器 // 创建HTTP服务器
srv := &http.Server{ srv := &http.Server{
+50 -44
View File
@@ -22,14 +22,20 @@ type FileInfo struct {
Ext string `json:"ext"` Ext string `json:"ext"`
} }
// fileUtilsStruct 用于封装文件工具方法
type fileUtilsStruct struct{}
// FileUtils 是对外暴露的文件工具对象
var FileUtils = fileUtilsStruct{}
// 1. 检查文件或目录是否存在 // 1. 检查文件或目录是否存在
func Exists(path string) bool { func (fileUtilsStruct) Exists(path string) bool {
_, err := os.Stat(path) _, err := os.Stat(path)
return !os.IsNotExist(err) return !os.IsNotExist(err)
} }
// 2. 检查是否为目录 // 2. 检查是否为目录
func IsDir(path string) bool { func (fileUtilsStruct) IsDir(path string) bool {
info, err := os.Stat(path) info, err := os.Stat(path)
if err != nil { if err != nil {
return false return false
@@ -38,7 +44,7 @@ func IsDir(path string) bool {
} }
// 3. 检查是否为文件 // 3. 检查是否为文件
func IsFile(path string) bool { func (fileUtilsStruct) IsFile(path string) bool {
info, err := os.Stat(path) info, err := os.Stat(path)
if err != nil { if err != nil {
return false return false
@@ -47,7 +53,7 @@ func IsFile(path string) bool {
} }
// 4. 获取指定目录下的所有文件夹 // 4. 获取指定目录下的所有文件夹
func GetDirectories(dirPath string) ([]string, error) { func (fileUtilsStruct) GetDirectories(dirPath string) ([]string, error) {
var dirs []string var dirs []string
entries, err := os.ReadDir(dirPath) entries, err := os.ReadDir(dirPath)
@@ -65,7 +71,7 @@ func GetDirectories(dirPath string) ([]string, error) {
} }
// 5. 获取指定目录下指定格式的文件 // 5. 获取指定目录下指定格式的文件
func GetFilesByExtension(dirPath string, extensions ...string) ([]string, error) { func (fileUtilsStruct) GetFilesByExtension(dirPath string, extensions ...string) ([]string, error) {
var files []string var files []string
// 标准化扩展名(确保以.开头) // 标准化扩展名(确保以.开头)
@@ -97,8 +103,8 @@ func GetFilesByExtension(dirPath string, extensions ...string) ([]string, error)
} }
// 6. 找到指定目录下第一个符合格式的文件 // 6. 找到指定目录下第一个符合格式的文件
func GetFirstFileByExtension(dirPath string, extensions ...string) (string, error) { func (f fileUtilsStruct) GetFirstFileByExtension(dirPath string, extensions ...string) (string, error) {
files, err := GetFilesByExtension(dirPath, extensions...) files, err := f.GetFilesByExtension(dirPath, extensions...)
if err != nil { if err != nil {
return "", err return "", err
} }
@@ -111,7 +117,7 @@ func GetFirstFileByExtension(dirPath string, extensions ...string) (string, erro
} }
// 7. 递归获取目录下所有文件 // 7. 递归获取目录下所有文件
func GetAllFiles(dirPath string, recursive bool) ([]FileInfo, error) { func (fileUtilsStruct) GetAllFiles(dirPath string, recursive bool) ([]FileInfo, error) {
var files []FileInfo var files []FileInfo
if recursive { if recursive {
@@ -161,7 +167,7 @@ func GetAllFiles(dirPath string, recursive bool) ([]FileInfo, error) {
} }
// 递归获取目录下所有文件夹 // 递归获取目录下所有文件夹
func GetAllDirs(dirPath string, recursive bool) ([]FileInfo, error) { func (fileUtilsStruct) GetAllDirs(dirPath string, recursive bool) ([]FileInfo, error) {
var dirs []FileInfo var dirs []FileInfo
if recursive { if recursive {
@@ -212,8 +218,8 @@ func GetAllDirs(dirPath string, recursive bool) ([]FileInfo, error) {
} }
// 8. 删除文件或目录 // 8. 删除文件或目录
func Delete(path string) error { func (f fileUtilsStruct) Delete(path string) error {
if !Exists(path) { if !f.Exists(path) {
return fmt.Errorf("路径不存在: %s", path) return fmt.Errorf("路径不存在: %s", path)
} }
@@ -221,12 +227,12 @@ func Delete(path string) error {
} }
// 9. 创建目录(递归创建) // 9. 创建目录(递归创建)
func CreateDir(dirPath string) error { func (fileUtilsStruct) CreateDir(dirPath string) error {
return os.MkdirAll(dirPath, 0755) return os.MkdirAll(dirPath, 0755)
} }
// 10. 复制文件 // 10. 复制文件
func CopyFile(src, dst string) error { func (f fileUtilsStruct) CopyFile(src, dst string) error {
sourceFile, err := os.Open(src) sourceFile, err := os.Open(src)
if err != nil { if err != nil {
return fmt.Errorf("打开源文件失败: %w", err) return fmt.Errorf("打开源文件失败: %w", err)
@@ -241,7 +247,7 @@ func CopyFile(src, dst string) error {
// 确保目标目录存在 // 确保目标目录存在
dstDir := filepath.Dir(dst) dstDir := filepath.Dir(dst)
if err := CreateDir(dstDir); err != nil { if err := f.CreateDir(dstDir); err != nil {
return fmt.Errorf("创建目标目录失败: %w", err) return fmt.Errorf("创建目标目录失败: %w", err)
} }
@@ -261,7 +267,7 @@ func CopyFile(src, dst string) error {
} }
// CopyDir 递归复制整个目录 // CopyDir 递归复制整个目录
func CopyDir(src, dst string) error { func (f fileUtilsStruct) CopyDir(src, dst string) error {
// 获取源目录信息 // 获取源目录信息
srcInfo, err := os.Stat(src) srcInfo, err := os.Stat(src)
if err != nil { if err != nil {
@@ -291,12 +297,12 @@ func CopyDir(src, dst string) error {
if entry.IsDir() { if entry.IsDir() {
// 如果是目录,递归复制 // 如果是目录,递归复制
if err := CopyDir(srcPath, dstPath); err != nil { if err := f.CopyDir(srcPath, dstPath); err != nil {
return fmt.Errorf("复制子目录 %s 失败: %w", entry.Name(), err) return fmt.Errorf("复制子目录 %s 失败: %w", entry.Name(), err)
} }
} else { } else {
// 如果是文件,使用你的 CopyFile 函数 // 如果是文件,使用你的 CopyFile 函数
if err := CopyFile(srcPath, dstPath); err != nil { if err := f.CopyFile(srcPath, dstPath); err != nil {
return fmt.Errorf("复制文件 %s 失败: %w", entry.Name(), err) return fmt.Errorf("复制文件 %s 失败: %w", entry.Name(), err)
} }
} }
@@ -311,7 +317,7 @@ type CopyOptions struct {
SkipSymlinks bool // 是否跳过符号链接 SkipSymlinks bool // 是否跳过符号链接
} }
func CopyDirWithOptions(src, dst string, opts *CopyOptions) error { func (f fileUtilsStruct) CopyDirWithOptions(src, dst string, opts *CopyOptions) error {
if opts == nil { if opts == nil {
opts = &CopyOptions{Overwrite: true, SkipSymlinks: false} opts = &CopyOptions{Overwrite: true, SkipSymlinks: false}
} }
@@ -358,7 +364,7 @@ func CopyDirWithOptions(src, dst string, opts *CopyOptions) error {
if opts.SkipSymlinks { if opts.SkipSymlinks {
continue continue
} }
if err := copySymlink(srcPath, dstPath); err != nil { if err := f.copySymlink(srcPath, dstPath); err != nil {
return fmt.Errorf("复制符号链接 %s 失败: %w", entry.Name(), err) return fmt.Errorf("复制符号链接 %s 失败: %w", entry.Name(), err)
} }
continue continue
@@ -366,7 +372,7 @@ func CopyDirWithOptions(src, dst string, opts *CopyOptions) error {
if entry.IsDir() { if entry.IsDir() {
// 递归复制子目录 // 递归复制子目录
if err := CopyDirWithOptions(srcPath, dstPath, opts); err != nil { if err := f.CopyDirWithOptions(srcPath, dstPath, opts); err != nil {
return fmt.Errorf("复制子目录 %s 失败: %w", entry.Name(), err) return fmt.Errorf("复制子目录 %s 失败: %w", entry.Name(), err)
} }
} else { } else {
@@ -378,7 +384,7 @@ func CopyDirWithOptions(src, dst string, opts *CopyOptions) error {
} }
// 复制文件 // 复制文件
if err := CopyFile(srcPath, dstPath); err != nil { if err := f.CopyFile(srcPath, dstPath); err != nil {
return fmt.Errorf("复制文件 %s 失败: %w", entry.Name(), err) return fmt.Errorf("复制文件 %s 失败: %w", entry.Name(), err)
} }
} }
@@ -388,7 +394,7 @@ func CopyDirWithOptions(src, dst string, opts *CopyOptions) error {
} }
// copySymlink 复制符号链接 // copySymlink 复制符号链接
func copySymlink(src, dst string) error { func (fileUtilsStruct) copySymlink(src, dst string) error {
link, err := os.Readlink(src) link, err := os.Readlink(src)
if err != nil { if err != nil {
return err return err
@@ -401,10 +407,10 @@ func copySymlink(src, dst string) error {
} }
// 11. 移动/重命名文件 // 11. 移动/重命名文件
func MoveFile(src, dst string) error { func (f fileUtilsStruct) MoveFile(src, dst string) error {
// 确保目标目录存在 // 确保目标目录存在
dstDir := filepath.Dir(dst) dstDir := filepath.Dir(dst)
if err := CreateDir(dstDir); err != nil { if err := f.CreateDir(dstDir); err != nil {
return fmt.Errorf("创建目标目录失败: %w", err) return fmt.Errorf("创建目标目录失败: %w", err)
} }
@@ -412,7 +418,7 @@ func MoveFile(src, dst string) error {
} }
// 12. 读取文件全部内容 // 12. 读取文件全部内容
func ReadFile(filePath string) (string, error) { func (fileUtilsStruct) ReadFile(filePath string) (string, error) {
content, err := os.ReadFile(filePath) content, err := os.ReadFile(filePath)
if err != nil { if err != nil {
return "", fmt.Errorf("读取文件失败: %w", err) return "", fmt.Errorf("读取文件失败: %w", err)
@@ -421,7 +427,7 @@ func ReadFile(filePath string) (string, error) {
} }
// 13. 按行读取文件 // 13. 按行读取文件
func ReadFileLines(filePath string) ([]string, error) { func (fileUtilsStruct) ReadFileLines(filePath string) ([]string, error) {
file, err := os.Open(filePath) file, err := os.Open(filePath)
if err != nil { if err != nil {
return nil, fmt.Errorf("打开文件失败: %w", err) return nil, fmt.Errorf("打开文件失败: %w", err)
@@ -442,10 +448,10 @@ func ReadFileLines(filePath string) ([]string, error) {
} }
// 14. 写入文件内容 // 14. 写入文件内容
func WriteFile(filePath, content string) error { func (f fileUtilsStruct) WriteFile(filePath, content string) error {
// 确保目录存在 // 确保目录存在
dir := filepath.Dir(filePath) dir := filepath.Dir(filePath)
if err := CreateDir(dir); err != nil { if err := f.CreateDir(dir); err != nil {
return fmt.Errorf("创建目录失败: %w", err) return fmt.Errorf("创建目录失败: %w", err)
} }
@@ -453,10 +459,10 @@ func WriteFile(filePath, content string) error {
} }
// 15. 追加写入文件 // 15. 追加写入文件
func AppendFile(filePath, content string) error { func (f fileUtilsStruct) AppendFile(filePath, content string) error {
// 确保目录存在 // 确保目录存在
dir := filepath.Dir(filePath) dir := filepath.Dir(filePath)
if err := CreateDir(dir); err != nil { if err := f.CreateDir(dir); err != nil {
return fmt.Errorf("创建目录失败: %w", err) return fmt.Errorf("创建目录失败: %w", err)
} }
@@ -471,7 +477,7 @@ func AppendFile(filePath, content string) error {
} }
// 16. 获取文件大小 // 16. 获取文件大小
func GetFileSize(filePath string) (int64, error) { func (fileUtilsStruct) GetFileSize(filePath string) (int64, error) {
info, err := os.Stat(filePath) info, err := os.Stat(filePath)
if err != nil { if err != nil {
return 0, fmt.Errorf("获取文件信息失败: %w", err) return 0, fmt.Errorf("获取文件信息失败: %w", err)
@@ -480,7 +486,7 @@ func GetFileSize(filePath string) (int64, error) {
} }
// 17. 获取文件修改时间 // 17. 获取文件修改时间
func GetModTime(filePath string) (time.Time, error) { func (fileUtilsStruct) GetModTime(filePath string) (time.Time, error) {
info, err := os.Stat(filePath) info, err := os.Stat(filePath)
if err != nil { if err != nil {
return time.Time{}, fmt.Errorf("获取文件信息失败: %w", err) return time.Time{}, fmt.Errorf("获取文件信息失败: %w", err)
@@ -489,29 +495,29 @@ func GetModTime(filePath string) (time.Time, error) {
} }
// 18. 获取文件扩展名 // 18. 获取文件扩展名
func GetExtension(filePath string) string { func (fileUtilsStruct) GetExtension(filePath string) string {
return filepath.Ext(filePath) return filepath.Ext(filePath)
} }
// 19. 获取文件名(不含扩展名) // 19. 获取文件名(不含扩展名)
func GetFileName(filePath string) string { func (fileUtilsStruct) GetFileName(filePath string) string {
base := filepath.Base(filePath) base := filepath.Base(filePath)
ext := filepath.Ext(base) ext := filepath.Ext(base)
return strings.TrimSuffix(base, ext) return strings.TrimSuffix(base, ext)
} }
// 20. 获取文件基本名称(含扩展名) // 20. 获取文件基本名称(含扩展名)
func GetBaseName(filePath string) string { func (fileUtilsStruct) GetBaseName(filePath string) string {
return filepath.Base(filePath) return filepath.Base(filePath)
} }
// 21. 获取文件目录 // 21. 获取文件目录
func GetDir(filePath string) string { func (fileUtilsStruct) GetDir(filePath string) string {
return filepath.Dir(filePath) return filepath.Dir(filePath)
} }
// 22. 计算文件MD5哈希值 // 22. 计算文件MD5哈希值
func GetFileMD5(filePath string) (string, error) { func (fileUtilsStruct) GetFileMD5(filePath string) (string, error) {
file, err := os.Open(filePath) file, err := os.Open(filePath)
if err != nil { if err != nil {
return "", fmt.Errorf("打开文件失败: %w", err) return "", fmt.Errorf("打开文件失败: %w", err)
@@ -527,7 +533,7 @@ func GetFileMD5(filePath string) (string, error) {
} }
// 23. 计算文件SHA256哈希值 // 23. 计算文件SHA256哈希值
func GetFileSHA256(filePath string) (string, error) { func (fileUtilsStruct) GetFileSHA256(filePath string) (string, error) {
file, err := os.Open(filePath) file, err := os.Open(filePath)
if err != nil { if err != nil {
return "", fmt.Errorf("打开文件失败: %w", err) return "", fmt.Errorf("打开文件失败: %w", err)
@@ -543,7 +549,7 @@ func GetFileSHA256(filePath string) (string, error) {
} }
// 24. 清空目录内容(保留目录本身) // 24. 清空目录内容(保留目录本身)
func CleanDir(dirPath string) error { func (fileUtilsStruct) CleanDir(dirPath string) error {
entries, err := os.ReadDir(dirPath) entries, err := os.ReadDir(dirPath)
if err != nil { if err != nil {
return fmt.Errorf("读取目录失败: %w", err) return fmt.Errorf("读取目录失败: %w", err)
@@ -560,7 +566,7 @@ func CleanDir(dirPath string) error {
} }
// 25. 获取目录大小 // 25. 获取目录大小
func GetDirSize(dirPath string) (int64, error) { func (fileUtilsStruct) GetDirSize(dirPath string) (int64, error) {
var size int64 var size int64
err := filepath.Walk(dirPath, func(path string, info os.FileInfo, err error) error { err := filepath.Walk(dirPath, func(path string, info os.FileInfo, err error) error {
@@ -577,7 +583,7 @@ func GetDirSize(dirPath string) (int64, error) {
} }
// 26. 格式化文件大小 // 26. 格式化文件大小
func FormatFileSize(bytes int64) string { func (fileUtilsStruct) FormatFileSize(bytes int64) string {
const unit = 1024 const unit = 1024
if bytes < unit { if bytes < unit {
return fmt.Sprintf("%d B", bytes) return fmt.Sprintf("%d B", bytes)
@@ -591,7 +597,7 @@ func FormatFileSize(bytes int64) string {
} }
// 27. 搜索文件(按文件名模式) // 27. 搜索文件(按文件名模式)
func SearchFiles(dirPath, pattern string, recursive bool) ([]string, error) { func (fileUtilsStruct) SearchFiles(dirPath, pattern string, recursive bool) ([]string, error) {
var matches []string var matches []string
if recursive { if recursive {
@@ -634,11 +640,11 @@ func SearchFiles(dirPath, pattern string, recursive bool) ([]string, error) {
} }
// 28. 获取当前工作目录 // 28. 获取当前工作目录
func GetCurrentDir() (string, error) { func (fileUtilsStruct) GetCurrentDir() (string, error) {
return os.Getwd() return os.Getwd()
} }
// 29. 改变工作目录 // 29. 改变工作目录
func ChangeDir(dirPath string) error { func (fileUtilsStruct) ChangeDir(dirPath string) error {
return os.Chdir(dirPath) return os.Chdir(dirPath)
} }
+42 -38
View File
@@ -14,6 +14,10 @@ import (
"sync" "sync"
) )
type hashUtilsStruct struct{}
var HashUtils = hashUtilsStruct{}
// HashType 定义支持的Hash算法类型 // HashType 定义支持的Hash算法类型
type HashType int type HashType int
@@ -40,7 +44,7 @@ type HashResult struct {
} }
// getHasher 根据类型返回对应的Hash实例 // getHasher 根据类型返回对应的Hash实例
func getHasher(hashType HashType) hash.Hash { func (h hashUtilsStruct) getHasher(hashType HashType) hash.Hash {
switch hashType { switch hashType {
case MD5: case MD5:
return md5.New() return md5.New()
@@ -56,7 +60,7 @@ func getHasher(hashType HashType) hash.Hash {
} }
// getHasherName 获取Hash算法名称 // getHasherName 获取Hash算法名称
func getHasherName(hashType HashType) string { func (h hashUtilsStruct) getHasherName(hashType HashType) string {
switch hashType { switch hashType {
case MD5: case MD5:
return "MD5" return "MD5"
@@ -72,21 +76,21 @@ func getHasherName(hashType HashType) string {
} }
// HashString 计算字符串的Hash值 // HashString 计算字符串的Hash值
func HashString(data string, hashType HashType) string { func (h hashUtilsStruct) HashString(data string, hashType HashType) string {
hasher := getHasher(hashType) hasher := h.getHasher(hashType)
hasher.Write([]byte(data)) hasher.Write([]byte(data))
return fmt.Sprintf("%x", hasher.Sum(nil)) return fmt.Sprintf("%x", hasher.Sum(nil))
} }
// HashBytes 计算字节数组的Hash值 // HashBytes 计算字节数组的Hash值
func HashBytes(data []byte, hashType HashType) string { func (h hashUtilsStruct) HashBytes(data []byte, hashType HashType) string {
hasher := getHasher(hashType) hasher := h.getHasher(hashType)
hasher.Write(data) hasher.Write(data)
return fmt.Sprintf("%x", hasher.Sum(nil)) return fmt.Sprintf("%x", hasher.Sum(nil))
} }
// HashFile 计算单个文件的Hash值(自动选择优化策略) // HashFile 计算单个文件的Hash值(自动选择优化策略)
func HashFile(filename string, hashType HashType) (string, error) { func (h hashUtilsStruct) HashFile(filename string, hashType HashType) (string, error) {
file, err := os.Open(filename) file, err := os.Open(filename)
if err != nil { if err != nil {
return "", err return "", err
@@ -99,20 +103,20 @@ func HashFile(filename string, hashType HashType) (string, error) {
return "", err return "", err
} }
hasher := getHasher(hashType) hasher := h.getHasher(hashType)
// 根据文件大小选择不同的处理策略 // 根据文件大小选择不同的处理策略
if fileInfo.Size() <= SmallFileThreshold { if fileInfo.Size() <= SmallFileThreshold {
// 小文件:直接读取 // 小文件:直接读取
return hashSmallFile(file, hasher) return h.hashSmallFile(file, hasher)
} else { } else {
// 大文件:使用缓冲区 // 大文件:使用缓冲区
return hashLargeFile(file, hasher, fileInfo.Size()) return h.hashLargeFile(file, hasher, fileInfo.Size())
} }
} }
// hashSmallFile 处理小文件 // hashSmallFile 处理小文件
func hashSmallFile(file *os.File, hasher hash.Hash) (string, error) { func (h hashUtilsStruct) hashSmallFile(file *os.File, hasher hash.Hash) (string, error) {
_, err := io.Copy(hasher, file) _, err := io.Copy(hasher, file)
if err != nil { if err != nil {
return "", err return "", err
@@ -121,7 +125,7 @@ func hashSmallFile(file *os.File, hasher hash.Hash) (string, error) {
} }
// hashLargeFile 处理大文件 // hashLargeFile 处理大文件
func hashLargeFile(file *os.File, hasher hash.Hash, fileSize int64) (string, error) { func (h hashUtilsStruct) hashLargeFile(file *os.File, hasher hash.Hash, fileSize int64) (string, error) {
// 根据文件大小动态调整缓冲区 // 根据文件大小动态调整缓冲区
bufferSize := DefaultBufferSize bufferSize := DefaultBufferSize
if fileSize > 100*1024*1024 { // 大于100MB使用更大缓冲区 if fileSize > 100*1024*1024 { // 大于100MB使用更大缓冲区
@@ -147,7 +151,7 @@ func hashLargeFile(file *os.File, hasher hash.Hash, fileSize int64) (string, err
} }
// HashFileWithProgress 计算文件Hash值并提供进度回调 // HashFileWithProgress 计算文件Hash值并提供进度回调
func HashFileWithProgress(filename string, hashType HashType, progressCallback func(processed, total int64)) (string, error) { func (h hashUtilsStruct) HashFileWithProgress(filename string, hashType HashType, progressCallback func(processed, total int64)) (string, error) {
file, err := os.Open(filename) file, err := os.Open(filename)
if err != nil { if err != nil {
return "", err return "", err
@@ -159,7 +163,7 @@ func HashFileWithProgress(filename string, hashType HashType, progressCallback f
return "", err return "", err
} }
hasher := getHasher(hashType) hasher := h.getHasher(hashType)
buffer := make([]byte, DefaultBufferSize) buffer := make([]byte, DefaultBufferSize)
var processed int64 var processed int64
@@ -184,7 +188,7 @@ func HashFileWithProgress(filename string, hashType HashType, progressCallback f
} }
// HashMultipleFiles 并发计算多个文件的Hash值 // HashMultipleFiles 并发计算多个文件的Hash值
func HashMultipleFiles(filenames []string, hashType HashType) []HashResult { func (h hashUtilsStruct) HashMultipleFiles(filenames []string, hashType HashType) []HashResult {
// 使用CPU核心数作为并发数 // 使用CPU核心数作为并发数
numWorkers := runtime.NumCPU() numWorkers := runtime.NumCPU()
jobs := make(chan string, len(filenames)) jobs := make(chan string, len(filenames))
@@ -197,7 +201,7 @@ func HashMultipleFiles(filenames []string, hashType HashType) []HashResult {
go func() { go func() {
defer wg.Done() defer wg.Done()
for filename := range jobs { for filename := range jobs {
hash, err := HashFile(filename, hashType) hash, err := h.HashFile(filename, hashType)
var size int64 var size int64
if err == nil { if err == nil {
@@ -240,13 +244,13 @@ func HashMultipleFiles(filenames []string, hashType HashType) []HashResult {
} }
// CompareFiles 比较两个文件是否相同(通过Hash值) // CompareFiles 比较两个文件是否相同(通过Hash值)
func CompareFiles(file1, file2 string, hashType HashType) (bool, error) { func (h hashUtilsStruct) CompareFiles(file1, file2 string, hashType HashType) (bool, error) {
hash1, err := HashFile(file1, hashType) hash1, err := h.HashFile(file1, hashType)
if err != nil { if err != nil {
return false, err return false, err
} }
hash2, err := HashFile(file2, hashType) hash2, err := h.HashFile(file2, hashType)
if err != nil { if err != nil {
return false, err return false, err
} }
@@ -255,8 +259,8 @@ func CompareFiles(file1, file2 string, hashType HashType) (bool, error) {
} }
// FindDuplicates 在文件列表中查找重复文件 // FindDuplicates 在文件列表中查找重复文件
func FindDuplicates(filenames []string, hashType HashType) map[string][]string { func (h hashUtilsStruct) FindDuplicates(filenames []string, hashType HashType) map[string][]string {
results := HashMultipleFiles(filenames, hashType) results := h.HashMultipleFiles(filenames, hashType)
duplicates := make(map[string][]string) duplicates := make(map[string][]string)
for _, result := range results { for _, result := range results {
@@ -276,8 +280,8 @@ func FindDuplicates(filenames []string, hashType HashType) map[string][]string {
} }
// VerifyFileIntegrity 验证文件完整性 // VerifyFileIntegrity 验证文件完整性
func VerifyFileIntegrity(filename, expectedHash string, hashType HashType) (bool, error) { func (h hashUtilsStruct) VerifyFileIntegrity(filename, expectedHash string, hashType HashType) (bool, error) {
actualHash, err := HashFile(filename, hashType) actualHash, err := h.HashFile(filename, hashType)
if err != nil { if err != nil {
return false, err return false, err
} }
@@ -287,7 +291,7 @@ func VerifyFileIntegrity(filename, expectedHash string, hashType HashType) (bool
} }
// HashFileMultipleAlgorithms 使用多种算法计算文件Hash // HashFileMultipleAlgorithms 使用多种算法计算文件Hash
func HashFileMultipleAlgorithms(filename string, hashTypes []HashType) (map[string]string, error) { func (h hashUtilsStruct) HashFileMultipleAlgorithms(filename string, hashTypes []HashType) (map[string]string, error) {
file, err := os.Open(filename) file, err := os.Open(filename)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -297,7 +301,7 @@ func HashFileMultipleAlgorithms(filename string, hashTypes []HashType) (map[stri
// 创建多个hasher // 创建多个hasher
hashers := make(map[HashType]hash.Hash) hashers := make(map[HashType]hash.Hash)
for _, hashType := range hashTypes { for _, hashType := range hashTypes {
hashers[hashType] = getHasher(hashType) hashers[hashType] = h.getHasher(hashType)
} }
// 创建MultiWriter // 创建MultiWriter
@@ -316,7 +320,7 @@ func HashFileMultipleAlgorithms(filename string, hashTypes []HashType) (map[stri
// 收集结果 // 收集结果
results := make(map[string]string) results := make(map[string]string)
for hashType, hasher := range hashers { for hashType, hasher := range hashers {
hashName := getHasherName(hashType) hashName := h.getHasherName(hashType)
results[hashName] = fmt.Sprintf("%x", hasher.Sum(nil)) results[hashName] = fmt.Sprintf("%x", hasher.Sum(nil))
} }
@@ -324,26 +328,26 @@ func HashFileMultipleAlgorithms(filename string, hashTypes []HashType) (map[stri
} }
// 便捷函数 // 便捷函数
func MD5File(filename string) (string, error) { func (h hashUtilsStruct) MD5File(filename string) (string, error) {
return HashFile(filename, MD5) return h.HashFile(filename, MD5)
} }
func SHA1File(filename string) (string, error) { func (h hashUtilsStruct) SHA1File(filename string) (string, error) {
return HashFile(filename, SHA1) return h.HashFile(filename, SHA1)
} }
func SHA256File(filename string) (string, error) { func (h hashUtilsStruct) SHA256File(filename string) (string, error) {
return HashFile(filename, SHA256) return h.HashFile(filename, SHA256)
} }
func SHA512File(filename string) (string, error) { func (h hashUtilsStruct) SHA512File(filename string) (string, error) {
return HashFile(filename, SHA512) return h.HashFile(filename, SHA512)
} }
func MD5String(data string) string { func (h hashUtilsStruct) MD5String(data string) string {
return HashString(data, MD5) return h.HashString(data, MD5)
} }
func SHA256String(data string) string { func (h hashUtilsStruct) SHA256String(data string) string {
return HashString(data, SHA256) return h.HashString(data, SHA256)
} }
+2 -2
View File
@@ -53,7 +53,7 @@ func TestMain(m *testing.M) {
fmt.Println("Testing SHA-256...") fmt.Println("Testing SHA-256...")
for i := 0; i < 5; i++ { for i := 0; i < 5; i++ {
start256 := time.Now() start256 := time.Now()
sha256Time, err := SHA256File(fileName) sha256Time, err := HashUtils.SHA256File(fileName)
elapsed256 := time.Since(start256) elapsed256 := time.Since(start256)
if err != nil { if err != nil {
@@ -66,7 +66,7 @@ func TestMain(m *testing.M) {
fmt.Println("Testing SHA-512...") fmt.Println("Testing SHA-512...")
for i := 0; i < 5; i++ { for i := 0; i < 5; i++ {
start512 := time.Now() start512 := time.Now()
sha512Time, err := SHA512File(fileName) sha512Time, err := HashUtils.SHA512File(fileName)
elapsed512 := time.Since(start512) elapsed512 := time.Since(start512)
if err != nil { if err != nil {
fmt.Println("SHA-512 error:", err) fmt.Println("SHA-512 error:", err)