From 329384d8ef310bea879d065e912a65e93690183a Mon Sep 17 00:00:00 2001 From: ayflying Date: Thu, 23 Oct 2025 15:39:38 +0800 Subject: [PATCH] =?UTF-8?q?=E8=87=AA=E5=8A=A8=E4=B8=8A=E4=BC=A0=E6=9C=8D?= =?UTF-8?q?=E5=8A=A1=E7=AB=AF=E7=89=88=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- go.mod | 9 ++ go.sum | 19 +++ internal/cmd/update.go | 45 ++++++- internal/logic/logic.go | 1 + internal/logic/s3/s3.go | 285 ++++++++++++++++++++++++++++++++++++++++ internal/service/s_3.go | 68 ++++++++++ 6 files changed, 424 insertions(+), 3 deletions(-) create mode 100644 internal/logic/s3/s3.go create mode 100644 internal/service/s_3.go diff --git a/go.mod b/go.mod index 18b595f..1b377c5 100644 --- a/go.mod +++ b/go.mod @@ -26,6 +26,7 @@ require ( github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/dlclark/regexp2 v1.11.4 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect github.com/emirpasic/gods v1.18.1 // indirect github.com/fatih/color v1.18.0 // indirect github.com/filecoin-project/go-clock v0.1.0 // indirect @@ -38,10 +39,12 @@ require ( github.com/getlantern/hex v0.0.0-20190417191902-c6586a6fe0b7 // indirect github.com/getlantern/hidden v0.0.0-20190325191715-f02dbb02be55 // indirect github.com/getlantern/ops v0.0.0-20190325191751-d70cb0d6f85f // indirect + github.com/go-ini/ini v1.67.0 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect github.com/go-stack/stack v1.8.0 // indirect + github.com/goccy/go-json v0.10.5 // indirect github.com/google/gopacket v1.1.19 // indirect github.com/google/pprof v0.0.0-20230405160723-4a4c7d95572b // indirect github.com/grokify/html-strip-tags-go v0.1.0 // indirect @@ -76,6 +79,9 @@ require ( github.com/miekg/dns v1.1.68 // indirect github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b // indirect github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc // indirect + github.com/minio/crc64nvme v1.0.2 // indirect + github.com/minio/md5-simd v1.1.2 // indirect + github.com/minio/minio-go/v7 v7.0.95 // indirect github.com/minio/sha256-simd v1.0.1 // indirect github.com/mr-tron/base58 v1.2.0 // indirect github.com/multiformats/go-base32 v0.1.0 // indirect @@ -94,6 +100,7 @@ require ( github.com/olekukonko/tablewriter v1.0.9 // indirect github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c // indirect github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect + github.com/philhofer/fwd v1.2.0 // indirect github.com/pion/datachannel v1.5.10 // indirect github.com/pion/dtls/v2 v2.2.12 // indirect github.com/pion/dtls/v3 v3.0.6 // indirect @@ -123,7 +130,9 @@ require ( github.com/quic-go/webtransport-go v0.9.0 // indirect github.com/redis/go-redis/v9 v9.12.1 // indirect github.com/rivo/uniseg v0.4.7 // indirect + github.com/rs/xid v1.6.0 // indirect github.com/spaolacci/murmur3 v1.1.0 // indirect + github.com/tinylib/msgp v1.3.0 // indirect github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1 // indirect github.com/wlynxg/anet v0.0.5 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect diff --git a/go.sum b/go.sum index bc5e977..d349871 100644 --- a/go.sum +++ b/go.sum @@ -49,6 +49,8 @@ github.com/dlclark/regexp2 v1.11.4/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cn github.com/dop251/goja v0.0.0-20250630131328-58d95d85e994 h1:aQYWswi+hRL2zJqGacdCZx32XjKYV8ApXFGntw79XAM= github.com/dop251/goja v0.0.0-20250630131328-58d95d85e994/go.mod h1:MxLav0peU43GgvwVgNbLAj1s/bSGboKkhuULvq/7hx4= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +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/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= @@ -82,6 +84,8 @@ github.com/getlantern/systray v1.2.2/go.mod h1:pXFOI1wwqwYXEhLPm9ZGjS2u/vVELeIgN github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= +github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A= +github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= @@ -92,6 +96,8 @@ github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5Nq github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0= +github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= +github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/gogf/gf/contrib/nosql/redis/v2 v2.9.3 h1:VTbeHq8XpBCWFIwBGmuBl+jP8AepULnpgNz8GPBKBRQ= github.com/gogf/gf/contrib/nosql/redis/v2 v2.9.3/go.mod h1:gcidgAYn4IWbx08QUThg7jw6bz3KklXI9/5zg8jnVHY= github.com/gogf/gf/v2 v2.9.3 h1:qjN4s55FfUzxZ1AE8vUHNDX3V0eIOUGXhF2DjRTVZQ4= @@ -160,6 +166,7 @@ github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfV github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= +github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= github.com/koron/go-ssdp v0.0.6 h1:Jb0h04599eq/CY7rB5YEqPS83HmRfHP2azkxMN2rFtU= @@ -226,6 +233,12 @@ github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b/go.mod h1:lxPUiZwKo github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc h1:PTfri+PuQmWDqERdnNMiD9ZejrlswWrCpBEZgWOiTrc= github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc/go.mod h1:cGKTAVKx4SxOuR/czcZ/E2RSJ3sfHs8FpHhQ5CWMf9s= github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1/go.mod h1:pD8RvIylQ358TN4wwqatJ8rNavkEINozVn9DtGI3dfQ= +github.com/minio/crc64nvme v1.0.2 h1:6uO1UxGAD+kwqWWp7mBFsi5gAse66C4NXO8cmcVculg= +github.com/minio/crc64nvme v1.0.2/go.mod h1:eVfm2fAzLlxMdUGc0EEBGSMmPwmXD5XiNRpnu9J3bvg= +github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34= +github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM= +github.com/minio/minio-go/v7 v7.0.95 h1:ywOUPg+PebTMTzn9VDsoFJy32ZuARN9zhB+K3IYEvYU= +github.com/minio/minio-go/v7 v7.0.95/go.mod h1:wOOX3uxS334vImCNRVyIDdXX9OsXDm89ToynKgqUKlo= github.com/minio/sha256-simd v0.1.1-0.20190913151208-6de447530771/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM= github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM= github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8= @@ -275,6 +288,8 @@ github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c h1:rp5dCmg/yLR3mgF github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c/go.mod h1:X07ZCGwUbLaax7L0S3Tw4hpejzu63ZrrQiUe6W0hcy0= github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2DcNVpwGmV9E1BkGknEliJkfwQj0= github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58/go.mod h1:DXv8WO4yhMYhSNPKjeNKa5WY9YCIEBRbNzFFPJbWO6Y= +github.com/philhofer/fwd v1.2.0 h1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM= +github.com/philhofer/fwd v1.2.0/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM= github.com/pion/datachannel v1.5.10 h1:ly0Q26K1i6ZkGf42W7D4hQYR90pZwzFOjTq5AuCKk4o= github.com/pion/datachannel v1.5.10/go.mod h1:p/jJfC9arb29W7WrxyKbepTU20CFgyx5oLo8Rs4Py/M= github.com/pion/dtls/v2 v2.2.7/go.mod h1:8WiMkebSHFD0T+dIU+UeBaoV7kDhOW5oDCzZ7WZ/F9s= @@ -347,6 +362,8 @@ github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= +github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= @@ -395,6 +412,8 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= +github.com/tinylib/msgp v1.3.0 h1:ULuf7GPooDaIlbyvgAxBV/FI7ynli6LZ1/nVUNu+0ww= +github.com/tinylib/msgp v1.3.0/go.mod h1:ykjzy2wzgrlvpDCRc4LA8UXy6D8bzMSuAF3WD57Gok0= github.com/urfave/cli v1.22.10/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU= github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM= diff --git a/internal/cmd/update.go b/internal/cmd/update.go index 5f8f11f..2001f38 100644 --- a/internal/cmd/update.go +++ b/internal/cmd/update.go @@ -1,14 +1,20 @@ package cmd import ( + "bytes" "context" "fmt" "path" "runtime" + "github.com/ayflying/p2p/internal/service" + "github.com/gogf/gf/v2/crypto/gsha1" + "github.com/gogf/gf/v2/encoding/gcompress" + "github.com/gogf/gf/v2/encoding/gjson" "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/os/gcfg" "github.com/gogf/gf/v2/os/gcmd" + "github.com/gogf/gf/v2/os/gfile" ) var ( @@ -21,8 +27,8 @@ var ( //加载编辑配置文件 g.Cfg("hack").GetAdapter().(*gcfg.AdapterFile).SetFileName("hack/config.yaml") //获取文件名 - getFileName, err := g.Cfg("hack").Get(ctx, "gfcli.build.name") - filename := getFileName.String() + getName, err := g.Cfg("hack").Get(ctx, "gfcli.build.name") + name := getName.String() getPath, err := g.Cfg("hack").Get(ctx, "gfcli.build.path") pathMain := getPath.String() @@ -34,7 +40,40 @@ var ( // 拼接操作系统和架构(格式:OS_ARCH) platform := fmt.Sprintf("%s_%s", runtime.GOOS, runtime.GOARCH) - var filePath = path.Join(pathMain, version, platform, filename) + var versionFile = make(map[string]string) + + var filePath = path.Join(pathMain, version, platform, name) + dirList, _ := gfile.ScanDir(path.Join(pathMain, version), "*", false) + for _, v := range dirList { + updateFilename := gfile.Name(v) + updateFilePath := path.Join("server_update", name, version, updateFilename) + + var obj bytes.Buffer + g.Log().Debugf(ctx, "读取目录成功:%v", v) + fileMian := path.Join(v, name) + g.Log().Debugf(ctx, "判断当前文件是否存在:%v", fileMian) + if gfile.IsFile(fileMian) { + // 写入文件哈希 + versionFile[updateFilePath+".gz"] = gsha1.MustEncryptFile(fileMian) + err = gcompress.GzipPathWriter(fileMian, &obj) + service.S3().PutObject(ctx, &obj, updateFilePath+".gz") + g.Log().Debugf(ctx, "成功上传文件到:%v", updateFilePath+".gz") + } + if gfile.IsFile(fileMian + ".exe") { + // 写入文件哈希 + versionFile[updateFilePath+".gz"] = gsha1.MustEncryptFile(fileMian + ".exe") + err = gcompress.GzipPathWriter(fileMian+".exe", &obj) + service.S3().PutObject(ctx, &obj, updateFilePath+".gz") + g.Log().Debugf(ctx, "成功上传文件到:%v", updateFilePath+".gz") + } + + // 写入文件版本文件 + fileByte := gjson.MustEncode(versionFile) + service.S3().PutObject(ctx, bytes.NewReader(fileByte), path.Join("server_update", name, version, "version.json")) + if err != nil { + g.Log().Error(ctx, err) + } + } g.Log().Debugf(ctx, "当前获取到的地址为:%v", filePath) return diff --git a/internal/logic/logic.go b/internal/logic/logic.go index d7e0bfa..abc1b01 100644 --- a/internal/logic/logic.go +++ b/internal/logic/logic.go @@ -7,4 +7,5 @@ package logic import ( _ "github.com/ayflying/p2p/internal/logic/os" _ "github.com/ayflying/p2p/internal/logic/p2p" + _ "github.com/ayflying/p2p/internal/logic/s3" ) diff --git a/internal/logic/s3/s3.go b/internal/logic/s3/s3.go new file mode 100644 index 0000000..744e753 --- /dev/null +++ b/internal/logic/s3/s3.go @@ -0,0 +1,285 @@ +package s3 + +import ( + "context" + "fmt" + "io" + "log" + "net/url" + "path" + "time" + + "github.com/ayflying/p2p/internal/service" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gcache" + "github.com/gogf/gf/v2/os/gctx" + "github.com/gogf/gf/v2/util/gconv" + "github.com/minio/minio-go/v7" + "github.com/minio/minio-go/v7/pkg/credentials" +) + +// ctx 全局上下文,用于在整个包中传递请求范围的数据 +var ( +//client *minio.Client + +) + +// DataType 定义了 S3 配置的数据结构,用于存储访问 S3 所需的各种信息 +type DataType struct { + AccessKey string `json:"access_key"` // 访问 S3 的密钥 ID + SecretKey string `json:"secret_key"` // 访问 S3 的密钥 + Address string `json:"address"` // S3 服务的地址 + Ssl bool `json:"ssl"` // 是否使用 SSL 加密连接 + Url string `json:"url"` // S3 服务的访问 URL + BucketName string `json:"bucket_name"` // 默认存储桶名称 +} + +var ( + clientList = make(map[string]*minio.Client) // Minio S3 客户端实例 + cfgList = make(map[string]*DataType) // S3 配置信息 +) + +// Mod 定义了 S3 模块的结构体,包含一个 S3 客户端实例和配置信息 +type sS3 struct { + client *minio.Client + cfg *DataType +} + +func init() { + service.RegisterS3(New()) +} + +// New 根据配置创建一个新的 S3 模块实例 +// 如果未提供名称,则从配置中获取默认的 S3 类型 +// 配置错误时会触发 panic +func New(_name ...string) *sS3 { + + var name string + if len(_name) > 0 { + name = _name[0] + } else { + getName, _ := g.Cfg("local").Get(gctx.New(), "s3.type") + name = getName.String() + } + + if _, ok := cfgList[name]; ok { + return &sS3{ + client: clientList[name], + cfg: cfgList[name], + } + } + + get, err := g.Cfg("local").Get(gctx.New(), "s3."+name) + if err != nil { + panic(err.Error()) + } + var cfg *DataType + get.Scan(&cfg) + + // 使用 minio-go 创建 S3 客户端 + obj, err := minio.New( + cfg.Address, + &minio.Options{ + Creds: credentials.NewStaticV4(cfg.AccessKey, cfg.SecretKey, ""), + Secure: cfg.Ssl, + //BucketLookup: minio.BucketLookupPath, + }, + ) + if err != nil { + log.Fatalln(err) + } + + // 复制初始化参数 + cfgList[name] = cfg + clientList[name] = obj + + mod := &sS3{ + client: clientList[name], + cfg: cfgList[name], + } + + return mod +} + +//// GetCfg 获取当前 S3 模块的配置信息 +//func (s *sS3) GetCfg() *DataType { +// return s.cfg +//} + +// GetFileUrl 生成指向 S3 存储桶中指定文件的预签名 URL +// 预签名 URL 可用于在有限时间内访问 S3 存储桶中的文件 +// 支持从缓存中获取预签名 URL,以减少重复请求 +func (s *sS3) GetFileUrl(ctx context.Context, name string, _expires ...time.Duration) (presignedURL *url.URL, err error) { + // 设置预签名 URL 的有效期为 1 小时,可通过参数覆盖 + expires := time.Hour * 1 + if len(_expires) > 0 { + expires = _expires[0] + } + // 生成缓存键 + cacheKey := fmt.Sprintf("s3:%v:%v", name, s.cfg.BucketName) + // 尝试从缓存中获取预签名 URL + get, _ := gcache.Get(ctx, cacheKey) + if !get.IsEmpty() { + // 将缓存中的值转换为 *url.URL 类型 + err = gconv.Struct(get.Val(), &presignedURL) + return + } + // 调用 S3 客户端生成预签名 URL + presignedURL, err = s.client.PresignedGetObject(ctx, s.cfg.BucketName, name, expires, nil) + // 将生成的预签名 URL 存入缓存 + err = gcache.Set(ctx, cacheKey, presignedURL, expires) + return +} + +// PutFileUrl 生成一个用于上传文件到指定存储桶的预签名 URL +// 预签名 URL 的有效期默认为 10 分钟 +func (s *sS3) PutFileUrl(ctx context.Context, name string) (presignedURL *url.URL, err error) { + // 设置预签名 URL 的有效期为 10 分钟 + expires := time.Minute * 10 + // 调用 S3 客户端生成预签名 URL + presignedURL, err = s.client.PresignedPutObject(ctx, s.cfg.BucketName, name, expires) + return +} + +// ListBuckets 获取当前 S3 客户端可访问的所有存储桶列表 +// 出错时返回 nil +func (s *sS3) ListBuckets(ctx context.Context, ) []minio.BucketInfo { + buckets, err := s.client.ListBuckets(ctx) + if err != nil { + return nil + } + return buckets +} + +// PutObject 上传文件到指定的存储桶中 +// 支持指定文件大小,未指定时将读取文件直到结束 +func (s *sS3) PutObject(ctx context.Context, f io.Reader, name string, _size ...int64) (res minio.UploadInfo, err error) { + // 初始化文件大小为 -1,表示将读取文件至结束 + var size = int64(-1) + //if len(_size) > 0 { + // size = _size[0] + //} + // 调用 S3 客户端上传文件,设置内容类型为 "application/octet-stream" + res, err = s.client.PutObject(ctx, s.cfg.BucketName, name, f, size, minio.PutObjectOptions{ + //ContentType: "application/octet-stream", + }) + if err != nil { + // 记录上传错误日志 + g.Log().Error(ctx, err) + } + return +} + +// RemoveObject 从指定存储桶中删除指定名称的文件 +func (s *sS3) RemoveObject(ctx context.Context, name string) (err error) { + opts := minio.RemoveObjectOptions{ + ForceDelete: true, + //GovernanceBypass: true, + //VersionID: "myversionid", + } + // 调用 S3 客户端删除文件 + err = s.client.RemoveObject(ctx, s.cfg.BucketName, name, opts) + return +} + +// ListObjects 获取指定存储桶中指定前缀的文件列表 +// 返回一个包含文件信息的通道 +func (s *sS3) ListObjects(ctx context.Context, prefix string) (res <-chan minio.ObjectInfo, err error) { + // 调用 S3 客户端获取文件列表 + res = s.client.ListObjects(ctx, s.cfg.BucketName, minio.ListObjectsOptions{ + Prefix: prefix, + }) + return +} + +// StatObject 获取指定存储桶中指定文件的元数据信息 +func (s *sS3) StatObject(ctx context.Context, objectName string) (res minio.ObjectInfo, err error) { + res, err = s.client.StatObject(ctx, s.cfg.BucketName, objectName, minio.StatObjectOptions{}) + return +} + +//// SetBucketPolicy 设置指定存储桶或对象前缀的访问策略 +//// 目前使用固定的策略,可根据需求修改 +//func (s *sS3) SetBucketPolicy(ctx context.Context, prefix string) (err error) { +// // 定义访问策略 +// policy := `{"Version": "2012-10-17","Statement": [{"Action": ["s3:GetObject"],"Effect": "Allow","Principal": {"AWS": ["*"]},"Resource": ["arn:aws:s3:::my-bucketname/*"],"Sid": ""}]}` +// // 调用 S3 客户端设置存储桶策略 +// err = s.client.SetBucketPolicy(ctx, s.cfg.BucketName, policy) +// return +//} + +// GetUrl 获取文件的访问地址 +// 支持返回默认文件地址,根据 SSL 配置生成不同格式的 URL +func (s *sS3) GetUrl(filePath string, defaultFile ...string) (url string) { + bucketName := s.cfg.BucketName + get := s.cfg.Url + + // 如果没有指定文件路径,且提供了默认文件路径,则使用默认路径 + if filePath == "" && len(defaultFile) > 0 { + filePath = defaultFile[0] + } + + //switch s.cfg.Provider { + //case "qiniu": + // url = get + path.Join(bucketName, filePath) + //default: + // url = get + filePath + //} + url = get + filePath + + if !s.cfg.Ssl { + url = get + path.Join(bucketName, filePath) + } + + return +} + +// GetPath 从文件访问 URL 中提取文件路径 +func (s *sS3) GetPath(url string) (filePath string) { + bucketName := s.cfg.BucketName + get := s.cfg.Url + + return url[len(get+bucketName)+1:] +} + +// CopyObject 在指定存储桶内复制文件 +// bucketName 存储桶名称 +// dstStr 目标文件路径 +// srcStr 源文件路径 +// 返回操作过程中可能出现的错误 +func (s *sS3) CopyObject(ctx context.Context, dstStr string, srcStr string) (err error) { + // 定义目标文件的复制选项,包含存储桶名称和目标文件路径 + var dst = minio.CopyDestOptions{ + Bucket: s.cfg.BucketName, + Object: dstStr, + } + + // 定义源文件的复制选项,包含存储桶名称和源文件路径 + var src = minio.CopySrcOptions{ + Bucket: s.cfg.BucketName, + Object: srcStr, + } + + // 调用 S3 客户端的 CopyObject 方法,将源文件复制到目标位置 + // 忽略返回的复制信息,仅关注是否发生错误 + _, err = s.client.CopyObject(ctx, dst, src) + return +} + +// Rename 重命名文件 +func (s *sS3) Rename(ctx context.Context, oldName string, newName string) (err error) { + // 复制文件到新的名称 + g.Log().Debugf(nil, "仓库=%v,rename %s to %s", s.cfg.BucketName, oldName, newName) + err = s.CopyObject(ctx, newName, oldName) + if err != nil { + g.Log().Error(ctx, err) + return + } + // 删除原始文件 + err = s.RemoveObject(ctx, oldName) + if err != nil { + g.Log().Error(ctx, err) + return + } + return +} diff --git a/internal/service/s_3.go b/internal/service/s_3.go new file mode 100644 index 0000000..fbe3bdd --- /dev/null +++ b/internal/service/s_3.go @@ -0,0 +1,68 @@ +// ================================================================================ +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// You can delete these comments if you wish manually maintain this interface file. +// ================================================================================ + +package service + +import ( + "context" + "io" + "net/url" + "time" + + "github.com/minio/minio-go/v7" +) + +type ( + IS3 interface { + // GetFileUrl 生成指向 S3 存储桶中指定文件的预签名 URL + // 预签名 URL 可用于在有限时间内访问 S3 存储桶中的文件 + // 支持从缓存中获取预签名 URL,以减少重复请求 + GetFileUrl(ctx context.Context, name string, _expires ...time.Duration) (presignedURL *url.URL, err error) + // PutFileUrl 生成一个用于上传文件到指定存储桶的预签名 URL + // 预签名 URL 的有效期默认为 10 分钟 + PutFileUrl(ctx context.Context, name string) (presignedURL *url.URL, err error) + // ListBuckets 获取当前 S3 客户端可访问的所有存储桶列表 + // 出错时返回 nil + ListBuckets(ctx context.Context) []minio.BucketInfo + // PutObject 上传文件到指定的存储桶中 + // 支持指定文件大小,未指定时将读取文件直到结束 + PutObject(ctx context.Context, f io.Reader, name string, _size ...int64) (res minio.UploadInfo, err error) + // RemoveObject 从指定存储桶中删除指定名称的文件 + RemoveObject(ctx context.Context, name string) (err error) + // ListObjects 获取指定存储桶中指定前缀的文件列表 + // 返回一个包含文件信息的通道 + ListObjects(ctx context.Context, prefix string) (res <-chan minio.ObjectInfo, err error) + // StatObject 获取指定存储桶中指定文件的元数据信息 + StatObject(ctx context.Context, objectName string) (res minio.ObjectInfo, err error) + // GetUrl 获取文件的访问地址 + // 支持返回默认文件地址,根据 SSL 配置生成不同格式的 URL + GetUrl(filePath string, defaultFile ...string) (url string) + // GetPath 从文件访问 URL 中提取文件路径 + GetPath(url string) (filePath string) + // CopyObject 在指定存储桶内复制文件 + // bucketName 存储桶名称 + // dstStr 目标文件路径 + // srcStr 源文件路径 + // 返回操作过程中可能出现的错误 + CopyObject(ctx context.Context, dstStr string, srcStr string) (err error) + // Rename 重命名文件 + Rename(ctx context.Context, oldName string, newName string) (err error) + } +) + +var ( + localS3 IS3 +) + +func S3() IS3 { + if localS3 == nil { + panic("implement not found for interface IS3, forgot register?") + } + return localS3 +} + +func RegisterS3(i IS3) { + localS3 = i +}