You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

package.go 11 kB

7 months ago
7 months ago
7 months ago
7 months ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362
  1. package http
  2. import (
  3. "archive/tar"
  4. "archive/zip"
  5. "fmt"
  6. "io"
  7. "mime/multipart"
  8. "net/http"
  9. "net/url"
  10. "path/filepath"
  11. "github.com/gin-gonic/gin"
  12. "gitlink.org.cn/cloudream/common/consts/errorcode"
  13. "gitlink.org.cn/cloudream/common/pkgs/logger"
  14. "gitlink.org.cn/cloudream/jcs-pub/client/internal/downloader"
  15. "gitlink.org.cn/cloudream/jcs-pub/client/internal/http/types"
  16. cliapi "gitlink.org.cn/cloudream/jcs-pub/client/sdk/api/v1"
  17. clitypes "gitlink.org.cn/cloudream/jcs-pub/client/types"
  18. "gitlink.org.cn/cloudream/jcs-pub/common/ecode"
  19. "gitlink.org.cn/cloudream/jcs-pub/common/pkgs/iterator"
  20. "gorm.io/gorm"
  21. )
  22. // PackageService 包服务,负责处理包相关的HTTP请求。
  23. type PackageService struct {
  24. *Server
  25. }
  26. // Package 返回PackageService的实例。
  27. func (s *Server) Package() *PackageService {
  28. return &PackageService{
  29. Server: s,
  30. }
  31. }
  32. func (s *PackageService) Get(ctx *gin.Context) {
  33. log := logger.WithField("HTTP", "Package.Get")
  34. var req cliapi.PackageGet
  35. if err := ctx.ShouldBindQuery(&req); err != nil {
  36. log.Warnf("binding body: %s", err.Error())
  37. ctx.JSON(http.StatusBadRequest, types.Failed(ecode.BadArgument, "missing argument or invalid argument"))
  38. return
  39. }
  40. pkg, err := s.svc.DB.Package().GetByID(s.svc.DB.DefCtx(), req.PackageID)
  41. if err != nil {
  42. log.Warnf("getting package: %s", err.Error())
  43. if err == gorm.ErrRecordNotFound {
  44. ctx.JSON(http.StatusOK, types.Failed(ecode.DataNotFound, "package not found"))
  45. return
  46. }
  47. ctx.JSON(http.StatusOK, types.FailedError(err))
  48. return
  49. }
  50. ctx.JSON(http.StatusOK, types.OK(cliapi.PackageGetResp{Package: pkg}))
  51. }
  52. func (s *PackageService) GetByFullName(ctx *gin.Context) {
  53. log := logger.WithField("HTTP", "Package.GetByFullName")
  54. var req cliapi.PackageGetByFullName
  55. if err := ctx.ShouldBindQuery(&req); err != nil {
  56. log.Warnf("binding query: %s", err.Error())
  57. ctx.JSON(http.StatusBadRequest, types.Failed(ecode.BadArgument, "missing argument or invalid argument"))
  58. return
  59. }
  60. pkg, err := s.svc.DB.Package().GetByFullName(s.svc.DB.DefCtx(), req.BucketName, req.PackageName)
  61. if err != nil {
  62. log.Warnf("getting package by name: %s", err.Error())
  63. if err == gorm.ErrRecordNotFound {
  64. ctx.JSON(http.StatusOK, types.Failed(ecode.DataNotFound, "package not found"))
  65. return
  66. }
  67. ctx.JSON(http.StatusOK, types.FailedError(err))
  68. return
  69. }
  70. ctx.JSON(http.StatusOK, types.OK(cliapi.PackageGetByFullNameResp{Package: pkg}))
  71. }
  72. // Create 处理创建新包的HTTP请求。
  73. func (s *PackageService) Create(ctx *gin.Context) {
  74. log := logger.WithField("HTTP", "Package.Create")
  75. var req cliapi.PackageCreate
  76. if err := ctx.ShouldBindJSON(&req); err != nil {
  77. log.Warnf("binding body: %s", err.Error())
  78. ctx.JSON(http.StatusBadRequest, types.Failed(ecode.BadArgument, "missing argument or invalid argument"))
  79. return
  80. }
  81. pkg, err := s.svc.PackageSvc().Create(req.BucketID, req.Name)
  82. if err != nil {
  83. log.Warnf("creating package: %s", err.Error())
  84. ctx.JSON(http.StatusOK, types.FailedError(err))
  85. return
  86. }
  87. ctx.JSON(http.StatusOK, types.OK(cliapi.PackageCreateResp{
  88. Package: pkg,
  89. }))
  90. }
  91. type PackageCreateUpload struct {
  92. Info cliapi.PackageCreateUploadInfo `form:"info" binding:"required"`
  93. Files []*multipart.FileHeader `form:"files"`
  94. }
  95. func (s *PackageService) CreateLoad(ctx *gin.Context) {
  96. log := logger.WithField("HTTP", "Package.CreateUpload")
  97. var req PackageCreateUpload
  98. if err := ctx.ShouldBind(&req); err != nil {
  99. log.Warnf("binding body: %s", err.Error())
  100. ctx.JSON(http.StatusBadRequest, types.Failed(ecode.BadArgument, "missing argument or invalid argument"))
  101. return
  102. }
  103. if len(req.Info.CopyTo) != len(req.Info.CopyToPath) {
  104. log.Warnf("CopyTo and CopyToPath count not match")
  105. ctx.JSON(http.StatusOK, types.Failed(ecode.BadArgument, "CopyTo and CopyToPath count not match"))
  106. return
  107. }
  108. copyToPath := make([]clitypes.JPath, 0, len(req.Info.CopyToPath))
  109. for _, p := range req.Info.CopyToPath {
  110. copyToPath = append(copyToPath, clitypes.PathFromJcsPathString(p))
  111. }
  112. up, err := s.svc.Uploader.BeginCreateUpload(req.Info.BucketID, req.Info.Name, req.Info.CopyTo, copyToPath)
  113. if err != nil {
  114. log.Warnf("begin package create upload: %s", err.Error())
  115. ctx.JSON(http.StatusOK, types.Failed(ecode.OperationFailed, "%v", err))
  116. return
  117. }
  118. defer up.Abort()
  119. var pathes []string
  120. for _, file := range req.Files {
  121. f, err := file.Open()
  122. if err != nil {
  123. log.Warnf("open file: %s", err.Error())
  124. ctx.JSON(http.StatusOK, types.Failed(ecode.OperationFailed, fmt.Sprintf("open file %v: %v", file.Filename, err)))
  125. return
  126. }
  127. path, err := url.PathUnescape(file.Filename)
  128. if err != nil {
  129. log.Warnf("unescape filename: %s", err.Error())
  130. ctx.JSON(http.StatusOK, types.Failed(ecode.OperationFailed, fmt.Sprintf("unescape filename %v: %v", file.Filename, err)))
  131. return
  132. }
  133. path = filepath.ToSlash(path)
  134. err = up.Upload(clitypes.PathFromJcsPathString(path), f)
  135. if err != nil {
  136. log.Warnf("uploading file: %s", err.Error())
  137. ctx.JSON(http.StatusOK, types.Failed(ecode.OperationFailed, fmt.Sprintf("uploading file %v: %v", file.Filename, err)))
  138. return
  139. }
  140. pathes = append(pathes, path)
  141. }
  142. ret, err := up.Commit()
  143. if err != nil {
  144. log.Warnf("commit create upload: %s", err.Error())
  145. ctx.JSON(http.StatusOK, types.Failed(ecode.OperationFailed, fmt.Sprintf("commit create upload: %v", err)))
  146. return
  147. }
  148. objs := make([]clitypes.Object, len(pathes))
  149. for i := range pathes {
  150. objs[i] = ret.Objects[pathes[i]]
  151. }
  152. ctx.JSON(http.StatusOK, types.OK(cliapi.PackageCreateUploadResp{Package: ret.Package, Objects: objs}))
  153. }
  154. func (s *PackageService) Download(ctx *gin.Context) {
  155. log := logger.WithField("HTTP", "Package.Download")
  156. var req cliapi.PackageDownload
  157. if err := ctx.ShouldBindQuery(&req); err != nil {
  158. log.Warnf("binding query: %s", err.Error())
  159. ctx.JSON(http.StatusBadRequest, types.Failed(errorcode.BadArgument, "missing argument or invalid argument"))
  160. return
  161. }
  162. pkg, iter, err := s.svc.Downloader.DownloadPackage(req.PackageID, req.Prefix)
  163. if err != nil {
  164. log.Warnf("downloading package: %s", err.Error())
  165. ctx.JSON(http.StatusOK, types.Failed(errorcode.OperationFailed, err.Error()))
  166. return
  167. }
  168. defer iter.Close()
  169. if req.Zip {
  170. s.downloadZip(ctx, req, pkg, iter)
  171. } else {
  172. s.downloadTar(ctx, req, pkg, iter)
  173. }
  174. }
  175. func (s *PackageService) downloadZip(ctx *gin.Context, req cliapi.PackageDownload, pkg clitypes.Package, iter downloader.DownloadIterator) {
  176. log := logger.WithField("HTTP", "Package.Download")
  177. ctx.Header("Content-Disposition", "attachment; filename="+url.PathEscape(pkg.Name)+".zip")
  178. ctx.Header("Content-Type", "application/zip")
  179. ctx.Header("Content-Transfer-Encoding", "binary")
  180. zipFile := zip.NewWriter(ctx.Writer)
  181. defer zipFile.Close()
  182. for {
  183. item, err := iter.MoveNext()
  184. if err == iterator.ErrNoMoreItem {
  185. return
  186. }
  187. if err != nil {
  188. log.Warnf("iterating next object: %v", err)
  189. return
  190. }
  191. filePath := item.Object.Path
  192. if req.Prefix != "" && req.NewPrefix != nil {
  193. filePath = *req.NewPrefix + filePath[len(req.Prefix):]
  194. }
  195. zf, err := zipFile.Create(filePath)
  196. if err != nil {
  197. log.Warnf("creating zip file: %v", err)
  198. item.File.Close()
  199. return
  200. }
  201. _, err = io.Copy(zf, item.File)
  202. if err != nil {
  203. log.Warnf("copying file to zip: %v", err)
  204. item.File.Close()
  205. return
  206. }
  207. item.File.Close()
  208. }
  209. }
  210. func (s *PackageService) downloadTar(ctx *gin.Context, req cliapi.PackageDownload, pkg clitypes.Package, iter downloader.DownloadIterator) {
  211. log := logger.WithField("HTTP", "Package.Download")
  212. ctx.Header("Content-Disposition", "attachment; filename="+url.PathEscape(pkg.Name)+".tar")
  213. ctx.Header("Content-Type", "application/x-tar")
  214. ctx.Header("Content-Transfer-Encoding", "binary")
  215. tarFile := tar.NewWriter(ctx.Writer)
  216. defer tarFile.Close()
  217. for {
  218. item, err := iter.MoveNext()
  219. if err == iterator.ErrNoMoreItem {
  220. return
  221. }
  222. if err != nil {
  223. log.Warnf("iterating next object: %v", err)
  224. return
  225. }
  226. filePath := item.Object.Path
  227. if req.Prefix != "" && req.NewPrefix != nil {
  228. filePath = *req.NewPrefix + filePath[len(req.Prefix):]
  229. }
  230. err = tarFile.WriteHeader(&tar.Header{
  231. Typeflag: tar.TypeReg,
  232. Name: filePath,
  233. Size: item.Object.Size,
  234. ModTime: item.Object.CreateTime,
  235. })
  236. if err != nil {
  237. log.Warnf("creating tar header: %v", err)
  238. item.File.Close()
  239. return
  240. }
  241. _, err = io.Copy(tarFile, item.File)
  242. if err != nil {
  243. log.Warnf("copying file to tar: %v", err)
  244. item.File.Close()
  245. return
  246. }
  247. item.File.Close()
  248. }
  249. }
  250. func (s *PackageService) Delete(ctx *gin.Context) {
  251. log := logger.WithField("HTTP", "Package.Delete")
  252. var req cliapi.PackageDelete
  253. if err := ctx.ShouldBindJSON(&req); err != nil {
  254. log.Warnf("binding body: %s", err.Error())
  255. ctx.JSON(http.StatusBadRequest, types.Failed(ecode.BadArgument, "missing argument or invalid argument"))
  256. return
  257. }
  258. err := s.svc.PackageSvc().DeletePackage(req.PackageID)
  259. if err != nil {
  260. log.Warnf("deleting package: %s", err.Error())
  261. ctx.JSON(http.StatusOK, types.Failed(ecode.OperationFailed, "delete package failed"))
  262. return
  263. }
  264. ctx.JSON(http.StatusOK, types.OK(nil))
  265. }
  266. func (s *PackageService) Clone(ctx *gin.Context) {
  267. log := logger.WithField("HTTP", "Package.Clone")
  268. var req cliapi.PackageClone
  269. if err := ctx.ShouldBindJSON(&req); err != nil {
  270. log.Warnf("binding body: %s", err.Error())
  271. ctx.JSON(http.StatusBadRequest, types.Failed(ecode.BadArgument, "missing argument or invalid argument"))
  272. return
  273. }
  274. pkg, err := s.svc.PackageSvc().Clone(req.PackageID, req.BucketID, req.Name)
  275. if err != nil {
  276. log.Warnf("cloning package: %s", err.Error())
  277. ctx.JSON(http.StatusOK, types.Failed(ecode.OperationFailed, "clone package failed"))
  278. return
  279. }
  280. ctx.JSON(http.StatusOK, types.OK(cliapi.PackageCloneResp{
  281. Package: pkg,
  282. }))
  283. }
  284. func (s *PackageService) ListBucketPackages(ctx *gin.Context) {
  285. log := logger.WithField("HTTP", "Package.ListBucketPackages")
  286. var req cliapi.PackageListBucketPackages
  287. if err := ctx.ShouldBindQuery(&req); err != nil {
  288. log.Warnf("binding query: %s", err.Error())
  289. ctx.JSON(http.StatusBadRequest, types.Failed(ecode.BadArgument, "missing argument or invalid argument"))
  290. return
  291. }
  292. pkgs, err := s.svc.PackageSvc().GetBucketPackages(req.BucketID)
  293. if err != nil {
  294. log.Warnf("getting bucket packages: %s", err.Error())
  295. ctx.JSON(http.StatusOK, types.Failed(ecode.OperationFailed, "get bucket packages failed"))
  296. return
  297. }
  298. ctx.JSON(http.StatusOK, types.OK(cliapi.PackageListBucketPackagesResp{
  299. Packages: pkgs,
  300. }))
  301. }

本项目旨在将云际存储公共基础设施化,使个人及企业可低门槛使用高效的云际存储服务(安装开箱即用云际存储客户端即可,无需关注其他组件的部署),同时支持用户灵活便捷定制云际存储的功能细节。