diff --git a/backend/app/events/subscribers/post_created.go b/backend/app/events/subscribers/post_created.go index 2013046..cb5ad50 100644 --- a/backend/app/events/subscribers/post_created.go +++ b/backend/app/events/subscribers/post_created.go @@ -88,6 +88,18 @@ func (e *PostCreated) Handler(msg *message.Message) ([]*message.Message, error) Hash: video.Hash, TenantID: post.TenantID, UserID: post.UserID, + Mark: "audio-preview", + }, nil) + if err != nil { + return nil, err + } + + _, err = job.Insert(context.Background(), jobs.PostVideoExtractAudioJob{ + PostID: post.ID, + Hash: video.Hash, + TenantID: post.TenantID, + UserID: post.UserID, + Mark: "audio", }, nil) if err != nil { return nil, err diff --git a/backend/app/http/medias/service.go b/backend/app/http/medias/service.go index d5af069..5faf9f1 100644 --- a/backend/app/http/medias/service.go +++ b/backend/app/http/medias/service.go @@ -3,6 +3,7 @@ package medias import ( "context" "database/sql" + "time" "backend/database/models/qvyun_v2/public/model" "backend/database/models/qvyun_v2/public/table" @@ -31,6 +32,14 @@ func (svc *Service) Create(ctx context.Context, m *model.Medias) (*model.Medias, _, span := otel.Start(ctx, "medias.service.Create") defer span.End() + if m.CreatedAt.IsZero() { + m.CreatedAt = time.Now() + } + + if m.UpdatedAt.IsZero() { + m.UpdatedAt = time.Now() + } + tbl := table.Medias stmt := tbl.INSERT(tbl.MutableColumns).MODEL(m).RETURNING(tbl.AllColumns) span.SetAttributes(semconv.DBStatementKey.String(stmt.DebugSql())) @@ -72,3 +81,30 @@ func (svc *Service) GetMediasByHash(ctx context.Context, tenantID, userID int64, return &item }), nil } + +func (svc *Service) GetMediaByHash(ctx context.Context, tenantID, userID int64, hash string) (*model.Medias, error) { + _, span := otel.Start(ctx, "medias.service.GetMediasByHash") + defer span.End() + + tbl := table.Medias + stmt := tbl. + SELECT(tbl.AllColumns). + LIMIT(1). + WHERE( + tbl.TenantID. + EQ(Int64(tenantID)). + AND( + tbl.UserID.EQ(Int64(userID)), + ). + AND( + tbl.Hash.EQ(String(hash)), + ), + ) + span.SetAttributes(semconv.DBStatementKey.String(stmt.DebugSql())) + + var ret model.Medias + if err := stmt.QueryContext(ctx, svc.db, &ret); err != nil { + return nil, err + } + return &ret, nil +} diff --git a/backend/app/http/posts/service.go b/backend/app/http/posts/service.go index 1ed63c9..b09944d 100644 --- a/backend/app/http/posts/service.go +++ b/backend/app/http/posts/service.go @@ -7,6 +7,7 @@ import ( "backend/app/requests" "backend/database" + "backend/database/fields" "backend/database/models/qvyun_v2/public/model" "backend/database/models/qvyun_v2/public/table" "backend/providers/hashids" @@ -308,8 +309,10 @@ func (svc *Service) Update(ctx context.Context, tenantID, userID, postID int64, attribute.Int64("user.id", userID), attribute.Int64("post.id", postID), ) - tbl := table.Posts + post.UpdatedAt = time.Now() + + tbl := table.Posts stmt := tbl. UPDATE( tbl.MutableColumns.Except( @@ -332,3 +335,84 @@ func (svc *Service) Update(ctx context.Context, tenantID, userID, postID int64, return nil } + +// AttachAssets to post +func (svc *Service) AttachAssets(ctx context.Context, tenantID, userID, postID int64, mediaAssets []fields.MediaAsset) error { + _, span := otel.Start(ctx, "users.service.AttachAssets") + defer span.End() + span.SetAttributes( + attribute.Int64("tenant.id", tenantID), + attribute.Int64("user.id", userID), + attribute.Int64("post.id", postID), + ) + + post, err := svc.ForceGetPostByID(ctx, postID) + if err != nil { + return err + } + + assets := append(post.Assets.Data, mediaAssets...) + post.Assets.Data = assets + + tbl := table.Posts + stmt := tbl. + UPDATE(tbl.UpdatedAt, tbl.Assets). + SET( + tbl.UpdatedAt.SET(TimestampT(time.Now())), + tbl.Assets.SET(Json(post.Assets)), + ). + WHERE( + tbl.ID.EQ(Int64(postID)).AND( + tbl.TenantID.EQ(Int64(tenantID)).AND( + tbl.UserID.EQ(Int64(userID)), + ), + ), + ) + span.SetAttributes(semconv.DBStatementKey.String(stmt.DebugSql())) + + if _, err := stmt.ExecContext(ctx, svc.db); err != nil { + return err + } + + return svc.Update(ctx, tenantID, userID, postID, post) +} + +// UpdateMeta +func (svc *Service) UpdateMeta(ctx context.Context, tenantID, userID, postID int64, meta fields.PostMeta) error { + _, span := otel.Start(ctx, "users.service.UpdateMeta") + defer span.End() + span.SetAttributes( + attribute.Int64("tenant.id", tenantID), + attribute.Int64("user.id", userID), + attribute.Int64("post.id", postID), + ) + + post, err := svc.ForceGetPostByID(ctx, postID) + if err != nil { + return err + } + + post.Meta = meta + + tbl := table.Posts + stmt := tbl. + UPDATE(tbl.UpdatedAt, tbl.Meta). + SET( + tbl.UpdatedAt.SET(TimestampT(time.Now())), + tbl.Meta.SET(Json(post.Meta)), + ). + WHERE( + tbl.ID.EQ(Int64(postID)).AND( + tbl.TenantID.EQ(Int64(tenantID)).AND( + tbl.UserID.EQ(Int64(userID)), + ), + ), + ) + span.SetAttributes(semconv.DBStatementKey.String(stmt.DebugSql())) + + if _, err := stmt.ExecContext(ctx, svc.db); err != nil { + return err + } + + return svc.Update(ctx, tenantID, userID, postID, post) +} diff --git a/backend/app/jobs/post_video_cut.go b/backend/app/jobs/post_video_cut.go index 0fc3185..4c48a18 100644 --- a/backend/app/jobs/post_video_cut.go +++ b/backend/app/jobs/post_video_cut.go @@ -1,12 +1,27 @@ package jobs import ( + "bytes" "context" + "os" + "os/exec" + "strings" "time" + "backend/app/http/medias" + "backend/app/http/posts" + "backend/app/http/storages" + "backend/database/fields" + "backend/database/models/qvyun_v2/public/model" + "backend/pkg/utils" + "backend/pkg/utils/fs" + _ "git.ipao.vip/rogeecn/atom" _ "git.ipao.vip/rogeecn/atom/contracts" + "github.com/pkg/errors" . "github.com/riverqueue/river" + "github.com/samber/lo" + "github.com/sirupsen/logrus" ) var ( @@ -26,9 +41,9 @@ func (s PostVideoCutJob) InsertOpts() InsertOpts { return InsertOpts{ Queue: QueueDefault, Priority: PriorityDefault, - // UniqueOpts: UniqueOpts{ - // ByArgs: true, - // }, + UniqueOpts: UniqueOpts{ + ByArgs: true, + }, } } @@ -43,6 +58,17 @@ var _ Worker[PostVideoCutJob] = (*PostVideoCutJobWorker)(nil) // @provider(job) type PostVideoCutJobWorker struct { WorkerDefaults[PostVideoCutJob] + + log *logrus.Entry `inject:"false"` + + postSvc *posts.Service + mediaSvc *medias.Service + storageSvc *storages.Service +} + +func (w *PostVideoCutJobWorker) Prepare() error { + w.log = logrus.WithField("worker", "PostVideoCutJobWorker") + return nil } func (w *PostVideoCutJobWorker) NextRetry(job *Job[PostVideoCutJob]) time.Time { @@ -50,5 +76,110 @@ func (w *PostVideoCutJobWorker) NextRetry(job *Job[PostVideoCutJob]) time.Time { } func (w *PostVideoCutJobWorker) Work(ctx context.Context, job *Job[PostVideoCutJob]) error { + post, err := w.postSvc.GetPostByID(ctx, job.Args.PostID) + if err != nil { + return errors.Wrapf(err, "get post(%d) failed", job.Args.PostID) + } + + media, err := w.mediaSvc.GetMediaByHash(ctx, job.Args.TenantID, job.Args.UserID, job.Args.Hash) + if err != nil { + return errors.Wrapf(err, "get media by hash(%s) failed", job.Args.Hash) + } + + videoPath := media.Path + + // 获取全长度的音频 + _, ok := lo.Find(post.Assets.Data, func(asset fields.MediaAsset) bool { + return asset.Type == fields.MediaAssetTypeAudio && asset.Mark != nil && *asset.Mark == "audio-preview" + }) + if ok { + return nil + } + + previewVideoPath := strings.Replace(videoPath, ".mp4", "-preview.mp4", -1) + + duration := lo.ToPtr("00:01:00") + if err := w.extractVideoFromVideo(ctx, videoPath, previewVideoPath, duration); err != nil { + return errors.Wrapf(err, "extract preview video from video failed") + } + + fileMd5, err := utils.FileMd5(previewVideoPath) + if err != nil { + return errors.Wrapf(err, "get preview video(%s) file md5 failed", previewVideoPath) + } + + if err := os.Rename(previewVideoPath, strings.Replace(videoPath, job.Args.Hash, fileMd5, 1)); err != nil { + return errors.Wrapf(err, "rename video(%s) file failed", videoPath) + } + + storage, err := w.storageSvc.GetDefault(ctx) + if err != nil { + return err + } + + // save to medias + _, err = w.mediaSvc.Create(ctx, &model.Medias{ + TenantID: job.Args.TenantID, + UserID: job.Args.UserID, + PostID: post.ID, + StorageID: storage.ID, + Hash: fileMd5, + Name: post.Title, + MimeType: "video/mp4", + Size: fs.FileSize(previewVideoPath), + Path: previewVideoPath, + }) + if err != nil { + return errors.Wrapf(err, "create media failed") + } + + assets := []fields.MediaAsset{ + { + Type: fields.MediaAssetTypeVideo, + Hash: fileMd5, + Mark: lo.ToPtr("video-preview"), + }, + } + + if err := w.postSvc.AttachAssets(ctx, job.Args.TenantID, job.Args.UserID, post.ID, assets); err != nil { + return errors.Wrapf(err, "attach video(%s) to post(%d) failed", videoPath, post.ID) + } + + post.Meta.WorkerMark = post.Meta.WorkerMark & 1 << 0 + if err := w.postSvc.UpdateMeta(ctx, job.Args.TenantID, job.Args.UserID, post.ID, post.Meta); err != nil { + return errors.Wrapf(err, "update post(%d) meta failed", post.ID) + } + + return nil +} + +// extractVideoFromVideo +func (w *PostVideoCutJobWorker) extractVideoFromVideo(ctx context.Context, videoPath, previewVideoPath string, duration *string) error { + args := []string{ + "-i", videoPath, + "-c:v", "copy", + "-c:a", "copy", + } + + if duration != nil { + args = append(args, "-t", *duration) + } + args = append(args, previewVideoPath) + + w.log.Infof("extractVideoFromVideo: ffmpeg %s", strings.Join(args, " ")) + + cmd := exec.CommandContext(ctx, "ffmpeg", args...) + + var buf bytes.Buffer + logWriter := utils.NewLogBuffer(func(line string) { + w.log.Info(line) + }) + cmd.Stdout = utils.NewCombinedBuffer(&buf, logWriter) + cmd.Stderr = utils.NewCombinedBuffer(&buf, logWriter) + + if err := cmd.Run(); err != nil { + return errors.Wrapf(err, "extract video failed: %s\n%s", videoPath, buf.String()) + } + return nil } diff --git a/backend/app/jobs/post_video_extract_audio.go b/backend/app/jobs/post_video_extract_audio.go index 7e054c2..154371d 100644 --- a/backend/app/jobs/post_video_extract_audio.go +++ b/backend/app/jobs/post_video_extract_audio.go @@ -1,12 +1,27 @@ package jobs import ( + "bytes" "context" + "os" + "os/exec" + "strings" "time" + "backend/app/http/medias" + "backend/app/http/posts" + "backend/app/http/storages" + "backend/database/fields" + "backend/database/models/qvyun_v2/public/model" + "backend/pkg/utils" + "backend/pkg/utils/fs" + _ "git.ipao.vip/rogeecn/atom" _ "git.ipao.vip/rogeecn/atom/contracts" + "github.com/pkg/errors" . "github.com/riverqueue/river" + "github.com/samber/lo" + "github.com/sirupsen/logrus" ) var ( @@ -19,6 +34,7 @@ type PostVideoExtractAudioJob struct { TenantID int64 UserID int64 Hash string + Mark string } // InsertOpts implements JobArgsWithInsertOpts. @@ -26,9 +42,9 @@ func (s PostVideoExtractAudioJob) InsertOpts() InsertOpts { return InsertOpts{ Queue: QueueDefault, Priority: PriorityDefault, - // UniqueOpts: UniqueOpts{ - // ByArgs: true, - // }, + UniqueOpts: UniqueOpts{ + ByArgs: true, + }, } } @@ -41,6 +57,17 @@ var _ Worker[PostVideoExtractAudioJob] = (*PostVideoExtractAudioJobWorker)(nil) // @provider(job) type PostVideoExtractAudioJobWorker struct { WorkerDefaults[PostVideoExtractAudioJob] + + log *logrus.Entry `inject:"false"` + + postSvc *posts.Service + mediaSvc *medias.Service + storageSvc *storages.Service +} + +func (w *PostVideoExtractAudioJobWorker) Prepare() error { + w.log = logrus.WithField("worker", "PostVideoExtractAudioJobWorker") + return nil } func (w *PostVideoExtractAudioJobWorker) NextRetry(job *Job[PostVideoExtractAudioJob]) time.Time { @@ -48,5 +75,122 @@ func (w *PostVideoExtractAudioJobWorker) NextRetry(job *Job[PostVideoExtractAudi } func (w *PostVideoExtractAudioJobWorker) Work(ctx context.Context, job *Job[PostVideoExtractAudioJob]) error { + post, err := w.postSvc.GetPostByID(ctx, job.Args.PostID) + if err != nil { + return errors.Wrapf(err, "get post(%d) failed", job.Args.PostID) + } + + media, err := w.mediaSvc.GetMediaByHash(ctx, job.Args.TenantID, job.Args.UserID, job.Args.Hash) + if err != nil { + return errors.Wrapf(err, "get media by hash(%s) failed", job.Args.Hash) + } + + videoPath := media.Path + + // 获取全长度的音频 + _, ok := lo.Find(post.Assets.Data, func(asset fields.MediaAsset) bool { + return asset.Type == fields.MediaAssetTypeAudio && asset.Mark != nil && *asset.Mark == job.Args.Mark + }) + if ok { + return nil + } + + audioPath := strings.Replace(videoPath, ".mp4", ".mp3", -1) + + var duration *string + if job.Args.Mark == "audio-preview" { + duration = lo.ToPtr("00:01:00") + } + + if err := w.extractAudioFromVideo(ctx, videoPath, audioPath, duration); err != nil { + return errors.Wrapf(err, "extract audio from video failed") + } + + fileMd5, err := utils.FileMd5(audioPath) + if err != nil { + return errors.Wrapf(err, "get audio(%s) file md5 failed", audioPath) + } + + if err := os.Rename(audioPath, strings.Replace(audioPath, job.Args.Hash, fileMd5, 1)); err != nil { + return errors.Wrapf(err, "rename audio(%s) file failed", audioPath) + } + + storage, err := w.storageSvc.GetDefault(ctx) + if err != nil { + return err + } + + // save to medias + _, err = w.mediaSvc.Create(ctx, &model.Medias{ + TenantID: job.Args.TenantID, + UserID: job.Args.UserID, + PostID: post.ID, + StorageID: storage.ID, + Hash: fileMd5, + Name: post.Title, + MimeType: "audio/mp3", + Size: fs.FileSize(audioPath), + Path: audioPath, + }) + if err != nil { + return errors.Wrapf(err, "create media failed") + } + + assets := []fields.MediaAsset{ + { + Type: fields.MediaAssetTypeAudio, + Hash: fileMd5, + Mark: lo.ToPtr(job.Args.Mark), + }, + } + + if err := w.postSvc.AttachAssets(ctx, job.Args.TenantID, job.Args.UserID, post.ID, assets); err != nil { + return errors.Wrapf(err, "attach audio(%s) to post(%d) failed", audioPath, post.ID) + } + + if job.Args.Mark == "audio-preview" { + post.Meta.WorkerMark = post.Meta.WorkerMark & 1 << 1 + } else if job.Args.Mark == "audio" { + post.Meta.WorkerMark = post.Meta.WorkerMark & 1 << 2 + } + + if err := w.postSvc.UpdateMeta(ctx, job.Args.TenantID, job.Args.UserID, post.ID, post.Meta); err != nil { + return errors.Wrapf(err, "update post(%d) meta failed", post.ID) + } + + return nil +} + +// extractAudioFromVideo extracts audio from video. +func (w *PostVideoExtractAudioJobWorker) extractAudioFromVideo(ctx context.Context, videoPath, audioPath string, duration *string) error { + args := []string{ + "-i", videoPath, + "-vn", + "-acodec", "libmp3lame", + "-ar", "44100", + "-b:a", "128k", + "-q:a", "2", + } + + if duration != nil { + args = append(args, "-t", *duration) + } + args = append(args, audioPath) + + w.log.Infof("extractAudioFromVideo: ffmpeg %s", strings.Join(args, " ")) + + cmd := exec.CommandContext(ctx, "ffmpeg", args...) + + var buf bytes.Buffer + logWriter := utils.NewLogBuffer(func(line string) { + w.log.Info(line) + }) + cmd.Stdout = utils.NewCombinedBuffer(&buf, logWriter) + cmd.Stderr = utils.NewCombinedBuffer(&buf, logWriter) + + if err := cmd.Run(); err != nil { + return errors.Wrapf(err, "extract audio failed: %s\n%s", videoPath, buf.String()) + } + return nil } diff --git a/backend/database/models/qvyun_v2/public/model/posts.go b/backend/database/models/qvyun_v2/public/model/posts.go index d2a189c..6031823 100644 --- a/backend/database/models/qvyun_v2/public/model/posts.go +++ b/backend/database/models/qvyun_v2/public/model/posts.go @@ -29,7 +29,7 @@ type Posts struct { Discount int16 `json:"discount"` Views int64 `json:"views"` Likes int64 `json:"likes"` - Meta *string `json:"meta"` + Meta fields.PostMeta `json:"meta"` Tags fields.Json[[]string] `json:"tags"` Assets fields.Json[[]fields.MediaAsset] `json:"assets"` } diff --git a/backend/database/transform.yaml b/backend/database/transform.yaml index 7d8ea95..cca2869 100644 --- a/backend/database/transform.yaml +++ b/backend/database/transform.yaml @@ -23,7 +23,7 @@ types: type: PostType assets: Json[[]MediaAsset] tags: Json[[]string] - metas: PostMeta + meta: PostMeta orders: type: OrderType diff --git a/backend/go.mod b/backend/go.mod index 9a47eed..3a249f6 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -37,6 +37,8 @@ require ( github.com/swaggo/files/v2 v2.0.2 github.com/swaggo/swag v1.16.4 github.com/uber/jaeger-client-go v2.30.0+incompatible + go.beyondstorage.io/services/fs/v4 v4.0.0 + go.beyondstorage.io/v5 v5.0.0 go.opentelemetry.io/contrib/instrumentation/runtime v0.58.0 go.opentelemetry.io/otel v1.33.0 go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.33.0 @@ -53,7 +55,7 @@ require ( golang.org/x/net v0.34.0 golang.org/x/sync v0.10.0 google.golang.org/grpc v1.69.2 - google.golang.org/protobuf v1.35.2 + google.golang.org/protobuf v1.36.1 gopkg.in/retry.v1 v1.0.3 ) @@ -64,10 +66,15 @@ require ( github.com/PuerkitoBio/purell v1.1.1 // indirect github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect github.com/Rican7/retry v0.3.1 // indirect + github.com/Xuanwo/gg v0.2.0 // indirect + github.com/Xuanwo/go-bufferpool v0.2.0 // indirect + github.com/Xuanwo/templateutils v0.1.0 // indirect github.com/andybalholm/brotli v1.1.1 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cloudflare/circl v1.5.0 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect + github.com/dave/dst v0.26.2 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/dnwe/otelsarama v0.0.0-20240308230250-9388d9d40bc0 // indirect @@ -110,6 +117,7 @@ require ( github.com/jcmturner/rpc/v2 v2.0.3 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/jtolds/gls v4.20.0+incompatible // indirect + github.com/kevinburke/go-bindata v3.22.0+incompatible // indirect github.com/klauspost/compress v1.17.11 // indirect github.com/lithammer/shortuuid/v3 v3.0.7 // indirect github.com/magiconair/properties v1.8.7 // indirect @@ -119,21 +127,24 @@ require ( github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/oklog/ulid v1.3.1 // indirect github.com/onsi/ginkgo/v2 v2.22.0 // indirect + github.com/pelletier/go-toml v1.9.4 // indirect github.com/pelletier/go-toml/v2 v2.2.2 // indirect github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c // indirect github.com/pierrec/lz4/v4 v4.1.21 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/qingstor/go-mime v0.1.0 // indirect github.com/quic-go/qpack v0.5.1 // indirect github.com/quic-go/quic-go v0.48.2 // indirect github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect github.com/refraction-networking/utls v1.6.7 // indirect github.com/riverqueue/river/riverdriver v0.15.0 // indirect github.com/riverqueue/river/rivershared v0.15.0 // indirect + github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/smarty/assertions v1.15.0 // indirect github.com/sourcegraph/conc v0.3.0 // indirect - github.com/spf13/afero v1.11.0 // indirect + github.com/spf13/afero v1.12.0 // indirect github.com/spf13/cast v1.7.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/spf13/viper v1.19.0 // indirect @@ -144,6 +155,7 @@ require ( github.com/tidwall/sjson v1.2.5 // indirect github.com/tinylib/msgp v1.2.5 // indirect github.com/uber/jaeger-lib v2.4.1+incompatible // indirect + github.com/urfave/cli/v2 v2.3.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasthttp v1.58.0 // indirect github.com/valyala/tcplisten v1.0.0 // indirect @@ -162,7 +174,7 @@ require ( golang.org/x/tools v0.28.0 // indirect google.golang.org/appengine v1.6.8 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/backend/go.sum b/backend/go.sum index 6c50ddd..6394dae 100644 --- a/backend/go.sum +++ b/backend/go.sum @@ -5,6 +5,7 @@ git.ipao.vip/rogeecn/atom v1.0.15 h1:2+Mj9WblGqpSMuiIA1ZiOOt+wxarHrpxNQGMTERLHtY git.ipao.vip/rogeecn/atom v1.0.15/go.mod h1:lCz4RuZNDjiZe1Z4asBfbkfDrcr2dkjhD1IoQQ66ZAA= git.ipao.vip/rogeecn/atomctl v0.0.0-20250109030503-bd6d6bc6e82c h1:FJ1J4mI/rwEI8c010FJwlh9brg8cBfWlv2S2Ts9wjNk= git.ipao.vip/rogeecn/atomctl v0.0.0-20250109030503-bd6d6bc6e82c/go.mod h1:tBI/WbTcMb9SArd7JZeArSfSoZSo02Kj9ci6d1FdgdE= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/HdrHistogram/hdrhistogram-go v1.1.2 h1:5IcZpTvzydCQeHzK4Ef/D5rrSqwxob0t8PQPMybUNFM= github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo= @@ -26,6 +27,12 @@ github.com/ThreeDotsLabs/watermill-redisstream v1.4.2 h1:FY6tsBcbhbJpKDOssU4bfyb github.com/ThreeDotsLabs/watermill-redisstream v1.4.2/go.mod h1:69++855LyB+ckYDe60PiJLBcUrpckfDE2WwyzuVJRCk= github.com/ThreeDotsLabs/watermill-sql/v3 v3.1.0 h1:g4uE5Nm3Z6LVB3m+uMgHlN4ne4bDpwf3RJmXYRgMv94= github.com/ThreeDotsLabs/watermill-sql/v3 v3.1.0/go.mod h1:G8/otZYWLTCeYL2Ww3ujQ7gQ/3+jw5Bj0UtyKn7bBjA= +github.com/Xuanwo/gg v0.2.0 h1:axbZmA0qmidh3s9PA86GqvBXVQ3o7Bbpf0aImGtlimA= +github.com/Xuanwo/gg v0.2.0/go.mod h1:0fLiiSxR87u2UA0ZNZiKZXuz3jnJdbDHWtU2xpdcH3s= +github.com/Xuanwo/go-bufferpool v0.2.0 h1:DXzqJD9lJufXbT/03GrcEvYOs4gXYUj9/g5yi6Q9rUw= +github.com/Xuanwo/go-bufferpool v0.2.0/go.mod h1:Mle++9GGouhOwGj52i9PJLNAPmW2nb8PWBP7JJzNCzk= +github.com/Xuanwo/templateutils v0.1.0 h1:WpkWOqQtIQ2vAIpJLa727DdN8WtxhUkkbDGa6UhntJY= +github.com/Xuanwo/templateutils v0.1.0/go.mod h1:OdE0DJ+CJxDBq6psX5DPV+gOZi8bhuHuVUpPCG++Wb8= github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA= github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA= @@ -39,8 +46,16 @@ github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UF github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cloudflare/circl v1.5.0 h1:hxIWksrX6XN5a1L2TI/h53AGPhNHoUBo+TD1ms9+pys= github.com/cloudflare/circl v1.5.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/dave/dst v0.26.2 h1:lnxLAKI3tx7MgLNVDirFCsDTlTG9nKTk7GcptKcWSwY= +github.com/dave/dst v0.26.2/go.mod h1:UMDJuIRPfyUCC78eFuB+SV/WI8oDeyFDvM/JR6NI3IU= +github.com/dave/gopackages v0.0.0-20170318123100-46e7023ec56e/go.mod h1:i00+b/gKdIDIxuLDFob7ustLAVqhsZRk2qVZrArELGQ= +github.com/dave/jennifer v1.2.0/go.mod h1:fIb+770HOpJ2fmN9EPPKOqm1vMGhB+TwXKMZhrIygKg= +github.com/dave/kerr v0.0.0-20170318121727-bc25dd6abe8e/go.mod h1:qZqlPyPvfsDJt+3wHJ1EvSXDuVjFTK0j2p/ca+gtsb8= +github.com/dave/rebecca v0.9.1/go.mod h1:N6XYdMD/OKw3lkF3ywh8Z6wPGuwNFDNtWYEMFWEmXBA= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= @@ -110,6 +125,8 @@ github.com/gofiber/utils/v2 v2.0.0-beta.7/go.mod h1:J/M03s+HMdZdvhAeyh76xT72IfVq github.com/golang-jwt/jwt/v4 v4.5.1 h1:JdqV9zKUdtaa9gdPlywC3aeoEsR681PlKC+4F5gQgeo= github.com/golang-jwt/jwt/v4 v4.5.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= +github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= @@ -121,11 +138,14 @@ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 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-20181127221834-b4f47329b966/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad h1:a6HEuzUHeKH6hwfN/ZoQgRgVIWFJljSWa/zetS2WTvg= github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 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/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g= github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k= github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= @@ -142,6 +162,7 @@ github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/C github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imroc/req/v3 v3.49.1 h1:Nvwo02riiPEzh74ozFHeEJrtjakFxnoWNR3YZYuQm9U= github.com/imroc/req/v3 v3.49.1/go.mod h1:tsOk8K7zI6cU4xu/VWCZVtq9Djw9IWm4MslKzme5woU= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= @@ -189,6 +210,8 @@ github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfV github.com/juju/go4 v0.0.0-20160222163258-40d72ab9641a h1:45JtCyuNYE+QN9aPuR1ID9++BQU+NMTMudHSuaK0Las= github.com/juju/go4 v0.0.0-20160222163258-40d72ab9641a/go.mod h1:RVHtZuvrpETIepiNUrNlih2OynoFf1eM6DGC6dloXzk= github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= +github.com/kevinburke/go-bindata v3.22.0+incompatible h1:/JmqEhIWQ7GRScV0WjX/0tqBrC5D21ALg0H0U/KZ/ts= +github.com/kevinburke/go-bindata v3.22.0+incompatible/go.mod h1:/pEEZ72flUW2p0yi30bslSp9YqD9pysLxunQDdb2CPM= github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= @@ -224,6 +247,8 @@ github.com/onsi/gomega v1.34.2 h1:pNCwDkzrsv7MS9kpaQvVb1aVLahQXyJ/Tv5oAZMI3i8= github.com/onsi/gomega v1.34.2/go.mod h1:v1xfxRgk0KIsG+QOdm7p8UosrOzPYRo60fd3B/1Dukc= github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= +github.com/pelletier/go-toml v1.9.4 h1:tjENF6MfZAg8e4ZmZTeWaWiT2vXtsoO6+iuOjFhECwM= +github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= 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/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c h1:dAMKvw0MlJT1GshSTtih8C2gDs04w8dReiOGXrGLNoY= @@ -235,6 +260,8 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/qingstor/go-mime v0.1.0 h1:FhTJtM7TRm9pfgCXpjGUxqwbumGojrgE9ecRz5PXvfc= +github.com/qingstor/go-mime v0.1.0/go.mod h1:EDwWgaMufg74m7futsF0ZGkdA52ajjAycY+XDeV8M88= github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= github.com/quic-go/quic-go v0.48.2 h1:wsKXZPeGWpMpCGSWqOcqpW2wZYic/8T3aqiOID0/KWE= @@ -265,6 +292,8 @@ github.com/rogpeppe/clock v0.0.0-20190514195947-2896927a307a h1:3QH7VyOaaiUHNrA9 github.com/rogpeppe/clock v0.0.0-20190514195947-2896927a307a/go.mod h1:4r5QyqhjIWCcK8DO4KMclc5Iknq5qVBAlbYYzAbUScQ= github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= @@ -272,10 +301,16 @@ github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6g github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= github.com/samber/lo v1.47.0 h1:z7RynLwP5nbyRscyvcD043DWYoOcYRv3mV8lBeqOCLc= github.com/samber/lo v1.47.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU= +github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= +github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/smarty/assertions v1.15.0 h1:cR//PqUBUiQRakZWqBiFFQ9wb8emQGDb0HeGdqGByCY= github.com/smarty/assertions v1.15.0/go.mod h1:yABtdzeQs6l1brC900WlRNwj6ZR55d7B+E8C6HtKdec= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.6/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/smartystreets/goconvey v1.8.1 h1:qGjIddxOk4grTu9JPOU31tVfq3cNdBlNa5sSznIX1xY= github.com/smartystreets/goconvey v1.8.1/go.mod h1:+/u4qLyY6x1jReYOp7GOM2FSt8aP9CzCZL03bI28W60= github.com/soheilhy/cmux v0.1.5 h1:jjzc5WVemNEDTLwv9tlmemhC73tI08BNOIGwBOo10Js= @@ -286,6 +321,8 @@ github.com/speps/go-hashids/v2 v2.0.1 h1:ViWOEqWES/pdOSq+C1SLVa8/Tnsd52XC34RY7lt github.com/speps/go-hashids/v2 v2.0.1/go.mod h1:47LKunwvDZki/uRVD6NImtyk712yFzIs3UF3KlHohGw= github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= +github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs= +github.com/spf13/afero v1.12.0/go.mod h1:ZTlWwG4/ahT8W7T0WQ5uYmjI9duaLQGy3Q2OAl4sk/4= github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w= github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= @@ -299,6 +336,7 @@ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSS github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= @@ -332,6 +370,8 @@ github.com/uber/jaeger-client-go v2.30.0+incompatible h1:D6wyKGCecFaSRUpo8lCVbaO github.com/uber/jaeger-client-go v2.30.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= github.com/uber/jaeger-lib v2.4.1+incompatible h1:td4jdvLcExb4cBISKIpHuGoVXh+dVKhn2Um6rjCsSsg= github.com/uber/jaeger-lib v2.4.1+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= +github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M= +github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasthttp v1.58.0 h1:GGB2dWxSbEprU9j0iMJHgdKYJVDyjrOwF9RE59PbRuE= @@ -344,7 +384,13 @@ github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +go.beyondstorage.io/services/fs/v4 v4.0.0 h1:ukDNhoUI1E5x6DDDRUFaA6JbOdrE0taDHKiOemLqnL8= +go.beyondstorage.io/services/fs/v4 v4.0.0/go.mod h1:gxqLiBwoQQBt1xHTUw2js59tXiYiW67M/RzbP3FwRUc= +go.beyondstorage.io/v5 v5.0.0 h1:k9Axfgbt+oZXoDwSBVCl1XANHSL4rkNTGP2Lz9YdJe0= +go.beyondstorage.io/v5 v5.0.0/go.mod h1:3wV9gCQnqu7tD/3LMeo2yimUKIeTSHpTc6wHSb0yY20= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= go.opentelemetry.io/contrib/instrumentation/runtime v0.58.0 h1:GrcF8ABgnBHQFgp4zu5/jTSqLkoJ9uiDz2e7eKkjq+w= @@ -381,8 +427,10 @@ go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU= go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +golang.org/x/arch v0.0.0-20180920145803-b19384d3c130/go.mod h1:cYlCBUl1MsqxdiKgmc4uh7TxZfWSFLOGSRR090WDxt8= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= @@ -400,14 +448,19 @@ golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMx golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= @@ -415,15 +468,21 @@ golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20180903190138-2b024373dcd9/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -449,12 +508,16 @@ golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200509030707-2212a7e161a5/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.28.0 h1:WuB6qZ4RPCQo5aP3WdKZS7i595EdWqWR8vqJTlwTVK8= golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= @@ -467,12 +530,14 @@ google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 h1: google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576/go.mod h1:1R3kvZ1dtP3+4p4d3G8uJ8rFk/fWlScl38vanWACI08= google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576 h1:8ZmaLZE4XWrtU3MyClkYqqtl6Oegr3235h7jxsDyqCY= google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8/go.mod h1:lcTa1sDdWEIHMWlITnIczmw5w60CF9ffkb8Z+DVmmjA= google.golang.org/grpc v1.69.2 h1:U3S9QEtbXC0bYNvRtcoklF3xGtLViumSYxWykJS+7AU= google.golang.org/grpc v1.69.2/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io= google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -482,7 +547,9 @@ gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/retry.v1 v1.0.3 h1:a9CArYczAVv6Qs6VGoLMio99GEs7kY9UzSF9+LD+iGs= gopkg.in/retry.v1 v1.0.3/go.mod h1:FJkXmWiMaAo7xB+xhvDF59zhfjDWyzmyAxiT4dB688g= +gopkg.in/src-d/go-billy.v4 v4.3.0/go.mod h1:tm33zBoOwxjYHZIE+OV8bxTWFMJLrconzFMd38aARFk= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/backend/main_test.go b/backend/main_test.go index 50c3385..261db4b 100644 --- a/backend/main_test.go +++ b/backend/main_test.go @@ -1,11 +1,18 @@ package main import ( + "errors" "os" "path/filepath" "testing" + "backend/pkg/utils" + . "github.com/smartystreets/goconvey/convey" + "github.com/spf13/afero" + _ "go.beyondstorage.io/services/fs/v4" + "go.beyondstorage.io/v5/services" + "go.beyondstorage.io/v5/types" ) func Test_mkdir(t *testing.T) { @@ -24,3 +31,52 @@ func Test_mkdir(t *testing.T) { }) }) } + +func Test_storage(t *testing.T) { + Convey("Test storage", t, func() { + Convey("local fs", func() { + store, err := services.NewStoragerFromString("fs:///mnt/yangpingliang/publish/") + So(err, ShouldBeNil) + + lst, err := store.List("") + So(err, ShouldBeNil) + for { + o, err := lst.Next() + if err != nil && !errors.Is(err, types.IterateDone) { + break + } + + So(err, ShouldBeNil) + + t.Logf("Object: %s", o.MustGetContentType()) + } + }) + }) +} + +func Test_afero(t *testing.T) { + Convey("Test afero", t, func() { + Convey("local fs", func() { + pathFs := afero.NewBasePathFs(afero.NewOsFs(), "/mnt/yangpingliang/publish/") + + lst, err := afero.ReadDir(pathFs, "") + So(err, ShouldBeNil) + + for _, fi := range lst { + k := "F" + if fi.IsDir() { + k = "D" + } + + if fi.IsDir() { + continue + } + + md5, err := utils.FileMd5(filepath.Join("/mnt/yangpingliang/publish/", fi.Name())) + So(err, ShouldBeNil) + + t.Logf("[%s]%s, md5: %s", k, fi.Name(), md5) + } + }) + }) +} diff --git a/backend/pkg/utils/fs/fs.go b/backend/pkg/utils/fs/fs.go new file mode 100644 index 0000000..3b5d40d --- /dev/null +++ b/backend/pkg/utils/fs/fs.go @@ -0,0 +1,37 @@ +package fs + +import ( + "io" + "os" +) + +// CopyFile +func CopyFile(src, dst string) error { + in, err := os.Open(src) + if err != nil { + return err + } + defer in.Close() + + out, err := os.Create(dst) + if err != nil { + return err + } + defer out.Close() + + _, err = io.Copy(out, in) + if err != nil { + return err + } + + return out.Close() +} + +// FileSize +func FileSize(file string) int64 { + fi, err := os.Stat(file) + if err != nil { + return 0 + } + return fi.Size() +} diff --git a/backend/pkg/utils/md5.go b/backend/pkg/utils/md5.go new file mode 100644 index 0000000..d44db55 --- /dev/null +++ b/backend/pkg/utils/md5.go @@ -0,0 +1,28 @@ +package utils + +import ( + "crypto/md5" + "encoding/hex" + "io" + "os" +) + +// FileMd5 calculates the md5 of a file +func FileMd5(file string) (string, error) { + f, err := os.Open(file) + if err != nil { + return "", err + } + + h := md5.New() + if _, err := io.Copy(h, f); err != nil { + return "", err + } + return hex.EncodeToString(h.Sum(nil)), nil +} + +func StringMd5(s string) string { + h := md5.New() + h.Write([]byte(s)) + return hex.EncodeToString(h.Sum(nil)) +}