diff --git a/go.mod b/go.mod index 4e0d2a0..00dc24a 100644 --- a/go.mod +++ b/go.mod @@ -1,10 +1,14 @@ module gitlink.org.cn/cloudream/common -go 1.20 +go 1.23.0 + +toolchain go1.23.2 require ( github.com/antonfisher/nested-logrus-formatter v1.3.1 - github.com/google/uuid v1.3.0 + github.com/aws/aws-sdk-go-v2 v1.36.3 + github.com/aws/aws-sdk-go-v2/credentials v1.17.62 + github.com/google/uuid v1.6.0 github.com/hashicorp/go-multierror v1.1.1 github.com/imdario/mergo v0.3.15 github.com/json-iterator/go v1.1.12 @@ -12,39 +16,41 @@ require ( github.com/mitchellh/mapstructure v1.5.0 github.com/modern-go/reflect2 v1.0.2 github.com/otiai10/copy v1.12.0 - github.com/samber/lo v1.36.0 - github.com/sirupsen/logrus v1.9.2 - github.com/smartystreets/goconvey v1.8.0 + github.com/samber/lo v1.49.1 + github.com/sirupsen/logrus v1.9.3 + github.com/smartystreets/goconvey v1.8.1 github.com/streadway/amqp v1.1.0 github.com/zyedidia/generic v1.2.1 - go.etcd.io/etcd/client/v3 v3.5.9 - golang.org/x/exp v0.0.0-20230519143937-03e91628a987 + go.etcd.io/etcd/client/v3 v3.5.12 + golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa +) + +require ( + github.com/aws/smithy-go v1.22.2 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/smarty/assertions v1.15.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20241015192408-796eee8c2d53 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38 // indirect ) require ( - github.com/benbjohnson/clock v1.3.0 // indirect - github.com/coreos/go-semver v0.3.0 // indirect + github.com/coreos/go-semver v0.3.1 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang/protobuf v1.5.3 // indirect + github.com/golang/protobuf v1.5.4 // indirect + github.com/google/go-querystring v1.1.0 github.com/gopherjs/gopherjs v1.17.2 // indirect - github.com/gorilla/websocket v1.5.3 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/jtolds/gls v4.20.0+incompatible // indirect - github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect - github.com/pkg/errors v0.9.1 // indirect - github.com/smartystreets/assertions v1.13.1 // indirect - github.com/stretchr/testify v1.8.2 // indirect - go.etcd.io/etcd/api/v3 v3.5.9 // indirect - go.etcd.io/etcd/client/pkg/v3 v3.5.9 // indirect - go.uber.org/atomic v1.10.0 // indirect - go.uber.org/goleak v1.1.12 // indirect - go.uber.org/multierr v1.9.0 // indirect - go.uber.org/zap v1.24.0 // indirect - golang.org/x/net v0.8.0 // indirect - golang.org/x/sys v0.6.0 // indirect - golang.org/x/text v0.8.0 // indirect - google.golang.org/genproto v0.0.0-20230403163135-c38d8f061ccd // indirect - google.golang.org/grpc v1.54.0 // indirect - google.golang.org/protobuf v1.30.0 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + go.etcd.io/etcd/api/v3 v3.5.12 // indirect + go.etcd.io/etcd/client/pkg/v3 v3.5.12 // indirect + go.uber.org/multierr v1.10.0 // indirect + go.uber.org/zap v1.27.0 // indirect + golang.org/x/net v0.35.0 // indirect + golang.org/x/sys v0.30.0 // indirect + golang.org/x/text v0.22.0 // indirect + google.golang.org/grpc v1.67.1 // indirect + google.golang.org/protobuf v1.35.1 // indirect ) diff --git a/go.sum b/go.sum index c51be4b..58cfdfc 100644 --- a/go.sum +++ b/go.sum @@ -1,29 +1,34 @@ github.com/antonfisher/nested-logrus-formatter v1.3.1 h1:NFJIr+pzwv5QLHTPyKz9UMEoHck02Q9L0FP13b/xSbQ= github.com/antonfisher/nested-logrus-formatter v1.3.1/go.mod h1:6WTfyWFkBc9+zyBaKIqRrg/KwMqBbodBjgbHjDz7zjA= -github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= -github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= -github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM= -github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/aws/aws-sdk-go-v2 v1.36.3 h1:mJoei2CxPutQVxaATCzDUjcZEjVRdpsiiXi2o38yqWM= +github.com/aws/aws-sdk-go-v2 v1.36.3/go.mod h1:LLXuLpgzEbD766Z5ECcRmi8AzSwfZItDtmABVkRLGzg= +github.com/aws/aws-sdk-go-v2/credentials v1.17.62 h1:fvtQY3zFzYJ9CfixuAQ96IxDrBajbBWGqjNTCa79ocU= +github.com/aws/aws-sdk-go-v2/credentials v1.17.62/go.mod h1:ElETBxIQqcxej++Cs8GyPBbgMys5DgQPTwo7cUPDKt8= +github.com/aws/smithy-go v1.22.2 h1:6D9hW43xKFrRx/tXXfAlIZc4JI+yQe6snnWcQyxSyLQ= +github.com/aws/smithy-go v1.22.2/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= +github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4= +github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/go-cmp v0.5.2/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/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= +github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= 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.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 v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g= github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k= -github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= -github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -37,119 +42,96 @@ github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7 github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/magefile/mage v1.15.0 h1:BvGheCMAsG3bWUDbZ8AyXXpCNwU9u5CB6sM+HNb9HYg= github.com/magefile/mage v1.15.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/otiai10/copy v1.12.0 h1:cLMgSQnXBs1eehF0Wy/FAGsgDTDmAqFR7rQylBb1nDY= github.com/otiai10/copy v1.12.0/go.mod h1:rSaLseMUsZFFbsFGc7wCJnnkTAvdc5L6VWxPE4308Ww= github.com/otiai10/mint v1.5.1 h1:XaPLeE+9vGbuyEHem1JNk3bYc7KKqyI/na0/mLd/Kks= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/otiai10/mint v1.5.1/go.mod h1:MJm72SBthJjz8qhefc4z1PYEieWmy8Bku7CjcAqyUSM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/samber/lo v1.36.0 h1:4LaOxH1mHnbDGhTVE0i1z8v/lWaQW8AIfOD3HU4mSaw= -github.com/samber/lo v1.36.0/go.mod h1:HLeWcJRRyLKp3+/XBJvOrerCQn9mhdKMHyd7IRlgeQ8= -github.com/sirupsen/logrus v1.9.2 h1:oxx1eChJGI6Uks2ZC4W1zpLlVgqB8ner4EuQwV4Ik1Y= -github.com/sirupsen/logrus v1.9.2/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/smartystreets/assertions v1.13.1 h1:Ef7KhSmjZcK6AVf9YbJdvPYG9avaF0ZxudX+ThRdWfU= -github.com/smartystreets/assertions v1.13.1/go.mod h1:cXr/IwVfSo/RbCSPhoAPv73p3hlSdrBH/b3SdnW/LMY= -github.com/smartystreets/goconvey v1.8.0 h1:Oi49ha/2MURE0WexF052Z0m+BNSGirfjg5RL+JXWq3w= -github.com/smartystreets/goconvey v1.8.0/go.mod h1:EdX8jtrTIj26jmjCOVNMVSIYAtgexqXKHOXW2Dx9JLg= +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/samber/lo v1.49.1 h1:4BIFyVfuQSEpluc7Fua+j1NolZHiEHEpaSEKdsH0tew= +github.com/samber/lo v1.49.1/go.mod h1:dO6KHFzUKXgP8LDhU0oI8d2hekjXnGOu0DB8Jecxd6o= +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/goconvey v1.8.1 h1:qGjIddxOk4grTu9JPOU31tVfq3cNdBlNa5sSznIX1xY= +github.com/smartystreets/goconvey v1.8.1/go.mod h1:+/u4qLyY6x1jReYOp7GOM2FSt8aP9CzCZL03bI28W60= github.com/streadway/amqp v1.1.0 h1:py12iX8XSyI7aN/3dUT8DFIDJazNJsVJdxNVEpnQTZM= github.com/streadway/amqp v1.1.0/go.mod h1:WYSrTEYHOXHd0nwFeUXAe2G2hRnQT+deZJJf88uS9Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= -github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/thoas/go-funk v0.9.1 h1:O549iLZqPpTUQ10ykd26sZhzD+rmR5pWhuElrhbC20M= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/zyedidia/generic v1.2.1 h1:Zv5KS/N2m0XZZiuLS82qheRG4X1o5gsWreGb0hR7XDc= github.com/zyedidia/generic v1.2.1/go.mod h1:ly2RBz4mnz1yeuVbQA/VFwGjK3mnHGRj1JuoG336Bis= -go.etcd.io/etcd/api/v3 v3.5.9 h1:4wSsluwyTbGGmyjJktOf3wFQoTBIURXHnq9n/G/JQHs= -go.etcd.io/etcd/api/v3 v3.5.9/go.mod h1:uyAal843mC8uUVSLWz6eHa/d971iDGnCRpmKd2Z+X8k= -go.etcd.io/etcd/client/pkg/v3 v3.5.9 h1:oidDC4+YEuSIQbsR94rY9gur91UPL6DnxDCIYd2IGsE= -go.etcd.io/etcd/client/pkg/v3 v3.5.9/go.mod h1:y+CzeSmkMpWN2Jyu1npecjB9BBnABxGM4pN8cGuJeL4= -go.etcd.io/etcd/client/v3 v3.5.9 h1:r5xghnU7CwbUxD/fbUtRyJGaYNfDun8sp/gTr1hew6E= -go.etcd.io/etcd/client/v3 v3.5.9/go.mod h1:i/Eo5LrZ5IKqpbtpPDuaUnDOUv471oDg8cjQaUr2MbA= -go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= -go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= -go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA= -go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= -go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= -go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= -go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= -go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= +go.etcd.io/etcd/api/v3 v3.5.12 h1:W4sw5ZoU2Juc9gBWuLk5U6fHfNVyY1WC5g9uiXZio/c= +go.etcd.io/etcd/api/v3 v3.5.12/go.mod h1:Ot+o0SWSyT6uHhA56al1oCED0JImsRiU9Dc26+C2a+4= +go.etcd.io/etcd/client/pkg/v3 v3.5.12 h1:EYDL6pWwyOsylrQyLp2w+HkQ46ATiOvoEdMarindU2A= +go.etcd.io/etcd/client/pkg/v3 v3.5.12/go.mod h1:seTzl2d9APP8R5Y2hFL3NVlD6qC/dOT+3kvrqPyTas4= +go.etcd.io/etcd/client/v3 v3.5.12 h1:v5lCPXn1pf1Uu3M4laUE2hp/geOTc5uPcYYsNe1lDxg= +go.etcd.io/etcd/client/v3 v3.5.12/go.mod h1:tSbBCakoWmmddL+BKVAJHa9km+O/E+bumDe9mSbPiqw= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= +go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 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/exp v0.0.0-20230519143937-03e91628a987 h1:3xJIFvzUFbu4ls0BTBYcgbCGhA63eAOEMxIHugyXJqA= -golang.org/x/exp v0.0.0-20230519143937-03e91628a987/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa h1:t2QcU6V556bFjYgu4L6C+6VrCPyJZ+eyRsABUPs1mz4= +golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa/go.mod h1:BHOTPb3L19zxehTsLoJXVaTktb06DFgmdW6Wb9s8jqk= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -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-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= -golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= +golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= golang.org/x/sync v0.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-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/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-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= +golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= -golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= +golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= 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= -google.golang.org/genproto v0.0.0-20230403163135-c38d8f061ccd h1:sLpv7bNL1AsX3fdnWh9WVh7ejIzXdOc1RRHGeAmeStU= -google.golang.org/genproto v0.0.0-20230403163135-c38d8f061ccd/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak= -google.golang.org/grpc v1.54.0 h1:EhTqbhiYeixwWQtAEZAxmV9MGqcjEU2mFx52xCzNyag= -google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g= -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.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= -google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/genproto/googleapis/api v0.0.0-20241015192408-796eee8c2d53 h1:fVoAXEKA4+yufmbdVYv+SE73+cPZbbbe8paLsHfkK+U= +google.golang.org/genproto/googleapis/api v0.0.0-20241015192408-796eee8c2d53/go.mod h1:riSXTwQ4+nqmPGtobMFyW5FqVAmIs0St6VPp4Ug7CE4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38 h1:zciRKQ4kBpFgpfC5QQCVtnnNAcLIqweL7plyZRQHVpI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= +google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E= +google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= +google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= +google.golang.org/protobuf v1.35.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/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/sdks/sdks.go b/sdks/sdks.go index c556cd5..6f303c9 100644 --- a/sdks/sdks.go +++ b/sdks/sdks.go @@ -1,6 +1,19 @@ package sdks -import "fmt" +import ( + "bytes" + "fmt" + "io" + "net/http" + "net/url" + "strings" + + "github.com/google/go-querystring/query" + "gitlink.org.cn/cloudream/common/consts/errorcode" + "gitlink.org.cn/cloudream/common/utils/http2" + "gitlink.org.cn/cloudream/common/utils/io2" + "gitlink.org.cn/cloudream/common/utils/serder" +) type CodeMessageError struct { Code string @@ -10,3 +23,179 @@ type CodeMessageError struct { func (e *CodeMessageError) Error() string { return fmt.Sprintf("code: %s, message: %s", e.Code, e.Message) } + +type RequestParam struct { + Method string + Path string + Query url.Values + Header http.Header + Body RequestBody +} + +func (p *RequestParam) MakeRequest(baseURL string) (*http.Request, error) { + var body io.ReadCloser + bodyLen := int64(0) + if p.Body != nil { + body = p.Body.IntoStream() + bodyLen = p.Body.Length() + } + + req, err := http.NewRequest(p.Method, joinUrlUnsafe(baseURL, p.Path), body) + if err != nil { + return nil, err + } + req.ContentLength = bodyLen + req.URL.RawQuery = p.Query.Encode() + + if p.Header != nil { + req.Header = p.Header + } + + return req, nil +} + +func (p *RequestParam) Do(baseURL string, cli *http.Client) (*http.Response, error) { + req, err := p.MakeRequest(baseURL) + if err != nil { + return nil, err + } + + return cli.Do(req) +} + +func joinUrlUnsafe(base string, path string) string { + if strings.HasSuffix(base, "/") { + if strings.HasPrefix(path, "/") { + return base + path[1:] + } + + return base + path + } + + if strings.HasPrefix(path, "/") { + return base + path + } + + return base + "/" + path +} + +type RequestBody interface { + // 请求体长度,如果长度未知,返回-1 + Length() int64 + // 将内部值变成一个流,用于发送请求 + IntoStream() io.ReadCloser +} + +type StringBody struct { + Value string +} + +func (s *StringBody) Length() int64 { + return int64(len(s.Value)) +} + +func (s *StringBody) IntoStream() io.ReadCloser { + return io.NopCloser(bytes.NewReader([]byte(s.Value))) +} + +type BytesBody struct { + Value []byte +} + +func (b *BytesBody) Length() int64 { + return int64(len(b.Value)) +} + +func (b *BytesBody) IntoStream() io.ReadCloser { + return io.NopCloser(bytes.NewReader(b.Value)) +} + +type StreamBody struct { + Stream io.ReadCloser + LengthHint int64 // 长度提示,如果长度未知,可以设置为-1 +} + +func (s *StreamBody) Length() int64 { + return s.LengthHint +} + +func (s *StreamBody) IntoStream() io.ReadCloser { + return s.Stream +} + +type APIRequest interface { + MakeParam() *RequestParam +} + +func MakeJSONParam(method string, path string, body any) *RequestParam { + data, err := serder.ObjectToJSONEx(body) + if err != nil { + // 开发人员应该保证param是可序列化的 + panic(err) + } + + return &RequestParam{ + Method: method, + Path: path, + Body: &BytesBody{Value: data}, + } +} + +func MakeQueryParam(method string, path string, q any) *RequestParam { + values, err := query.Values(q) + if err != nil { + // 开发人员应该保证param是可序列化的 + panic(err) + } + + return &RequestParam{ + Method: method, + Path: path, + Query: values, + } +} + +type APIResponse interface { + ParseResponse(resp *http.Response) error +} + +type CodeDataResponse[T any] struct { + Code string `json:"code"` + Message string `json:"message"` + Data T `json:"data"` +} + +func (r *CodeDataResponse[T]) Unwarp() (T, error) { + if r.Code == errorcode.OK { + return r.Data, nil + } + var def T + return def, &CodeMessageError{Code: r.Code, Message: r.Message} +} + +func ParseCodeDataJSONResponse[T any](resp *http.Response, ret T) error { + contType := resp.Header.Get("Content-Type") + if strings.Contains(contType, http2.ContentTypeJSON) { + var err error + var r CodeDataResponse[T] + r.Data = ret + + if err = serder.JSONToObjectStreamExRaw(resp.Body, &r); err != nil { + return fmt.Errorf("parsing response: %w", err) + } + + if r.Code != errorcode.OK { + return &CodeMessageError{Code: r.Code, Message: r.Message} + } + + return nil + } + + cont, err := io2.ReadMost(resp.Body, 200) + if err != nil { + return fmt.Errorf("unknow response content type: %s, status: %d", contType, resp.StatusCode) + } + strCont := string(cont) + + return fmt.Errorf("unknow response content type: %s, status: %d, body[:200]: %s", contType, resp.StatusCode, strCont) +} diff --git a/sdks/storage/cdsapi/bucket.go b/sdks/storage/cdsapi/bucket.go index 2cb2e9c..f0c1ad1 100644 --- a/sdks/storage/cdsapi/bucket.go +++ b/sdks/storage/cdsapi/bucket.go @@ -1,11 +1,10 @@ package cdsapi import ( - "net/url" + "net/http" - "gitlink.org.cn/cloudream/common/consts/errorcode" + "gitlink.org.cn/cloudream/common/sdks" cdssdk "gitlink.org.cn/cloudream/common/sdks/storage" - "gitlink.org.cn/cloudream/common/utils/http2" ) type BucketService struct { @@ -19,36 +18,24 @@ func (c *Client) Bucket() *BucketService { const BucketGetByNamePath = "/bucket/getByName" type BucketGetByName struct { - UserID cdssdk.UserID `json:"userID" form:"userID" binding:"required"` - Name string `json:"name" form:"name" binding:"required"` + UserID cdssdk.UserID `url:"userID" form:"userID" binding:"required"` + Name string `url:"name" form:"name" binding:"required"` } + +func (r *BucketGetByName) MakeParam() *sdks.RequestParam { + return sdks.MakeQueryParam(http.MethodGet, BucketGetByNamePath, r) +} + type BucketGetByNameResp struct { Bucket cdssdk.Bucket `json:"bucket"` } -func (c *BucketService) GetByName(req BucketGetByName) (*BucketGetByNameResp, error) { - url, err := url.JoinPath(c.baseURL, BucketGetByNamePath) - if err != nil { - return nil, err - } - - resp, err := http2.GetForm(url, http2.RequestParam{ - Query: req, - }) - if err != nil { - return nil, err - } - - codeResp, err := ParseJSONResponse[response[BucketGetByNameResp]](resp) - if err != nil { - return nil, err - } - - if codeResp.Code == errorcode.OK { - return &codeResp.Data, nil - } +func (r *BucketGetByNameResp) ParseResponse(resp *http.Response) error { + return sdks.ParseCodeDataJSONResponse(resp, r) +} - return nil, codeResp.ToError() +func (c *BucketService) GetByName(req BucketGetByName) (*BucketGetByNameResp, error) { + return JSONAPI(c.cfg, http.DefaultClient, &req, &BucketGetByNameResp{}) } const BucketCreatePath = "/bucket/create" @@ -58,33 +45,20 @@ type BucketCreate struct { Name string `json:"name" binding:"required"` } +func (r *BucketCreate) MakeParam() *sdks.RequestParam { + return sdks.MakeJSONParam(http.MethodPost, BucketCreatePath, r) +} + type BucketCreateResp struct { Bucket cdssdk.Bucket `json:"bucket"` } -func (c *BucketService) Create(req BucketCreate) (*BucketCreateResp, error) { - url, err := url.JoinPath(c.baseURL, BucketCreatePath) - if err != nil { - return nil, err - } - - resp, err := http2.PostJSON(url, http2.RequestParam{ - Body: req, - }) - if err != nil { - return nil, err - } - - codeResp, err := ParseJSONResponse[response[BucketCreateResp]](resp) - if err != nil { - return nil, err - } - - if codeResp.Code == errorcode.OK { - return &codeResp.Data, nil - } +func (r *BucketCreateResp) ParseResponse(resp *http.Response) error { + return sdks.ParseCodeDataJSONResponse(resp, r) +} - return nil, codeResp.ToError() +func (c *BucketService) Create(req BucketCreate) (*BucketCreateResp, error) { + return JSONAPI(c.cfg, http.DefaultClient, &req, &BucketCreateResp{}) } const BucketDeletePath = "/bucket/delete" @@ -94,64 +68,38 @@ type BucketDelete struct { BucketID cdssdk.BucketID `json:"bucketID" binding:"required"` } -type BucketDeleteResp struct{} - -func (c *BucketService) Delete(req BucketDelete) error { - url, err := url.JoinPath(c.baseURL, BucketDeletePath) - if err != nil { - return err - } - - resp, err := http2.PostJSON(url, http2.RequestParam{ - Body: req, - }) - if err != nil { - return err - } +func (r *BucketDelete) MakeParam() *sdks.RequestParam { + return sdks.MakeJSONParam(http.MethodPost, BucketDeletePath, r) +} - codeResp, err := ParseJSONResponse[response[BucketDeleteResp]](resp) - if err != nil { - return err - } +type BucketDeleteResp struct{} - if codeResp.Code == errorcode.OK { - return nil - } +func (r *BucketDeleteResp) ParseResponse(resp *http.Response) error { + return sdks.ParseCodeDataJSONResponse(resp, r) +} - return codeResp.ToError() +func (c *BucketService) Delete(req BucketDelete) error { + return JSONAPINoData(c.cfg, http.DefaultClient, &req) } const BucketListUserBucketsPath = "/bucket/listUserBuckets" type BucketListUserBucketsReq struct { - UserID cdssdk.UserID `form:"userID" json:"userID" binding:"required"` + UserID cdssdk.UserID `form:"userID" url:"userID" binding:"required"` +} + +func (r *BucketListUserBucketsReq) MakeParam() *sdks.RequestParam { + return sdks.MakeQueryParam(http.MethodGet, BucketListUserBucketsPath, r) } type BucketListUserBucketsResp struct { Buckets []cdssdk.Bucket `json:"buckets"` } +func (r *BucketListUserBucketsResp) ParseResponse(resp *http.Response) error { + return sdks.ParseCodeDataJSONResponse(resp, r) +} + func (c *BucketService) ListUserBuckets(req BucketListUserBucketsReq) (*BucketListUserBucketsResp, error) { - url, err := url.JoinPath(c.baseURL, BucketListUserBucketsPath) - if err != nil { - return nil, err - } - - resp, err := http2.GetForm(url, http2.RequestParam{ - Query: req, - }) - if err != nil { - return nil, err - } - - codeResp, err := ParseJSONResponse[response[BucketListUserBucketsResp]](resp) - if err != nil { - return nil, err - } - - if codeResp.Code == errorcode.OK { - return &codeResp.Data, nil - } - - return nil, codeResp.ToError() + return JSONAPI(c.cfg, http.DefaultClient, &req, &BucketListUserBucketsResp{}) } diff --git a/sdks/storage/cdsapi/cache.go b/sdks/storage/cdsapi/cache.go index 609cdd0..b71725d 100644 --- a/sdks/storage/cdsapi/cache.go +++ b/sdks/storage/cdsapi/cache.go @@ -1,11 +1,10 @@ package cdsapi import ( - "net/url" + "net/http" - "gitlink.org.cn/cloudream/common/consts/errorcode" + "gitlink.org.cn/cloudream/common/sdks" cdssdk "gitlink.org.cn/cloudream/common/sdks/storage" - "gitlink.org.cn/cloudream/common/utils/http2" ) const CacheMovePackagePath = "/cache/movePackage" @@ -15,29 +14,17 @@ type CacheMovePackageReq struct { PackageID cdssdk.PackageID `json:"packageID"` StorageID cdssdk.StorageID `json:"storageID"` } + +func (r *CacheMovePackageReq) MakeParam() *sdks.RequestParam { + return sdks.MakeJSONParam(http.MethodPost, CacheMovePackagePath, r) +} + type CacheMovePackageResp struct{} +func (r *CacheMovePackageResp) ParseResponse(resp *http.Response) error { + return sdks.ParseCodeDataJSONResponse(resp, r) +} + func (c *Client) CacheMovePackage(req CacheMovePackageReq) (*CacheMovePackageResp, error) { - url, err := url.JoinPath(c.baseURL, CacheMovePackagePath) - if err != nil { - return nil, err - } - - resp, err := http2.PostJSON(url, http2.RequestParam{ - Body: req, - }) - if err != nil { - return nil, err - } - - jsonResp, err := ParseJSONResponse[response[CacheMovePackageResp]](resp) - if err != nil { - return nil, err - } - - if jsonResp.Code == errorcode.OK { - return &jsonResp.Data, nil - } - - return nil, jsonResp.ToError() + return JSONAPI(c.cfg, http.DefaultClient, &req, &CacheMovePackageResp{}) } diff --git a/sdks/storage/cdsapi/client.go b/sdks/storage/cdsapi/client.go index 40af4dd..8e12be3 100644 --- a/sdks/storage/cdsapi/client.go +++ b/sdks/storage/cdsapi/client.go @@ -18,12 +18,12 @@ func (r *response[T]) ToError() *sdks.CodeMessageError { } type Client struct { - baseURL string + cfg *Config } func NewClient(cfg *Config) *Client { return &Client{ - baseURL: cfg.URL, + cfg: cfg, } } diff --git a/sdks/storage/cdsapi/config.go b/sdks/storage/cdsapi/config.go index 484808c..448039e 100644 --- a/sdks/storage/cdsapi/config.go +++ b/sdks/storage/cdsapi/config.go @@ -1,5 +1,7 @@ package cdsapi type Config struct { - URL string `json:"url"` + URL string `json:"url"` + AccessKey string `json:"accessKey"` + SecretKey string `json:"secretKey"` } diff --git a/sdks/storage/cdsapi/hub.go b/sdks/storage/cdsapi/hub.go index d1ed7b7..20a9d18 100644 --- a/sdks/storage/cdsapi/hub.go +++ b/sdks/storage/cdsapi/hub.go @@ -1,44 +1,30 @@ package cdsapi import ( - "net/url" + "net/http" - "gitlink.org.cn/cloudream/common/consts/errorcode" + "gitlink.org.cn/cloudream/common/sdks" cdssdk "gitlink.org.cn/cloudream/common/sdks/storage" - "gitlink.org.cn/cloudream/common/utils/http2" ) var HubGetHubsPath = "/hub/getHubs" type HubGetHubsReq struct { - HubIDs []cdssdk.HubID `json:"hubIDs"` + HubIDs []cdssdk.HubID `form:"hubIDs" url:"hubIDs"` +} + +func (r *HubGetHubsReq) MakeParam() *sdks.RequestParam { + return sdks.MakeQueryParam(http.MethodGet, HubGetHubsPath, r) } type HubGetHubsResp struct { Hubs []*cdssdk.Hub `json:"hubs"` } +func (r *HubGetHubsResp) ParseResponse(resp *http.Response) error { + return sdks.ParseCodeDataJSONResponse(resp, r) +} + func (c *Client) HubGetHubs(req HubGetHubsReq) (*HubGetHubsResp, error) { - url, err := url.JoinPath(c.baseURL, HubGetHubsPath) - if err != nil { - return nil, err - } - - resp, err := http2.GetForm(url, http2.RequestParam{ - Query: req, - }) - if err != nil { - return nil, err - } - - jsonResp, err := ParseJSONResponse[response[HubGetHubsResp]](resp) - if err != nil { - return nil, err - } - - if jsonResp.Code == errorcode.OK { - return &jsonResp.Data, nil - } - - return nil, jsonResp.ToError() + return JSONAPI(c.cfg, http.DefaultClient, &req, &HubGetHubsResp{}) } diff --git a/sdks/storage/cdsapi/hub_io.go b/sdks/storage/cdsapi/hub_io.go index bd5f1a3..826c425 100644 --- a/sdks/storage/cdsapi/hub_io.go +++ b/sdks/storage/cdsapi/hub_io.go @@ -24,7 +24,7 @@ type GetStreamReq struct { } func (c *Client) GetStream(req GetStreamReq) (io.ReadCloser, error) { - targetUrl, err := url.JoinPath(c.baseURL, GetStreamPath) + targetUrl, err := url.JoinPath(c.cfg.URL, GetStreamPath) if err != nil { return nil, err } @@ -65,7 +65,7 @@ type SendStreamInfo struct { } func (c *Client) SendStream(req SendStreamReq) error { - targetUrl, err := url.JoinPath(c.baseURL, SendStreamPath) + targetUrl, err := url.JoinPath(c.cfg.URL, SendStreamPath) if err != nil { return err } @@ -133,7 +133,7 @@ type ExecuteIOPlanReq struct { } func (c *Client) ExecuteIOPlan(req ExecuteIOPlanReq) error { - targetUrl, err := url.JoinPath(c.baseURL, ExecuteIOPlanPath) + targetUrl, err := url.JoinPath(c.cfg.URL, ExecuteIOPlanPath) if err != nil { return err } @@ -171,7 +171,7 @@ type SendVarReq struct { } func (c *Client) SendVar(req SendVarReq) error { - targetUrl, err := url.JoinPath(c.baseURL, SendVarPath) + targetUrl, err := url.JoinPath(c.cfg.URL, SendVarPath) if err != nil { return err } @@ -214,7 +214,7 @@ type GetVarResp struct { } func (c *Client) GetVar(req GetVarReq) (*GetVarResp, error) { - targetUrl, err := url.JoinPath(c.baseURL, GetVarPath) + targetUrl, err := url.JoinPath(c.cfg.URL, GetVarPath) if err != nil { return nil, err } diff --git a/sdks/storage/cdsapi/object.go b/sdks/storage/cdsapi/object.go index e3486fb..aca0aa9 100644 --- a/sdks/storage/cdsapi/object.go +++ b/sdks/storage/cdsapi/object.go @@ -1,15 +1,20 @@ package cdsapi import ( + "context" "fmt" "io" "mime" + "net/http" "net/url" "strings" "time" + v4 "github.com/aws/aws-sdk-go-v2/aws/signer/v4" + "github.com/aws/aws-sdk-go-v2/credentials" "gitlink.org.cn/cloudream/common/consts/errorcode" "gitlink.org.cn/cloudream/common/pkgs/iterator" + "gitlink.org.cn/cloudream/common/sdks" cdssdk "gitlink.org.cn/cloudream/common/sdks/storage" "gitlink.org.cn/cloudream/common/utils/http2" "gitlink.org.cn/cloudream/common/utils/serder" @@ -28,73 +33,52 @@ func (c *Client) Object() *ObjectService { const ObjectListPathByPath = "/object/listByPath" type ObjectListByPath struct { - UserID cdssdk.UserID `form:"userID" binding:"required"` - PackageID cdssdk.PackageID `form:"packageID" binding:"required"` - Path string `form:"path"` // 允许为空字符串 - IsPrefix bool `form:"isPrefix"` -} -type ObjectListByPathResp struct { - Objects []cdssdk.Object `json:"objects"` -} + UserID cdssdk.UserID `form:"userID" binding:"required" url:"userID"` + PackageID cdssdk.PackageID `form:"packageID" binding:"required" url:"packageID"` + Path string `form:"path" url:"path"` // 允许为空字符串 + IsPrefix bool `form:"isPrefix" url:"isPrefix"` + NoRecursive bool `form:"noRecursive" url:"noRecursive"` // 仅当isPrefix为true时有效,表示仅查询直接属于Prefix下的对象,对于更深的对象,返回它们的公共前缀 -func (c *ObjectService) ListByPath(req ObjectListByPath) (*ObjectListByPathResp, error) { - url, err := url.JoinPath(c.baseURL, ObjectListPathByPath) - if err != nil { - return nil, err - } +} - resp, err := http2.GetForm(url, http2.RequestParam{ - Query: req, - }) - if err != nil { - return nil, err - } +func (r *ObjectListByPath) MakeParam() *sdks.RequestParam { + return sdks.MakeQueryParam(http.MethodGet, ObjectListPathByPath, r) +} - jsonResp, err := ParseJSONResponse[response[ObjectListByPathResp]](resp) - if err != nil { - return nil, err - } +type ObjectListByPathResp struct { + CommonPrefixes []string `json:"commonPrefixes"` // 仅在IsPrefix为true且NoRecursive为true时有效,包含更深层对象的shared prefix + Objects []cdssdk.Object `json:"objects"` // 如果IsPrefix为true且NoRecursive为false,则返回所有匹配的对象,否则只返回直接属于Prefix下的对象 +} - if jsonResp.Code == errorcode.OK { - return &jsonResp.Data, nil - } +func (r *ObjectListByPathResp) ParseResponse(resp *http.Response) error { + return sdks.ParseCodeDataJSONResponse(resp, r) +} - return nil, jsonResp.ToError() +func (c *ObjectService) ListByPath(req ObjectListByPath) (*ObjectListByPathResp, error) { + return JSONAPI(c.cfg, http.DefaultClient, &req, &ObjectListByPathResp{}) } const ObjectListByIDsPath = "/object/listByIDs" type ObjectListByIDs struct { - UserID cdssdk.UserID `json:"userID" binding:"required"` - ObjectIDs []cdssdk.ObjectID `json:"objectIDs" binding:"required"` + UserID cdssdk.UserID `form:"userID" binding:"required" url:"userID"` + ObjectIDs []cdssdk.ObjectID `form:"objectIDs" binding:"required" url:"objectIDs"` +} + +func (r *ObjectListByIDs) MakeParam() *sdks.RequestParam { + return sdks.MakeQueryParam(http.MethodGet, ObjectListByIDsPath, r) } + type ObjectListByIDsResp struct { Objects []*cdssdk.Object `json:"object"` // 与ObjectIDs一一对应,如果某个ID不存在,则对应位置为nil } -func (c *ObjectService) ListByIDs(req ObjectListByIDs) (*ObjectListByIDsResp, error) { - url, err := url.JoinPath(c.baseURL, ObjectListByIDsPath) - if err != nil { - return nil, err - } - - resp, err := http2.PostJSON(url, http2.RequestParam{ - Body: req, - }) - if err != nil { - return nil, err - } - - jsonResp, err := ParseJSONResponse[response[ObjectListByIDsResp]](resp) - if err != nil { - return nil, err - } - - if jsonResp.Code == errorcode.OK { - return &jsonResp.Data, nil - } +func (r *ObjectListByIDsResp) ParseResponse(resp *http.Response) error { + return sdks.ParseCodeDataJSONResponse(resp, r) +} - return nil, jsonResp.ToError() +func (c *ObjectService) ListByIDs(req ObjectListByIDs) (*ObjectListByIDsResp, error) { + return JSONAPI(c.cfg, http.DefaultClient, &req, &ObjectListByIDsResp{}) } const ObjectUploadPath = "/object/upload" @@ -124,7 +108,11 @@ type ObjectUploadResp struct { } func (c *ObjectService) Upload(req ObjectUpload) (*ObjectUploadResp, error) { - url, err := url.JoinPath(c.baseURL, ObjectUploadPath) + type uploadInfo struct { + Info string `url:"info"` + } + + url, err := url.JoinPath(c.cfg.URL, ObjectUploadPath) if err != nil { return nil, err } @@ -134,16 +122,15 @@ func (c *ObjectService) Upload(req ObjectUpload) (*ObjectUploadResp, error) { return nil, fmt.Errorf("upload info to json: %w", err) } - resp, err := http2.PostMultiPart(url, http2.MultiPartRequestParam{ - Form: map[string]string{"info": string(infoJSON)}, - Files: iterator.Map(req.Files, func(src *UploadingObject) (*http2.IterMultiPartFile, error) { + resp, err := PostMultiPart(c.cfg, url, + uploadInfo{Info: string(infoJSON)}, + iterator.Map(req.Files, func(src *UploadingObject) (*http2.IterMultiPartFile, error) { return &http2.IterMultiPartFile{ FieldName: "files", FileName: src.Path, File: src.File, }, nil - }), - }) + })) if err != nil { return nil, err } @@ -170,25 +157,42 @@ func (c *ObjectService) Upload(req ObjectUpload) (*ObjectUploadResp, error) { const ObjectDownloadPath = "/object/download" type ObjectDownload struct { - UserID cdssdk.UserID `form:"userID" json:"userID" binding:"required"` - ObjectID cdssdk.ObjectID `form:"objectID" json:"objectID" binding:"required"` - Offset int64 `form:"offset" json:"offset,omitempty"` - Length *int64 `form:"length" json:"length,omitempty"` + UserID cdssdk.UserID `form:"userID" url:"userID" binding:"required"` + ObjectID cdssdk.ObjectID `form:"objectID" url:"objectID" binding:"required"` + Offset int64 `form:"offset" url:"offset,omitempty"` + Length *int64 `form:"length" url:"length,omitempty"` +} + +func (r *ObjectDownload) MakeParam() *sdks.RequestParam { + return sdks.MakeQueryParam(http.MethodGet, ObjectDownloadPath, r) } + type DownloadingObject struct { Path string File io.ReadCloser } func (c *ObjectService) Download(req ObjectDownload) (*DownloadingObject, error) { - url, err := url.JoinPath(c.baseURL, ObjectDownloadPath) + httpReq, err := req.MakeParam().MakeRequest(c.cfg.URL) if err != nil { return nil, err } - resp, err := http2.GetJSON(url, http2.RequestParam{ - Query: req, - }) + if c.cfg.AccessKey != "" && c.cfg.SecretKey != "" { + prod := credentials.NewStaticCredentialsProvider(c.cfg.AccessKey, c.cfg.SecretKey, "") + cred, err := prod.Retrieve(context.TODO()) + if err != nil { + return nil, err + } + + signer := v4.NewSigner() + err = signer.SignHTTP(context.Background(), cred, httpReq, "", AuthService, AuthRegion, time.Now()) + if err != nil { + return nil, err + } + } + + resp, err := http.DefaultClient.Do(httpReq) if err != nil { return nil, err } @@ -218,22 +222,38 @@ func (c *ObjectService) Download(req ObjectDownload) (*DownloadingObject, error) const ObjectDownloadByPathPath = "/object/downloadByPath" type ObjectDownloadByPath struct { - UserID cdssdk.UserID `form:"userID" json:"userID" binding:"required"` - PackageID cdssdk.PackageID `form:"packageID" json:"packageID" binding:"required"` - Path string `form:"path" json:"path" binding:"required"` - Offset int64 `form:"offset" json:"offset,omitempty"` - Length *int64 `form:"length" json:"length,omitempty"` + UserID cdssdk.UserID `form:"userID" url:"userID" binding:"required"` + PackageID cdssdk.PackageID `form:"packageID" url:"packageID" binding:"required"` + Path string `form:"path" url:"path" binding:"required"` + Offset int64 `form:"offset" url:"offset,omitempty"` + Length *int64 `form:"length" url:"length,omitempty"` +} + +func (r *ObjectDownloadByPath) MakeParam() *sdks.RequestParam { + return sdks.MakeQueryParam(http.MethodGet, ObjectDownloadByPathPath, r) } func (c *ObjectService) DownloadByPath(req ObjectDownloadByPath) (*DownloadingObject, error) { - url, err := url.JoinPath(c.baseURL, ObjectDownloadByPathPath) + httpReq, err := req.MakeParam().MakeRequest(c.cfg.URL) if err != nil { return nil, err } - resp, err := http2.GetJSON(url, http2.RequestParam{ - Query: req, - }) + if c.cfg.AccessKey != "" && c.cfg.SecretKey != "" { + prod := credentials.NewStaticCredentialsProvider(c.cfg.AccessKey, c.cfg.SecretKey, "") + cred, err := prod.Retrieve(context.TODO()) + if err != nil { + return nil, err + } + + signer := v4.NewSigner() + err = signer.SignHTTP(context.Background(), cred, httpReq, "", AuthService, AuthRegion, time.Now()) + if err != nil { + return nil, err + } + } + + resp, err := http.DefaultClient.Do(httpReq) if err != nil { return nil, err } @@ -276,33 +296,20 @@ type ObjectUpdateInfo struct { Updatings []UpdatingObject `json:"updatings" binding:"required"` } +func (r *ObjectUpdateInfo) MakeParam() *sdks.RequestParam { + return sdks.MakeJSONParam(http.MethodPost, ObjectUpdateInfoPath, r) +} + type ObjectUpdateInfoResp struct { Successes []cdssdk.ObjectID `json:"successes"` } -func (c *ObjectService) UpdateInfo(req ObjectUpdateInfo) (*ObjectUpdateInfoResp, error) { - url, err := url.JoinPath(c.baseURL, ObjectUpdateInfoPath) - if err != nil { - return nil, err - } - - resp, err := http2.PostJSON(url, http2.RequestParam{ - Body: req, - }) - if err != nil { - return nil, err - } - - jsonResp, err := ParseJSONResponse[response[ObjectUpdateInfoResp]](resp) - if err != nil { - return nil, err - } - - if jsonResp.Code == errorcode.OK { - return &jsonResp.Data, nil - } +func (r *ObjectUpdateInfoResp) ParseResponse(resp *http.Response) error { + return sdks.ParseCodeDataJSONResponse(resp, r) +} - return nil, jsonResp.ToError() +func (c *ObjectService) UpdateInfo(req ObjectUpdateInfo) (*ObjectUpdateInfoResp, error) { + return JSONAPI(c.cfg, http.DefaultClient, &req, &ObjectUpdateInfoResp{}) } const ObjectUpdateInfoByPathPath = "/object/updateInfoByPath" @@ -314,31 +321,18 @@ type ObjectUpdateInfoByPath struct { UpdateTime time.Time `json:"updateTime" binding:"required"` } -type ObjectUpdateInfoByPathResp struct{} - -func (c *ObjectService) UpdateInfoByPath(req ObjectUpdateInfoByPath) (*ObjectUpdateInfoByPathResp, error) { - url, err := url.JoinPath(c.baseURL, ObjectUpdateInfoByPathPath) - if err != nil { - return nil, err - } - - resp, err := http2.PostJSON(url, http2.RequestParam{ - Body: req, - }) - if err != nil { - return nil, err - } +func (r *ObjectUpdateInfoByPath) MakeParam() *sdks.RequestParam { + return sdks.MakeJSONParam(http.MethodPost, ObjectUpdateInfoByPathPath, r) +} - jsonResp, err := ParseJSONResponse[response[ObjectUpdateInfoByPathResp]](resp) - if err != nil { - return nil, err - } +type ObjectUpdateInfoByPathResp struct{} - if jsonResp.Code == errorcode.OK { - return &jsonResp.Data, nil - } +func (r *ObjectUpdateInfoByPathResp) ParseResponse(resp *http.Response) error { + return sdks.ParseCodeDataJSONResponse(resp, r) +} - return nil, jsonResp.ToError() +func (c *ObjectService) UpdateInfoByPath(req ObjectUpdateInfoByPath) (*ObjectUpdateInfoByPathResp, error) { + return JSONAPI(c.cfg, http.DefaultClient, &req, &ObjectUpdateInfoByPathResp{}) } const ObjectMovePath = "/object/move" @@ -359,33 +353,20 @@ type ObjectMove struct { Movings []MovingObject `json:"movings" binding:"required"` } +func (r *ObjectMove) MakeParam() *sdks.RequestParam { + return sdks.MakeJSONParam(http.MethodPost, ObjectMovePath, r) +} + type ObjectMoveResp struct { Successes []cdssdk.ObjectID `json:"successes"` } -func (c *ObjectService) Move(req ObjectMove) (*ObjectMoveResp, error) { - url, err := url.JoinPath(c.baseURL, ObjectMovePath) - if err != nil { - return nil, err - } - - resp, err := http2.PostJSON(url, http2.RequestParam{ - Body: req, - }) - if err != nil { - return nil, err - } - - jsonResp, err := ParseJSONResponse[response[ObjectMoveResp]](resp) - if err != nil { - return nil, err - } - - if jsonResp.Code == errorcode.OK { - return &jsonResp.Data, nil - } +func (r *ObjectMoveResp) ParseResponse(resp *http.Response) error { + return sdks.ParseCodeDataJSONResponse(resp, r) +} - return nil, jsonResp.ToError() +func (c *ObjectService) Move(req ObjectMove) (*ObjectMoveResp, error) { + return JSONAPI(c.cfg, http.DefaultClient, &req, &ObjectMoveResp{}) } const ObjectDeletePath = "/object/delete" @@ -395,31 +376,18 @@ type ObjectDelete struct { ObjectIDs []cdssdk.ObjectID `json:"objectIDs" binding:"required"` } -type ObjectDeleteResp struct{} - -func (c *ObjectService) Delete(req ObjectDelete) error { - url, err := url.JoinPath(c.baseURL, ObjectDeletePath) - if err != nil { - return err - } - - resp, err := http2.PostJSON(url, http2.RequestParam{ - Body: req, - }) - if err != nil { - return err - } +func (r *ObjectDelete) MakeParam() *sdks.RequestParam { + return sdks.MakeJSONParam(http.MethodPost, ObjectDeletePath, r) +} - jsonResp, err := ParseJSONResponse[response[ObjectDeleteResp]](resp) - if err != nil { - return err - } +type ObjectDeleteResp struct{} - if jsonResp.Code == errorcode.OK { - return nil - } +func (r *ObjectDeleteResp) ParseResponse(resp *http.Response) error { + return sdks.ParseCodeDataJSONResponse(resp, r) +} - return jsonResp.ToError() +func (c *ObjectService) Delete(req ObjectDelete) error { + return JSONAPINoData(c.cfg, http.DefaultClient, &req) } const ObjectDeleteByPathPath = "/object/deleteByPath" @@ -429,31 +397,19 @@ type ObjectDeleteByPath struct { PackageID cdssdk.PackageID `json:"packageID" binding:"required"` Path string `json:"path" binding:"required"` } -type ObjectDeleteByPathResp struct{} -func (c *ObjectService) DeleteByPath(req ObjectDeleteByPath) error { - url, err := url.JoinPath(c.baseURL, ObjectDeleteByPathPath) - if err != nil { - return err - } - - resp, err := http2.PostJSON(url, http2.RequestParam{ - Body: req, - }) - if err != nil { - return err - } +func (r *ObjectDeleteByPath) MakeParam() *sdks.RequestParam { + return sdks.MakeJSONParam(http.MethodPost, ObjectDeleteByPathPath, r) +} - jsonResp, err := ParseJSONResponse[response[ObjectDeleteByPathResp]](resp) - if err != nil { - return err - } +type ObjectDeleteByPathResp struct{} - if jsonResp.Code == errorcode.OK { - return nil - } +func (r *ObjectDeleteByPathResp) ParseResponse(resp *http.Response) error { + return sdks.ParseCodeDataJSONResponse(resp, r) +} - return jsonResp.ToError() +func (c *ObjectService) DeleteByPath(req ObjectDeleteByPath) error { + return JSONAPINoData(c.cfg, http.DefaultClient, &req) } const ObjectClonePath = "/object/clone" @@ -463,6 +419,10 @@ type ObjectClone struct { Clonings []CloningObject `json:"clonings" binding:"required"` } +func (r *ObjectClone) MakeParam() *sdks.RequestParam { + return sdks.MakeJSONParam(http.MethodPost, ObjectClonePath, r) +} + type CloningObject struct { ObjectID cdssdk.ObjectID `json:"objectID" binding:"required"` NewPath string `json:"newPath" binding:"required"` @@ -473,62 +433,136 @@ type ObjectCloneResp struct { Objects []*cdssdk.Object `json:"objects"` } -func (c *ObjectService) Clone(req ObjectClone) (*ObjectCloneResp, error) { - url, err := url.JoinPath(c.baseURL, ObjectClonePath) - if err != nil { - return nil, err - } - - resp, err := http2.PostJSON(url, http2.RequestParam{ - Body: req, - }) - if err != nil { - return nil, err - } - - jsonResp, err := ParseJSONResponse[response[ObjectCloneResp]](resp) - if err != nil { - return nil, err - } - - if jsonResp.Code == errorcode.OK { - return &jsonResp.Data, nil - } +func (r *ObjectCloneResp) ParseResponse(resp *http.Response) error { + return sdks.ParseCodeDataJSONResponse(resp, r) +} - return nil, jsonResp.ToError() +func (c *ObjectService) Clone(req ObjectClone) (*ObjectCloneResp, error) { + return JSONAPI(c.cfg, http.DefaultClient, &req, &ObjectCloneResp{}) } const ObjectGetPackageObjectsPath = "/object/getPackageObjects" type ObjectGetPackageObjects struct { - UserID cdssdk.UserID `form:"userID" json:"userID" binding:"required"` - PackageID cdssdk.PackageID `form:"packageID" json:"packageID" binding:"required"` + UserID cdssdk.UserID `form:"userID" url:"userID" binding:"required"` + PackageID cdssdk.PackageID `form:"packageID" url:"packageID" binding:"required"` +} + +func (r *ObjectGetPackageObjects) MakeParam() *sdks.RequestParam { + return sdks.MakeQueryParam(http.MethodGet, ObjectGetPackageObjectsPath, r) } + type ObjectGetPackageObjectsResp struct { Objects []cdssdk.Object `json:"objects"` } +func (r *ObjectGetPackageObjectsResp) ParseResponse(resp *http.Response) error { + return sdks.ParseCodeDataJSONResponse(resp, r) +} + func (c *ObjectService) GetPackageObjects(req ObjectGetPackageObjects) (*ObjectGetPackageObjectsResp, error) { - url, err := url.JoinPath(c.baseURL, ObjectGetPackageObjectsPath) + return JSONAPI(c.cfg, http.DefaultClient, &req, &ObjectGetPackageObjectsResp{}) +} + +const ObjectNewMultipartUploadPath = "/v1/object/newMultipartUpload" + +type ObjectNewMultipartUpload struct { + UserID cdssdk.UserID `json:"userID" binding:"required"` + PackageID cdssdk.PackageID `json:"packageID" binding:"required"` + Path string `json:"path" binding:"required"` +} + +func (r *ObjectNewMultipartUpload) MakeParam() *sdks.RequestParam { + return sdks.MakeJSONParam(http.MethodPost, ObjectNewMultipartUploadPath, r) +} + +type ObjectNewMultipartUploadResp struct { + Object cdssdk.Object `json:"object"` +} + +func (r *ObjectNewMultipartUploadResp) ParseResponse(resp *http.Response) error { + return sdks.ParseCodeDataJSONResponse(resp, r) +} + +func (c *ObjectService) NewMultipartUpload(req ObjectNewMultipartUpload) (*ObjectNewMultipartUploadResp, error) { + return JSONAPI(c.cfg, http.DefaultClient, &req, &ObjectNewMultipartUploadResp{}) +} + +const ObjectUploadPartPath = "/v1/object/uploadPart" + +type ObjectUploadPart struct { + ObjectUploadPartInfo + File io.ReadCloser `json:"-"` +} + +type ObjectUploadPartInfo struct { + UserID cdssdk.UserID `json:"userID" binding:"required"` + ObjectID cdssdk.ObjectID `json:"objectID" binding:"required"` + Index int `json:"index"` +} + +type ObjectUploadPartResp struct{} + +func (c *ObjectService) UploadPart(req ObjectUploadPart) (*ObjectUploadPartResp, error) { + url, err := url.JoinPath(c.cfg.URL, ObjectUploadPartPath) if err != nil { return nil, err } - resp, err := http2.GetForm(url, http2.RequestParam{ - Query: req, - }) + infoJSON, err := serder.ObjectToJSON(req) if err != nil { - return nil, err + return nil, fmt.Errorf("upload info to json: %w", err) } - jsonResp, err := ParseJSONResponse[response[ObjectGetPackageObjectsResp]](resp) + resp, err := http2.PostMultiPart(url, http2.MultiPartRequestParam{ + Form: map[string]string{"info": string(infoJSON)}, + Files: iterator.Array(&http2.IterMultiPartFile{ + FieldName: "file", + File: req.File, + }), + }) if err != nil { return nil, err } - if jsonResp.Code == errorcode.OK { - return &jsonResp.Data, nil + contType := resp.Header.Get("Content-Type") + if strings.Contains(contType, http2.ContentTypeJSON) { + var err error + var codeResp response[ObjectUploadPartResp] + if codeResp, err = serder.JSONToObjectStreamEx[response[ObjectUploadPartResp]](resp.Body); err != nil { + return nil, fmt.Errorf("parsing response: %w", err) + } + + if codeResp.Code == errorcode.OK { + return &codeResp.Data, nil + } + + return nil, codeResp.ToError() } - return nil, jsonResp.ToError() + return nil, fmt.Errorf("unknow response content type: %s", contType) +} + +const ObjectCompleteMultipartUploadPath = "/v1/object/completeMultipartUpload" + +type ObjectCompleteMultipartUpload struct { + UserID cdssdk.UserID `json:"userID" binding:"required"` + ObjectID cdssdk.ObjectID `json:"objectID" binding:"required"` + Indexes []int `json:"indexes" binding:"required"` +} + +func (r *ObjectCompleteMultipartUpload) MakeParam() *sdks.RequestParam { + return sdks.MakeJSONParam(http.MethodPost, ObjectCompleteMultipartUploadPath, r) +} + +type ObjectCompleteMultipartUploadResp struct { + Object cdssdk.Object `json:"object"` +} + +func (r *ObjectCompleteMultipartUploadResp) ParseResponse(resp *http.Response) error { + return sdks.ParseCodeDataJSONResponse(resp, r) +} + +func (c *ObjectService) CompleteMultipartUpload(req ObjectCompleteMultipartUpload) (*ObjectCompleteMultipartUploadResp, error) { + return JSONAPI(c.cfg, http.DefaultClient, &req, &ObjectCompleteMultipartUploadResp{}) } diff --git a/sdks/storage/cdsapi/package.go b/sdks/storage/cdsapi/package.go index e72ae3e..6c1d2c3 100644 --- a/sdks/storage/cdsapi/package.go +++ b/sdks/storage/cdsapi/package.go @@ -2,11 +2,12 @@ package cdsapi import ( "fmt" + "net/http" "net/url" - "strings" "gitlink.org.cn/cloudream/common/consts/errorcode" "gitlink.org.cn/cloudream/common/pkgs/iterator" + "gitlink.org.cn/cloudream/common/sdks" cdssdk "gitlink.org.cn/cloudream/common/sdks/storage" "gitlink.org.cn/cloudream/common/utils/http2" "gitlink.org.cn/cloudream/common/utils/serder" @@ -23,72 +24,48 @@ func (c *Client) Package() *PackageService { const PackageGetPath = "/package/get" type PackageGetReq struct { - UserID cdssdk.UserID `form:"userID" json:"userID" binding:"required"` - PackageID cdssdk.PackageID `form:"packageID" json:"packageID" binding:"required"` + UserID cdssdk.UserID `form:"userID" url:"userID" binding:"required"` + PackageID cdssdk.PackageID `form:"packageID" url:"packageID" binding:"required"` } + +func (r *PackageGetReq) MakeParam() *sdks.RequestParam { + return sdks.MakeQueryParam(http.MethodGet, PackageGetPath, r) +} + type PackageGetResp struct { cdssdk.Package } -func (c *PackageService) Get(req PackageGetReq) (*PackageGetResp, error) { - url, err := url.JoinPath(c.baseURL, PackageGetPath) - if err != nil { - return nil, err - } - - resp, err := http2.GetForm(url, http2.RequestParam{ - Query: req, - }) - if err != nil { - return nil, err - } - - codeResp, err := ParseJSONResponse[response[PackageGetResp]](resp) - if err != nil { - return nil, err - } - - if codeResp.Code == errorcode.OK { - return &codeResp.Data, nil - } +func (r *PackageGetResp) ParseResponse(resp *http.Response) error { + return sdks.ParseCodeDataJSONResponse(resp, r) +} - return nil, codeResp.ToError() +func (c *PackageService) Get(req PackageGetReq) (*PackageGetResp, error) { + return JSONAPI(c.cfg, http.DefaultClient, &req, &PackageGetResp{}) } const PackageGetByFullNamePath = "/package/getByFullName" type PackageGetByFullName struct { - UserID cdssdk.UserID `form:"userID" json:"userID" binding:"required"` - BucketName string `form:"bucketName" json:"bucketName" binding:"required"` - PackageName string `form:"packageName" json:"packageName" binding:"required"` + UserID cdssdk.UserID `form:"userID" url:"userID" binding:"required"` + BucketName string `form:"bucketName" url:"bucketName" binding:"required"` + PackageName string `form:"packageName" url:"packageName" binding:"required"` } + +func (r *PackageGetByFullName) MakeParam() *sdks.RequestParam { + return sdks.MakeQueryParam(http.MethodGet, PackageGetByFullNamePath, r) +} + type PackageGetByFullNameResp struct { Package cdssdk.Package `json:"package"` } -func (c *PackageService) GetByName(req PackageGetByFullName) (*PackageGetByFullNameResp, error) { - url, err := url.JoinPath(c.baseURL, PackageGetByFullNamePath) - if err != nil { - return nil, err - } - - resp, err := http2.GetForm(url, http2.RequestParam{ - Query: req, - }) - if err != nil { - return nil, err - } - - codeResp, err := ParseJSONResponse[response[PackageGetByFullNameResp]](resp) - if err != nil { - return nil, err - } - - if codeResp.Code == errorcode.OK { - return &codeResp.Data, nil - } +func (r *PackageGetByFullNameResp) ParseResponse(resp *http.Response) error { + return sdks.ParseCodeDataJSONResponse(resp, r) +} - return nil, codeResp.ToError() +func (c *PackageService) GetByName(req PackageGetByFullName) (*PackageGetByFullNameResp, error) { + return JSONAPI(c.cfg, http.DefaultClient, &req, &PackageGetByFullNameResp{}) } const PackageCreatePath = "/package/create" @@ -99,33 +76,20 @@ type PackageCreate struct { Name string `json:"name"` } +func (r *PackageCreate) MakeParam() *sdks.RequestParam { + return sdks.MakeJSONParam(http.MethodPost, PackageCreatePath, r) +} + type PackageCreateResp struct { Package cdssdk.Package `json:"package"` } -func (s *PackageService) Create(req PackageCreate) (*PackageCreateResp, error) { - url, err := url.JoinPath(s.baseURL, PackageCreatePath) - if err != nil { - return nil, err - } - - resp, err := http2.PostJSON(url, http2.RequestParam{ - Body: req, - }) - if err != nil { - return nil, err - } - - codeResp, err := ParseJSONResponse[response[PackageCreateResp]](resp) - if err != nil { - return nil, err - } - - if codeResp.Code == errorcode.OK { - return &codeResp.Data, nil - } +func (r *PackageCreateResp) ParseResponse(resp *http.Response) error { + return sdks.ParseCodeDataJSONResponse(resp, r) +} - return nil, codeResp.ToError() +func (s *PackageService) Create(req PackageCreate) (*PackageCreateResp, error) { + return JSONAPI(s.cfg, http.DefaultClient, &req, &PackageCreateResp{}) } const PackageCreateLoadPath = "/package/createLoad" @@ -147,7 +111,7 @@ type PackageCreateLoadResp struct { } func (c *PackageService) CreateLoad(req PackageCreateLoad) (*PackageCreateLoadResp, error) { - url, err := url.JoinPath(c.baseURL, PackageCreateLoadPath) + url, err := url.JoinPath(c.cfg.URL, PackageCreateLoadPath) if err != nil { return nil, err } @@ -157,16 +121,15 @@ func (c *PackageService) CreateLoad(req PackageCreateLoad) (*PackageCreateLoadRe return nil, fmt.Errorf("upload info to json: %w", err) } - resp, err := http2.PostMultiPart(url, http2.MultiPartRequestParam{ - Form: map[string]string{"info": string(infoJSON)}, - Files: iterator.Map(req.Files, func(src *UploadingObject) (*http2.IterMultiPartFile, error) { + resp, err := PostMultiPart(c.cfg, url, + map[string]string{"info": string(infoJSON)}, + iterator.Map(req.Files, func(src *UploadingObject) (*http2.IterMultiPartFile, error) { return &http2.IterMultiPartFile{ FieldName: "files", FileName: src.Path, File: src.File, }, nil - }), - }) + })) if err != nil { return nil, err } @@ -190,35 +153,18 @@ type PackageDelete struct { PackageID cdssdk.PackageID `json:"packageID" binding:"required"` } -func (c *PackageService) Delete(req PackageDelete) error { - url, err := url.JoinPath(c.baseURL, PackageDeletePath) - if err != nil { - return err - } - - resp, err := http2.PostJSON(url, http2.RequestParam{ - Body: req, - }) - if err != nil { - return err - } - - contType := resp.Header.Get("Content-Type") - - if strings.Contains(contType, http2.ContentTypeJSON) { - var codeResp response[any] - if err := serder.JSONToObjectStream(resp.Body, &codeResp); err != nil { - return fmt.Errorf("parsing response: %w", err) - } +func (r *PackageDelete) MakeParam() *sdks.RequestParam { + return sdks.MakeJSONParam(http.MethodPost, PackageDeletePath, r) +} - if codeResp.Code == errorcode.OK { - return nil - } +type PackageDeleteResp struct{} - return codeResp.ToError() - } +func (r *PackageDeleteResp) ParseResponse(resp *http.Response) error { + return sdks.ParseCodeDataJSONResponse(resp, r) +} - return fmt.Errorf("unknow response content type: %s", contType) +func (c *PackageService) Delete(req PackageDelete) error { + return JSONAPINoData(c.cfg, http.DefaultClient, &req) } const PackageClonePath = "/package/clone" @@ -230,102 +176,64 @@ type PackageClone struct { Name string `json:"name" binding:"required"` } +func (r *PackageClone) MakeParam() *sdks.RequestParam { + return sdks.MakeJSONParam(http.MethodPost, PackageClonePath, r) +} + type PackageCloneResp struct { Package cdssdk.Package `json:"package"` } -func (c *PackageService) Clone(req PackageClone) (*PackageCloneResp, error) { - url, err := url.JoinPath(c.baseURL, PackageClonePath) - if err != nil { - return nil, err - } - - resp, err := http2.PostJSON(url, http2.RequestParam{ - Body: req, - }) - if err != nil { - return nil, err - } - - codeResp, err := ParseJSONResponse[response[PackageCloneResp]](resp) - if err != nil { - return nil, err - } - - if codeResp.Code == errorcode.OK { - return &codeResp.Data, nil - } +func (r *PackageCloneResp) ParseResponse(resp *http.Response) error { + return sdks.ParseCodeDataJSONResponse(resp, r) +} - return nil, codeResp.ToError() +func (c *PackageService) Clone(req PackageClone) (*PackageCloneResp, error) { + return JSONAPI(c.cfg, http.DefaultClient, &req, &PackageCloneResp{}) } const PackageListBucketPackagesPath = "/package/listBucketPackages" type PackageListBucketPackages struct { - UserID cdssdk.UserID `form:"userID" json:"userID" binding:"required"` - BucketID cdssdk.BucketID `form:"bucketID" json:"bucketID" binding:"required"` + UserID cdssdk.UserID `form:"userID" url:"userID" binding:"required"` + BucketID cdssdk.BucketID `form:"bucketID" url:"bucketID" binding:"required"` +} + +func (r *PackageListBucketPackages) MakeParam() *sdks.RequestParam { + return sdks.MakeQueryParam(http.MethodGet, PackageListBucketPackagesPath, r) } type PackageListBucketPackagesResp struct { Packages []cdssdk.Package `json:"packages"` } -func (c *PackageService) ListBucketPackages(req PackageListBucketPackages) (*PackageListBucketPackagesResp, error) { - url, err := url.JoinPath(c.baseURL, PackageListBucketPackagesPath) - if err != nil { - return nil, err - } - - resp, err := http2.GetForm(url, http2.RequestParam{ - Query: req, - }) - if err != nil { - return nil, err - } - - codeResp, err := ParseJSONResponse[response[PackageListBucketPackagesResp]](resp) - if err != nil { - return nil, err - } - - if codeResp.Code == errorcode.OK { - return &codeResp.Data, nil - } +func (r *PackageListBucketPackagesResp) ParseResponse(resp *http.Response) error { + return sdks.ParseCodeDataJSONResponse(resp, r) +} - return nil, codeResp.ToError() +func (c *PackageService) ListBucketPackages(req PackageListBucketPackages) (*PackageListBucketPackagesResp, error) { + return JSONAPI(c.cfg, http.DefaultClient, &req, &PackageListBucketPackagesResp{}) } const PackageGetCachedStoragesPath = "/package/getCachedStorages" type PackageGetCachedStoragesReq struct { - PackageID cdssdk.PackageID `form:"packageID" json:"packageID" binding:"required"` - UserID cdssdk.UserID `form:"userID" json:"userID" binding:"required"` + PackageID cdssdk.PackageID `form:"packageID" url:"packageID" binding:"required"` + UserID cdssdk.UserID `form:"userID" url:"userID" binding:"required"` +} + +func (r *PackageGetCachedStoragesReq) MakeParam() *sdks.RequestParam { + return sdks.MakeQueryParam(http.MethodGet, PackageGetCachedStoragesPath, r) } type PackageGetCachedStoragesResp struct { cdssdk.PackageCachingInfo } -func (c *PackageService) GetCachedStorages(req PackageGetCachedStoragesReq) (*PackageGetCachedStoragesResp, error) { - url, err := url.JoinPath(c.baseURL, PackageGetCachedStoragesPath) - if err != nil { - return nil, err - } - resp, err := http2.GetJSON(url, http2.RequestParam{ - Query: req, - }) - if err != nil { - return nil, err - } - - codeResp, err := ParseJSONResponse[response[PackageGetCachedStoragesResp]](resp) - if err != nil { - return nil, err - } - - if codeResp.Code == errorcode.OK { - return &codeResp.Data, nil - } +func (r *PackageGetCachedStoragesResp) ParseResponse(resp *http.Response) error { + return sdks.ParseCodeDataJSONResponse(resp, r) +} - return nil, codeResp.ToError() +func (c *PackageService) GetCachedStorages(req PackageGetCachedStoragesReq) (*PackageGetCachedStoragesResp, error) { + return JSONAPI(c.cfg, http.DefaultClient, &req, &PackageGetCachedStoragesResp{}) } diff --git a/sdks/storage/cdsapi/presigned.go b/sdks/storage/cdsapi/presigned.go new file mode 100644 index 0000000..b357e71 --- /dev/null +++ b/sdks/storage/cdsapi/presigned.go @@ -0,0 +1,147 @@ +package cdsapi + +import ( + "context" + "fmt" + "net/http" + "net/url" + "time" + + v4 "github.com/aws/aws-sdk-go-v2/aws/signer/v4" + "github.com/aws/aws-sdk-go-v2/credentials" + "github.com/google/go-querystring/query" + cdssdk "gitlink.org.cn/cloudream/common/sdks/storage" +) + +type PresignedService struct { + *Client +} + +func (c *Client) Presigned() *PresignedService { + return &PresignedService{ + Client: c, + } +} + +const PresignedObjectDownloadByPathPath = "/v1/presigned/object/downloadByPath" + +type PresignedObjectDownloadByPath struct { + UserID cdssdk.UserID `form:"userID" url:"userID" binding:"required"` + PackageID cdssdk.PackageID `form:"packageID" url:"packageID" binding:"required"` + Path string `form:"path" url:"path" binding:"required"` + Offset int64 `form:"offset" url:"offset,omitempty"` + Length *int64 `form:"length" url:"length,omitempty"` +} + +func (c *PresignedService) ObjectDownloadByPath(req PresignedObjectDownloadByPath, expireIn int) (string, error) { + return c.presign(req, PresignedObjectDownloadByPathPath, http.MethodGet, expireIn) +} + +const PresignedObjectDownloadPath = "/v1/presigned/object/download" + +type PresignedObjectDownload struct { + UserID cdssdk.UserID `form:"userID" url:"userID" binding:"required"` + ObjectID cdssdk.ObjectID `form:"objectID" url:"objectID" binding:"required"` + Offset int64 `form:"offset" url:"offset,omitempty"` + Length *int64 `form:"length" url:"length,omitempty"` +} + +func (c *PresignedService) ObjectDownload(req PresignedObjectDownload, expireIn int) (string, error) { + return c.presign(req, PresignedObjectDownloadPath, http.MethodGet, expireIn) +} + +const PresignedObjectUploadPath = "/v1/presigned/object/upload" + +type PresignedObjectUpload struct { + UserID cdssdk.UserID `form:"userID" binding:"required" url:"userID"` + PackageID cdssdk.PackageID `form:"packageID" binding:"required" url:"packageID"` + Path string `form:"path" binding:"required" url:"path"` + Affinity cdssdk.StorageID `form:"affinity" url:"affinity,omitempty"` + LoadTo []cdssdk.StorageID `form:"loadTo" url:"loadTo,omitempty"` + LoadToPath []string `form:"loadToPath" url:"loadToPath,omitempty"` +} + +type PresignedObjectUploadResp struct { + Object cdssdk.Object `json:"object"` +} + +func (c *PresignedService) ObjectUpload(req PresignedObjectUpload, expireIn int) (string, error) { + return c.presign(req, PresignedObjectUploadPath, http.MethodPost, expireIn) +} + +const PresignedObjectNewMultipartUploadPath = "/v1/presigned/object/newMultipartUpload" + +type PresignedObjectNewMultipartUpload struct { + UserID cdssdk.UserID `form:"userID" binding:"required" url:"userID"` + PackageID cdssdk.PackageID `form:"packageID" binding:"required" url:"packageID"` + Path string `form:"path" binding:"required" url:"path"` +} + +type PresignedObjectNewMultipartUploadResp struct { + Object cdssdk.Object `json:"object"` +} + +func (c *PresignedService) ObjectNewMultipartUpload(req PresignedObjectNewMultipartUpload, expireIn int) (string, error) { + return c.presign(req, PresignedObjectNewMultipartUploadPath, http.MethodPost, expireIn) +} + +const PresignedObjectUploadPartPath = "/v1/presigned/object/uploadPart" + +type PresignedObjectUploadPart struct { + UserID cdssdk.UserID `form:"userID" binding:"required" url:"userID"` + ObjectID cdssdk.ObjectID `form:"objectID" binding:"required" url:"objectID"` + Index int `form:"index" binding:"required" url:"index"` +} + +type PresignedUploadPartResp struct{} + +func (c *PresignedService) ObjectUploadPart(req PresignedObjectUploadPart, expireIn int) (string, error) { + return c.presign(req, PresignedObjectUploadPartPath, http.MethodPost, expireIn) +} + +const PresignedObjectCompleteMultipartUploadPath = "/v1/presigned/object/completeMultipartUpload" + +type PresignedObjectCompleteMultipartUpload struct { + UserID cdssdk.UserID `form:"userID" binding:"required" url:"userID"` + ObjectID cdssdk.ObjectID `form:"objectID" binding:"required" url:"objectID"` + Indexes []int `form:"indexes" binding:"required" url:"indexes"` +} + +type PresignedObjectCompleteMultipartUploadResp struct { + Object cdssdk.Object `json:"object"` +} + +func (c *PresignedService) ObjectCompleteMultipartUpload(req PresignedObjectCompleteMultipartUpload, expireIn int) (string, error) { + return c.presign(req, PresignedObjectCompleteMultipartUploadPath, http.MethodPost, expireIn) +} + +func (c *PresignedService) presign(req any, path string, method string, expireIn int) (string, error) { + u, err := url.Parse(c.cfg.URL) + if err != nil { + return "", err + } + u = u.JoinPath(path) + + us, err := query.Values(req) + if err != nil { + return "", err + } + us.Add("X-Expires", fmt.Sprintf("%v", expireIn)) + + u.RawQuery = us.Encode() + + prod := credentials.NewStaticCredentialsProvider(c.cfg.AccessKey, c.cfg.SecretKey, "") + cred, err := prod.Retrieve(context.TODO()) + if err != nil { + return "", err + } + + r, err := http.NewRequest(method, u.String(), nil) + if err != nil { + return "", err + } + + signer := v4.NewSigner() + signedURL, _, err := signer.PresignHTTP(context.Background(), cred, r, "", AuthService, AuthRegion, time.Now()) + return signedURL, err +} diff --git a/sdks/storage/cdsapi/presigned_test.go b/sdks/storage/cdsapi/presigned_test.go new file mode 100644 index 0000000..f4071c9 --- /dev/null +++ b/sdks/storage/cdsapi/presigned_test.go @@ -0,0 +1,153 @@ +package cdsapi + +import ( + "testing" + + . "github.com/smartystreets/goconvey/convey" + "gitlink.org.cn/cloudream/common/pkgs/types" +) + +func Test_Presigned(t *testing.T) { + cli := NewClient(&Config{ + URL: "http://localhost:7890", + AccessKey: "123456", + SecretKey: "123456", + }) + + Convey("下载文件", t, func() { + pre := cli.Presigned() + url, err := pre.ObjectDownloadByPath(PresignedObjectDownloadByPath{ + UserID: 1, + PackageID: 3, + Path: "example.java", + Offset: 1, + Length: types.Ref(int64(100)), + }, 100) + So(err, ShouldEqual, nil) + t.Logf("url: %s", url) + }) + + Convey("上传文件", t, func() { + pre := cli.Presigned() + url, err := pre.ObjectUpload(PresignedObjectUpload{ + UserID: 1, + PackageID: 3, + Path: "example.java", + }, 100) + So(err, ShouldEqual, nil) + t.Logf("url: %s", url) + }) +} + +func Test_PresignedObjectDownloadByPath(t *testing.T) { + cli := NewClient(&Config{ + URL: "http://localhost:7890", + AccessKey: "123456", + SecretKey: "123456", + }) + + Convey("下载文件", t, func() { + pre := cli.Presigned() + url, err := pre.ObjectDownloadByPath(PresignedObjectDownloadByPath{ + UserID: 1, + PackageID: 3, + Path: "example.java", + // Offset: 1, + // Length: types.Ref(int64(100)), + }, 100) + So(err, ShouldEqual, nil) + t.Logf("url: %s", url) + }) +} + +func Test_PresignedObjectDownload(t *testing.T) { + cli := NewClient(&Config{ + URL: "http://localhost:7890", + AccessKey: "123456", + SecretKey: "123456", + }) + + Convey("下载文件", t, func() { + pre := cli.Presigned() + url, err := pre.ObjectDownload(PresignedObjectDownload{ + UserID: 1, + ObjectID: 1039, + // Offset: 1, + // Length: types.Ref(int64(100)), + }, 100) + So(err, ShouldEqual, nil) + t.Logf("url: %s", url) + }) +} + +func Test_PresignedObjectUpload(t *testing.T) { + cli := NewClient(&Config{ + URL: "http://localhost:7890", + }) + + Convey("上传文件", t, func() { + pre := cli.Presigned() + url, err := pre.ObjectUpload(PresignedObjectUpload{ + UserID: 1, + PackageID: 3, + Path: "example.java", + }, 100) + So(err, ShouldEqual, nil) + t.Logf("url: %s", url) + }) +} + +func Test_PresignedNewMultipartUpload(t *testing.T) { + cli := NewClient(&Config{ + URL: "http://localhost:7890", + }) + + Convey("启动分片上传", t, func() { + pre := cli.Presigned() + url, err := pre.ObjectNewMultipartUpload(PresignedObjectNewMultipartUpload{ + UserID: 1, + PackageID: 3, + Path: "example.java", + }, 600) + So(err, ShouldEqual, nil) + t.Logf("url: %s", url) + }) +} + +func Test_PresignedObjectUploadPart(t *testing.T) { + cli := NewClient(&Config{ + URL: "http://localhost:7890", + AccessKey: "123456", + SecretKey: "123456", + }) + + Convey("上传分片", t, func() { + pre := cli.Presigned() + url, err := pre.ObjectUploadPart(PresignedObjectUploadPart{ + UserID: 1, + ObjectID: 7, + Index: 3, + }, 600) + So(err, ShouldEqual, nil) + t.Logf("url: %s", url) + }) +} + +func Test_PresignedCompleteMultipartUpload(t *testing.T) { + cli := NewClient(&Config{ + URL: "http://localhost:7890", + AccessKey: "123456", + SecretKey: "123456", + }) + + Convey("合并分片", t, func() { + pre := cli.Presigned() + url, err := pre.ObjectCompleteMultipartUpload(PresignedObjectCompleteMultipartUpload{ + UserID: 1, + ObjectID: 7, + Indexes: []int{1, 2, 3}, + }, 600) + So(err, ShouldEqual, nil) + t.Logf("url: %s", url) + }) +} diff --git a/sdks/storage/cdsapi/signer.go b/sdks/storage/cdsapi/signer.go new file mode 100644 index 0000000..67e39e5 --- /dev/null +++ b/sdks/storage/cdsapi/signer.go @@ -0,0 +1,114 @@ +package cdsapi + +import ( + "bytes" + "context" + "crypto/sha256" + "encoding/hex" + "fmt" + "io" + "net/http" + "time" + + v4 "github.com/aws/aws-sdk-go-v2/aws/signer/v4" + "github.com/aws/aws-sdk-go-v2/credentials" +) + +const ( + AuthService = "jcs" + AuthRegion = "any" +) + +// 对一个请求进行签名,并将签名信息添加到请求头中。 +// +// 会读取请求体计算sha256哈希值。如果hash值已知,可以使用SignWithPayloadHash方法。 +func Sign(req *http.Request, accessKey, secretKey string) error { + prod := credentials.NewStaticCredentialsProvider(accessKey, secretKey, "") + cred, err := prod.Retrieve(context.TODO()) + if err != nil { + return err + } + + payloadHash := "" + if req.Body != nil { + data, err := io.ReadAll(req.Body) + if err != nil { + return err + } + req.Body.Close() + req.Body = io.NopCloser(bytes.NewReader(data)) + + hasher := sha256.New() + hasher.Write(data) + payloadHash = hex.EncodeToString(hasher.Sum(nil)) + } else { + hash := sha256.Sum256([]byte("")) + payloadHash = hex.EncodeToString(hash[:]) + } + + signer := v4.NewSigner() + err = signer.SignHTTP(context.Background(), cred, req, payloadHash, AuthService, AuthRegion, time.Now()) + if err != nil { + return err + } + + return nil +} + +// 对一个请求进行签名,不计算请求体的哈希,适合上传文件接口。 +func SignWithoutBody(req *http.Request, accessKey, secretKey string) error { + prod := credentials.NewStaticCredentialsProvider(accessKey, secretKey, "") + cred, err := prod.Retrieve(context.TODO()) + if err != nil { + return err + } + + signer := v4.NewSigner() + err = signer.SignHTTP(context.Background(), cred, req, "", AuthService, AuthRegion, time.Now()) + if err != nil { + return err + } + + return nil +} + +// 对一个请求进行签名,签名时使用指定的哈希值作为请求体的哈希值。 +// +// 参数payloadHash必须为sha256哈希值的16进制字符串,全小写。 +func SignWithPayloadHash(req *http.Request, payloadHash string, accessKey, secretKey string) error { + prod := credentials.NewStaticCredentialsProvider(accessKey, secretKey, "") + cred, err := prod.Retrieve(context.TODO()) + if err != nil { + return err + } + + signer := v4.NewSigner() + err = signer.SignHTTP(context.Background(), cred, req, payloadHash, AuthService, AuthRegion, time.Now()) + if err != nil { + return err + } + + return nil +} + +// 生成一个带签名的URL。 +// +// expiration为签名过期时间,单位为秒。 +// +// 签名时不会包含请求体的哈希值。注:不要设置任何额外的Header(除了自动添加的Host),以免签名校验不通过 +func Presign(req *http.Request, accessKey, secretKey string, expiration int) (string, error) { + query := req.URL.Query() + query.Add("X-Expires", fmt.Sprintf("%v", expiration)) + + req.URL.RawQuery = query.Encode() + + prod := credentials.NewStaticCredentialsProvider(accessKey, secretKey, "") + cred, err := prod.Retrieve(context.TODO()) + if err != nil { + return "", err + } + + signer := v4.NewSigner() + signedURL, _, err := signer.PresignHTTP(context.Background(), cred, req, "", AuthService, AuthRegion, time.Now()) + return signedURL, err +} diff --git a/sdks/storage/cdsapi/storage.go b/sdks/storage/cdsapi/storage.go index f45b234..d275432 100644 --- a/sdks/storage/cdsapi/storage.go +++ b/sdks/storage/cdsapi/storage.go @@ -1,14 +1,10 @@ package cdsapi import ( - "fmt" - "net/url" - "strings" + "net/http" - "gitlink.org.cn/cloudream/common/consts/errorcode" + "gitlink.org.cn/cloudream/common/sdks" cdssdk "gitlink.org.cn/cloudream/common/sdks/storage" - "gitlink.org.cn/cloudream/common/utils/http2" - "gitlink.org.cn/cloudream/common/utils/serder" ) const StorageLoadPackagePath = "/storage/loadPackage" @@ -19,31 +15,19 @@ type StorageLoadPackageReq struct { StorageID cdssdk.StorageID `json:"storageID" binding:"required"` RootPath string `json:"rootPath"` } + +func (r *StorageLoadPackageReq) MakeParam() *sdks.RequestParam { + return sdks.MakeJSONParam(http.MethodPost, StorageLoadPackagePath, r) +} + type StorageLoadPackageResp struct{} +func (r *StorageLoadPackageResp) ParseResponse(resp *http.Response) error { + return sdks.ParseCodeDataJSONResponse(resp, r) +} + func (c *Client) StorageLoadPackage(req StorageLoadPackageReq) (*StorageLoadPackageResp, error) { - url, err := url.JoinPath(c.baseURL, StorageLoadPackagePath) - if err != nil { - return nil, err - } - - resp, err := http2.PostJSON(url, http2.RequestParam{ - Body: req, - }) - if err != nil { - return nil, err - } - - codeResp, err := ParseJSONResponse[response[StorageLoadPackageResp]](resp) - if err != nil { - return nil, err - } - - if codeResp.Code == errorcode.OK { - return &codeResp.Data, nil - } - - return nil, codeResp.ToError() + return JSONAPI(c.cfg, http.DefaultClient, &req, &StorageLoadPackageResp{}) } const StorageCreatePackagePath = "/storage/createPackage" @@ -57,71 +41,41 @@ type StorageCreatePackageReq struct { StorageAffinity cdssdk.StorageID `json:"storageAffinity"` } +func (r *StorageCreatePackageReq) MakeParam() *sdks.RequestParam { + return sdks.MakeJSONParam(http.MethodPost, StorageCreatePackagePath, r) +} + type StorageCreatePackageResp struct { - PackageID cdssdk.PackageID `json:"packageID"` + Package cdssdk.Package `json:"package"` +} + +func (r *StorageCreatePackageResp) ParseResponse(resp *http.Response) error { + return sdks.ParseCodeDataJSONResponse(resp, r) } func (c *Client) StorageCreatePackage(req StorageCreatePackageReq) (*StorageCreatePackageResp, error) { - url, err := url.JoinPath(c.baseURL, StorageCreatePackagePath) - if err != nil { - return nil, err - } - - resp, err := http2.PostJSON(url, http2.RequestParam{ - Body: req, - }) - if err != nil { - return nil, err - } - - contType := resp.Header.Get("Content-Type") - if strings.Contains(contType, http2.ContentTypeJSON) { - var codeResp response[StorageCreatePackageResp] - if err := serder.JSONToObjectStream(resp.Body, &codeResp); err != nil { - return nil, fmt.Errorf("parsing response: %w", err) - } - - if codeResp.Code == errorcode.OK { - return &codeResp.Data, nil - } - - return nil, codeResp.ToError() - } - - return nil, fmt.Errorf("unknow response content type: %s", contType) + return JSONAPI(c.cfg, http.DefaultClient, &req, &StorageCreatePackageResp{}) } const StorageGetPath = "/storage/get" type StorageGet struct { - UserID cdssdk.UserID `form:"userID" json:"userID" binding:"required"` - StorageID cdssdk.StorageID `form:"storageID" json:"storageID" binding:"required"` + UserID cdssdk.UserID `form:"userID" url:"userID" binding:"required"` + StorageID cdssdk.StorageID `form:"storageID" url:"storageID" binding:"required"` +} + +func (r *StorageGet) MakeParam() *sdks.RequestParam { + return sdks.MakeQueryParam(http.MethodGet, StorageGetPath, r) } + type StorageGetResp struct { cdssdk.Storage } +func (r *StorageGetResp) ParseResponse(resp *http.Response) error { + return sdks.ParseCodeDataJSONResponse(resp, r) +} + func (c *Client) StorageGet(req StorageGet) (*StorageGetResp, error) { - url, err := url.JoinPath(c.baseURL, StorageGetPath) - if err != nil { - return nil, err - } - - resp, err := http2.GetForm(url, http2.RequestParam{ - Query: req, - }) - if err != nil { - return nil, err - } - - codeResp, err := ParseJSONResponse[response[StorageGetResp]](resp) - if err != nil { - return nil, err - } - - if codeResp.Code == errorcode.OK { - return &codeResp.Data, nil - } - - return nil, codeResp.ToError() + return JSONAPI(c.cfg, http.DefaultClient, &req, &StorageGetResp{}) } diff --git a/sdks/storage/cdsapi/storage_test.go b/sdks/storage/cdsapi/storage_test.go index edc1994..c61ad46 100644 --- a/sdks/storage/cdsapi/storage_test.go +++ b/sdks/storage/cdsapi/storage_test.go @@ -245,3 +245,59 @@ func Test_Cache(t *testing.T) { So(err, ShouldBeNil) }) } + +func Test_Sign(t *testing.T) { + Convey("签名接口", t, func() { + cli := NewClient(&Config{ + URL: "http://localhost:7890/v1", + AccessKey: "123456", + SecretKey: "123456", + }) + + fileData := make([]byte, 4096) + for i := 0; i < len(fileData); i++ { + fileData[i] = byte(i) + } + + pkgName := uuid.NewString() + createResp, err := cli.Package().Create(PackageCreate{ + UserID: 1, + BucketID: 1, + Name: pkgName, + }) + So(err, ShouldBeNil) + + _, err = cli.Object().Upload(ObjectUpload{ + ObjectUploadInfo: ObjectUploadInfo{ + UserID: 1, + PackageID: createResp.Package.PackageID, + }, + Files: iterator.Array( + &UploadingObject{ + Path: "abc/test", + File: io.NopCloser(bytes.NewBuffer(fileData)), + }, + &UploadingObject{ + Path: "test4", + File: io.NopCloser(bytes.NewBuffer(fileData)), + }, + ), + }) + So(err, ShouldBeNil) + + getResp, err := cli.Package().Get(PackageGetReq{ + UserID: 1, + PackageID: createResp.Package.PackageID, + }) + So(err, ShouldBeNil) + + So(getResp.PackageID, ShouldEqual, createResp.Package.PackageID) + So(getResp.Package.Name, ShouldEqual, pkgName) + + err = cli.Package().Delete(PackageDelete{ + UserID: 1, + PackageID: createResp.Package.PackageID, + }) + So(err, ShouldBeNil) + }) +} diff --git a/sdks/storage/cdsapi/user.go b/sdks/storage/cdsapi/user.go index 7397f89..2230451 100644 --- a/sdks/storage/cdsapi/user.go +++ b/sdks/storage/cdsapi/user.go @@ -1,11 +1,10 @@ package cdsapi import ( - "net/url" + "net/http" - "gitlink.org.cn/cloudream/common/consts/errorcode" + "gitlink.org.cn/cloudream/common/sdks" cdssdk "gitlink.org.cn/cloudream/common/sdks/storage" - "gitlink.org.cn/cloudream/common/utils/http2" ) const UserCreatePath = "/v1/user/create" @@ -13,32 +12,21 @@ const UserCreatePath = "/v1/user/create" type UserCreate struct { Name string `json:"name" binding:"required"` } + +func (r *UserCreate) MakeParam() *sdks.RequestParam { + return sdks.MakeJSONParam(http.MethodPost, UserCreatePath, r) +} + type UserCreateResp struct { User cdssdk.User `json:"user"` } -func (c *Client) UserCreate(req *UserCreate) (*UserCreateResp, error) { - url, err := url.JoinPath(c.baseURL, UserCreatePath) - if err != nil { - return nil, err - } - - resp, err := http2.PostJSON(url, http2.RequestParam{ - Body: req, - }) - if err != nil { - return nil, err - } - codeResp, err := ParseJSONResponse[response[UserCreateResp]](resp) - if err != nil { - return nil, err - } - - if codeResp.Code == errorcode.OK { - return &codeResp.Data, nil - } +func (r *UserCreateResp) ParseResponse(resp *http.Response) error { + return sdks.ParseCodeDataJSONResponse(resp, r) +} - return nil, codeResp.ToError() +func (c *Client) UserCreate(req *UserCreate) (*UserCreateResp, error) { + return JSONAPI(c.cfg, http.DefaultClient, req, &UserCreateResp{}) } const UserDeletePath = "/v1/user/delete" @@ -47,28 +35,16 @@ type UserDelete struct { UserID cdssdk.UserID `json:"userID" binding:"required"` } -type UserDeleteResp struct{} - -func (c *Client) UserDelete(req *UserDelete) error { - url, err := url.JoinPath(c.baseURL, UserDeletePath) - if err != nil { - return err - } +func (r *UserDelete) MakeParam() *sdks.RequestParam { + return sdks.MakeJSONParam(http.MethodPost, UserDeletePath, r) +} - resp, err := http2.PostJSON(url, http2.RequestParam{ - Body: req, - }) - if err != nil { - return err - } - codeResp, err := ParseJSONResponse[response[UserDeleteResp]](resp) - if err != nil { - return err - } +type UserDeleteResp struct{} - if codeResp.Code == errorcode.OK { - return nil - } +func (r *UserDeleteResp) ParseResponse(resp *http.Response) error { + return sdks.ParseCodeDataJSONResponse(resp, r) +} - return codeResp.ToError() +func (c *Client) UserDelete(req *UserDelete) error { + return JSONAPINoData(c.cfg, http.DefaultClient, req) } diff --git a/sdks/storage/cdsapi/utils.go b/sdks/storage/cdsapi/utils.go index 2ef1bc8..811c225 100644 --- a/sdks/storage/cdsapi/utils.go +++ b/sdks/storage/cdsapi/utils.go @@ -1,12 +1,19 @@ package cdsapi import ( + "crypto/sha256" + "encoding/hex" "fmt" "io" + "mime/multipart" "net/http" + ul "net/url" "path/filepath" "strings" + "github.com/google/go-querystring/query" + "gitlink.org.cn/cloudream/common/pkgs/iterator" + "gitlink.org.cn/cloudream/common/sdks" "gitlink.org.cn/cloudream/common/utils/http2" "gitlink.org.cn/cloudream/common/utils/math2" "gitlink.org.cn/cloudream/common/utils/serder" @@ -36,3 +43,155 @@ func ParseJSONResponse[TBody any](resp *http.Response) (TBody, error) { return ret, fmt.Errorf("unknow response content type: %s, status: %d, body(prefix): %s", contType, resp.StatusCode, strCont[:math2.Min(len(strCont), 200)]) } + +func JSONAPI[Resp sdks.APIResponse, Req sdks.APIRequest](cfg *Config, cli *http.Client, req Req, resp Resp) (Resp, error) { + + param := req.MakeParam() + + httpReq, err := param.MakeRequest(cfg.URL) + if err != nil { + return resp, err + } + + if cfg.AccessKey != "" && cfg.SecretKey != "" { + err = SignWithPayloadHash(httpReq, calcSha256(param.Body), cfg.AccessKey, cfg.SecretKey) + if err != nil { + return resp, err + } + } + + httpResp, err := cli.Do(httpReq) + if err != nil { + return resp, err + } + + err = resp.ParseResponse(httpResp) + return resp, err +} + +func JSONAPINoData[Req sdks.APIRequest](cfg *Config, cli *http.Client, req Req) error { + param := req.MakeParam() + + httpReq, err := param.MakeRequest(cfg.URL) + if err != nil { + return err + } + + if cfg.AccessKey != "" && cfg.SecretKey != "" { + err = SignWithPayloadHash(httpReq, calcSha256(param.Body), cfg.AccessKey, cfg.SecretKey) + if err != nil { + return err + } + } + + resp, err := cli.Do(httpReq) + if err != nil { + return err + } + + return sdks.ParseCodeDataJSONResponse(resp, any(nil)) +} + +func calcSha256(body sdks.RequestBody) string { + hasher := sha256.New() + switch body := body.(type) { + case *sdks.StringBody: + hasher.Write([]byte(body.Value)) + return hex.EncodeToString(hasher.Sum(nil)) + + case *sdks.BytesBody: + hasher.Write(body.Value) + return hex.EncodeToString(hasher.Sum(nil)) + + case *sdks.StreamBody: + return "" + + default: + hash := sha256.Sum256([]byte("")) + return hex.EncodeToString(hash[:]) + } +} + +func PostMultiPart(cfg *Config, url string, info any, files http2.MultiPartFileIterator) (*http.Response, error) { + req, err := http.NewRequest(http.MethodPost, url, nil) + if err != nil { + return nil, err + } + + pr, pw := io.Pipe() + muWriter := multipart.NewWriter(pw) + + req.Header.Set("Content-Type", fmt.Sprintf("%s;boundary=%s", http2.ContentTypeMultiPart, muWriter.Boundary())) + + writeResult := make(chan error, 1) + go func() { + writeResult <- func() error { + defer pw.Close() + defer muWriter.Close() + + if info != nil { + mp, err := query.Values(info) + if err != nil { + return fmt.Errorf("formValues object to map failed, err: %w", err) + } + + for k, v := range mp { + err := muWriter.WriteField(k, v[0]) + if err != nil { + return fmt.Errorf("write form field failed, err: %w", err) + } + } + } + + for { + file, err := files.MoveNext() + if err == iterator.ErrNoMoreItem { + break + } + if err != nil { + return fmt.Errorf("opening file: %w", err) + } + + err = sendFileOnePart(muWriter, file.FieldName, file.FileName, file.File) + file.File.Close() + if err != nil { + return err + } + } + + return nil + }() + }() + + req.Body = pr + + if cfg.AccessKey != "" && cfg.SecretKey != "" { + err = SignWithoutBody(req, cfg.AccessKey, cfg.SecretKey) + if err != nil { + return nil, err + } + } + + cli := http.Client{} + resp, err := cli.Do(req) + if err != nil { + return nil, err + } + + writeErr := <-writeResult + if writeErr != nil { + return nil, writeErr + } + + return resp, nil +} + +func sendFileOnePart(muWriter *multipart.Writer, fieldName, fileName string, file io.ReadCloser) error { + w, err := muWriter.CreateFormFile(fieldName, ul.PathEscape(fileName)) + if err != nil { + return fmt.Errorf("create form file failed, err: %w", err) + } + + _, err = io.Copy(w, file) + return err +} diff --git a/sdks/storage/filehash.go b/sdks/storage/filehash.go index d4f9df5..b60cc30 100644 --- a/sdks/storage/filehash.go +++ b/sdks/storage/filehash.go @@ -20,6 +20,7 @@ type FileHash string const ( FullHashPrefix = "Full" CompositeHashPrefix = "Comp" + EmptyHash = FileHash("Full0000000000000000000000000000000000000000000000000000000000000000") ) func (h *FileHash) GetPrefix() string { @@ -30,6 +31,12 @@ func (h *FileHash) GetHash() string { return string((*h)[4:]) } +// 由调用者保证Hash值有效 +func (h *FileHash) GetHashBytes() []byte { + bytes, _ := hex.DecodeString(h.GetHash()) + return bytes +} + func (h *FileHash) GetHashPrefix(len int) string { return string((*h)[4 : 4+len]) } diff --git a/sdks/storage/models.go b/sdks/storage/models.go index 7fcfc4d..293618a 100644 --- a/sdks/storage/models.go +++ b/sdks/storage/models.go @@ -40,6 +40,7 @@ var RedundancyUnion = serder.UseTypeUnionInternallyTagged(types.Ref(types.NewTyp (*ECRedundancy)(nil), (*LRCRedundancy)(nil), (*SegmentRedundancy)(nil), + (*MultipartUploadRedundancy)(nil), )), "type") type NoneRedundancy struct { @@ -229,6 +230,17 @@ func (b *SegmentRedundancy) CalcSegmentRange(start int64, end *int64) (segIdxSta return } +type MultipartUploadRedundancy struct { + serder.Metadata `union:"multipartUpload"` + Type string `json:"type"` +} + +func NewMultipartUploadRedundancy() *MultipartUploadRedundancy { + return &MultipartUploadRedundancy{ + Type: "multipartUpload", + } +} + type User struct { UserID UserID `gorm:"column:UserID; primaryKey; type:bigint" json:"userID"` Name string `gorm:"column:Name; type:varchar(255); not null" json:"name"` diff --git a/utils/io2/io.go b/utils/io2/io.go index 8a0d113..5107ae0 100644 --- a/utils/io2/io.go +++ b/utils/io2/io.go @@ -196,3 +196,17 @@ func DropWithBuf(str io.Reader, buf []byte) { } } } + +func ReadMost(str io.Reader, n int) ([]byte, error) { + buf := make([]byte, n) + n, err := io.ReadFull(str, buf) + if err == nil { + return buf, nil + } + + if err == io.EOF || err == io.ErrUnexpectedEOF { + return buf[:n], nil + } + + return buf[:n], err +} diff --git a/utils/serder/serder.go b/utils/serder/serder.go index c665201..cbc7fbb 100644 --- a/utils/serder/serder.go +++ b/utils/serder/serder.go @@ -6,6 +6,7 @@ import ( "fmt" "io" "reflect" + "strings" jsoniter "github.com/json-iterator/go" "github.com/mitchellh/mapstructure" @@ -79,6 +80,16 @@ func JSONToObjectStreamEx[T any](stream io.Reader) (T, error) { return ret, nil } +func JSONToObjectStreamExRaw(stream io.Reader, ret any) error { + dec := defaultAPI.NewDecoder(stream) + err := dec.Decode(ret) + if err != nil { + return err + } + + return nil +} + // 将对象转为JSON字符串。如果需要支持解析TypeUnion类型,则使用"Ex"结尾的同名函数。 // // 注:[]byte会被base64编码,如果要JSON内容要给外部解析,那么应该避免使用[]byte。 @@ -189,3 +200,79 @@ func ObjectToMap(obj any) (map[string]any, error) { } return mp, dec.Decode(obj) } + +// 1. 尝试解开所有引用 +// +// 2. nil值将会是空字符串 +func ObjectToMapString(obj any) (map[string]string, error) { + if obj == nil { + return make(map[string]string), nil + } + + v := reflect.ValueOf(obj) + for v.Kind() == reflect.Ptr { + v = v.Elem() + } + if !v.IsValid() { + return make(map[string]string), nil + } + if v.Kind() != reflect.Struct { + return nil, fmt.Errorf("type %v is not a struct", v.Type()) + } + + mp := make(map[string]string) + objectToMapString(v, mp) + return mp, nil +} + +func objectToMapString(val reflect.Value, mp map[string]string) { + typ := val.Type() + for i := 0; i < val.NumField(); i++ { + vf := val.Field(i) + tf := typ.Field(i) + if tf.Anonymous { + objectToMapString(vf, mp) + continue + } + + fieldName := tf.Name + + omitEmpty := false + jsonTag := tf.Tag.Get("json") + if jsonTag != "" { + tagParts := strings.Split(jsonTag, ",") + fieldName = strings.TrimSpace(tagParts[0]) + if len(tagParts) > 1 { + for _, tagPart := range tagParts[1:] { + tagPart = strings.TrimSpace(tagPart) + if tagPart == "omitempty" { + omitEmpty = true + } + } + } + } + + for vf.Kind() == reflect.Ptr { + vf = vf.Elem() + } + + if !vf.IsValid() { + if omitEmpty { + continue + } + + mp[fieldName] = "" + continue + } + + if vf.IsZero() && omitEmpty { + continue + } + + if vf.Kind() == reflect.Array { + + } + + mp[fieldName] = fmt.Sprintf("%v", vf) + } +} diff --git a/utils/serder/serder_test.go b/utils/serder/serder_test.go index abd7aef..e0d1f4c 100644 --- a/utils/serder/serder_test.go +++ b/utils/serder/serder_test.go @@ -694,3 +694,108 @@ func Test_ObjectToJSONEx4(t *testing.T) { So(ret[0].(*StCallback).Value, ShouldEqual, "called") }) } + +type StStringer struct { +} + +func (s StStringer) String() string { + return "StStringer" +} + +func Test_ObjectToMapString(t *testing.T) { + Convey("结构体", t, func() { + type StEmb struct { + IntRef *int `json:"intRef,omitempty"` + BoolRef **bool `json:"boolRef"` + StrRef *string `json:"strRef"` + } + + type St struct { + StEmb + Int int + Bool bool + Str string + StStringer *StStringer + } + + st := St{ + StEmb: StEmb{ + IntRef: types.Ref(123), + BoolRef: types.Ref(types.Ref(true)), + }, + Int: 456, + Bool: false, + Str: "test", + StStringer: &StStringer{}, + } + + mp, err := ObjectToMapString(st) + So(err, ShouldBeNil) + + So(mp["intRef"], ShouldEqual, "123") + So(mp["boolRef"], ShouldEqual, "true") + So(mp["strRef"], ShouldEqual, "") + So(mp["Int"], ShouldEqual, "456") + So(mp["Bool"], ShouldEqual, "false") + So(mp["Str"], ShouldEqual, "test") + So(mp["StStringer"], ShouldEqual, "StStringer") + }) + + Convey("结构体引用", t, func() { + type StEmb struct { + IntRef *int `json:"intRef,omitempty"` + BoolRef **bool `json:"boolRef"` + StrRef *string `json:"strRef"` + } + + type St struct { + StEmb + Int int + Bool bool + Str string + StStringer *StStringer + } + + st := St{ + StEmb: StEmb{ + IntRef: types.Ref(123), + BoolRef: types.Ref(types.Ref(true)), + }, + Int: 456, + Bool: false, + Str: "test", + StStringer: &StStringer{}, + } + + mp, err := ObjectToMapString(&st) + So(err, ShouldBeNil) + + So(mp["intRef"], ShouldEqual, "123") + So(mp["boolRef"], ShouldEqual, "true") + So(mp["strRef"], ShouldEqual, "") + So(mp["Int"], ShouldEqual, "456") + So(mp["Bool"], ShouldEqual, "false") + So(mp["Str"], ShouldEqual, "test") + So(mp["StStringer"], ShouldEqual, "StStringer") + }) + + Convey("nil", t, func() { + mp, err := ObjectToMapString(nil) + So(err, ShouldBeNil) + So(mp, ShouldResemble, map[string]string{}) + }) + + Convey("nil指针", t, func() { + type St struct{} + var st *St + mp, err := ObjectToMapString(st) + So(err, ShouldBeNil) + So(mp, ShouldResemble, map[string]string{}) + }) + + Convey("非结构体", t, func() { + mp, err := ObjectToMapString(123) + So(err, ShouldNotBeNil) + So(mp, ShouldBeNil) + }) +} diff --git a/utils/sync2/bucket_pool.go b/utils/sync2/bucket_pool.go index f5fa633..d3b4114 100644 --- a/utils/sync2/bucket_pool.go +++ b/utils/sync2/bucket_pool.go @@ -52,12 +52,12 @@ func (p *BucketPool[T]) GetFilled() (T, bool) { p.filledCond.L.Lock() defer p.filledCond.L.Unlock() - if p.closed { - var t T - return t, false - } - if len(p.filled) == 0 { + if p.closed { + var t T + return t, false + } + p.filledCond.Wait() }