diff --git a/docs/docs.go b/docs/docs.go index db216bf..8cbf592 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -291,8 +291,8 @@ const docTemplate = `{ "summary": "获取批次详情", "parameters": [ { - "type": "integer", - "description": "批次 ID", + "type": "string", + "description": "批次 ID (UUID)", "name": "batch_id", "in": "path", "required": true @@ -344,8 +344,8 @@ const docTemplate = `{ "summary": "修改批次信息", "parameters": [ { - "type": "integer", - "description": "批次 ID", + "type": "string", + "description": "批次 ID (UUID)", "name": "batch_id", "in": "path", "required": true @@ -403,8 +403,8 @@ const docTemplate = `{ "summary": "删除批次", "parameters": [ { - "type": "integer", - "description": "批次 ID", + "type": "string", + "description": "批次 ID (UUID)", "name": "batch_id", "in": "path", "required": true @@ -710,8 +710,8 @@ const docTemplate = `{ "summary": "下载单个文件", "parameters": [ { - "type": "integer", - "description": "文件 ID", + "type": "string", + "description": "文件 ID (UUID)", "name": "file_id", "in": "path", "required": true @@ -883,7 +883,7 @@ const docTemplate = `{ } }, "id": { - "type": "integer" + "type": "string" }, "max_downloads": { "type": "integer" @@ -911,13 +911,13 @@ const docTemplate = `{ "type": "object", "properties": { "batch_id": { - "type": "integer" + "type": "string" }, "created_at": { "type": "string" }, "id": { - "type": "integer" + "type": "string" }, "mime_type": { "type": "string" @@ -983,7 +983,7 @@ const docTemplate = `{ "type": "object", "properties": { "batch_id": { - "type": "integer" + "type": "string" }, "expire_at": { "type": "string" diff --git a/docs/swagger.json b/docs/swagger.json index 024dc39..db38483 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -284,8 +284,8 @@ "summary": "获取批次详情", "parameters": [ { - "type": "integer", - "description": "批次 ID", + "type": "string", + "description": "批次 ID (UUID)", "name": "batch_id", "in": "path", "required": true @@ -337,8 +337,8 @@ "summary": "修改批次信息", "parameters": [ { - "type": "integer", - "description": "批次 ID", + "type": "string", + "description": "批次 ID (UUID)", "name": "batch_id", "in": "path", "required": true @@ -396,8 +396,8 @@ "summary": "删除批次", "parameters": [ { - "type": "integer", - "description": "批次 ID", + "type": "string", + "description": "批次 ID (UUID)", "name": "batch_id", "in": "path", "required": true @@ -703,8 +703,8 @@ "summary": "下载单个文件", "parameters": [ { - "type": "integer", - "description": "文件 ID", + "type": "string", + "description": "文件 ID (UUID)", "name": "file_id", "in": "path", "required": true @@ -876,7 +876,7 @@ } }, "id": { - "type": "integer" + "type": "string" }, "max_downloads": { "type": "integer" @@ -904,13 +904,13 @@ "type": "object", "properties": { "batch_id": { - "type": "integer" + "type": "string" }, "created_at": { "type": "string" }, "id": { - "type": "integer" + "type": "string" }, "mime_type": { "type": "string" @@ -976,7 +976,7 @@ "type": "object", "properties": { "batch_id": { - "type": "integer" + "type": "string" }, "expire_at": { "type": "string" diff --git a/docs/swagger.yaml b/docs/swagger.yaml index fbbf810..679924e 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -94,7 +94,7 @@ definitions: $ref: '#/definitions/model.FileItem' type: array id: - type: integer + type: string max_downloads: type: integer pickup_code: @@ -113,11 +113,11 @@ definitions: model.FileItem: properties: batch_id: - type: integer + type: string created_at: type: string id: - type: integer + type: string mime_type: type: string original_name: @@ -161,7 +161,7 @@ definitions: public.UploadResponse: properties: batch_id: - type: integer + type: string expire_at: type: string pickup_code: @@ -353,11 +353,11 @@ paths: delete: description: 标记批次为已删除,并物理删除关联的存储文件 parameters: - - description: 批次 ID + - description: 批次 ID (UUID) in: path name: batch_id required: true - type: integer + type: string produces: - application/json responses: @@ -377,11 +377,11 @@ paths: get: description: 根据批次 ID 获取批次信息及关联的文件列表 parameters: - - description: 批次 ID + - description: 批次 ID (UUID) in: path name: batch_id required: true - type: integer + type: string produces: - application/json responses: @@ -408,11 +408,11 @@ paths: - application/json description: 允许修改备注、过期策略、最大下载次数、状态等 parameters: - - description: 批次 ID + - description: 批次 ID (UUID) in: path name: batch_id required: true - type: integer + type: string - description: 修改内容 in: body name: request @@ -611,11 +611,11 @@ paths: get: description: 根据文件 ID 下载单个文件 parameters: - - description: 文件 ID + - description: 文件 ID (UUID) in: path name: file_id required: true - type: integer + type: string produces: - application/octet-stream responses: diff --git a/internal/api/admin/batch.go b/internal/api/admin/batch.go index e3e055f..4f78015 100644 --- a/internal/api/admin/batch.go +++ b/internal/api/admin/batch.go @@ -93,7 +93,7 @@ func (h *BatchHandler) ListBatches(c *gin.Context) { // @Description 根据批次 ID 获取批次信息及关联的文件列表 // @Tags Admin // @Security AdminAuth -// @Param batch_id path int true "批次 ID" +// @Param batch_id path string true "批次 ID (UUID)" // @Produce json // @Success 200 {object} model.Response{data=model.FileBatch} // @Failure 404 {object} model.Response @@ -101,7 +101,7 @@ func (h *BatchHandler) ListBatches(c *gin.Context) { func (h *BatchHandler) GetBatch(c *gin.Context) { id := c.Param("batch_id") var batch model.FileBatch - if err := bootstrap.DB.Preload("FileItems").First(&batch, id).Error; err != nil { + if err := bootstrap.DB.Preload("FileItems").First(&batch, "id = ?", id).Error; err != nil { c.JSON(http.StatusNotFound, model.ErrorResponse(model.CodeNotFound, "batch not found")) return } @@ -115,7 +115,7 @@ func (h *BatchHandler) GetBatch(c *gin.Context) { // @Security AdminAuth // @Accept json // @Produce json -// @Param batch_id path int true "批次 ID" +// @Param batch_id path string true "批次 ID (UUID)" // @Param request body UpdateBatchRequest true "修改内容" // @Success 200 {object} model.Response{data=model.FileBatch} // @Failure 400 {object} model.Response @@ -123,7 +123,7 @@ func (h *BatchHandler) GetBatch(c *gin.Context) { func (h *BatchHandler) UpdateBatch(c *gin.Context) { id := c.Param("batch_id") var batch model.FileBatch - if err := bootstrap.DB.First(&batch, id).Error; err != nil { + if err := bootstrap.DB.First(&batch, "id = ?", id).Error; err != nil { c.JSON(http.StatusNotFound, model.ErrorResponse(model.CodeNotFound, "batch not found")) return } @@ -155,16 +155,15 @@ func (h *BatchHandler) UpdateBatch(c *gin.Context) { // @Description 标记批次为已删除,并物理删除关联的存储文件 // @Tags Admin // @Security AdminAuth -// @Param batch_id path int true "批次 ID" +// @Param batch_id path string true "批次 ID (UUID)" // @Produce json // @Success 200 {object} model.Response // @Failure 500 {object} model.Response // @Router /admin/batches/{batch_id} [delete] func (h *BatchHandler) DeleteBatch(c *gin.Context) { - idStr := c.Param("batch_id") - id, _ := strconv.ParseUint(idStr, 10, 32) + id := c.Param("batch_id") - if err := h.batchService.DeleteBatch(c.Request.Context(), uint(id)); err != nil { + if err := h.batchService.DeleteBatch(c.Request.Context(), id); err != nil { c.JSON(http.StatusInternalServerError, model.ErrorResponse(model.CodeInternalError, err.Error())) return } diff --git a/internal/api/public/pickup.go b/internal/api/public/pickup.go index f8e95fb..2d4aaf2 100644 --- a/internal/api/public/pickup.go +++ b/internal/api/public/pickup.go @@ -103,6 +103,11 @@ func (h *PickupHandler) Pickup(c *gin.Context) { return } + if batch.Type == "text" { + h.batchService.IncrementDownloadCount(batch.ID) + batch.DownloadCount++ + } + c.JSON(http.StatusOK, model.SuccessResponse(PickupResponse{ Remark: batch.Remark, ExpireAt: batch.ExpireAt, @@ -119,24 +124,23 @@ func (h *PickupHandler) Pickup(c *gin.Context) { // @Summary 下载单个文件 // @Description 根据文件 ID 下载单个文件 // @Tags Public -// @Param file_id path int true "文件 ID" +// @Param file_id path string true "文件 ID (UUID)" // @Produce application/octet-stream // @Success 200 {file} file // @Failure 404 {object} model.Response // @Failure 410 {object} model.Response // @Router /api/files/{file_id}/download [get] func (h *PickupHandler) DownloadFile(c *gin.Context) { - fileIDStr := c.Param("file_id") - fileID, _ := strconv.ParseUint(fileIDStr, 10, 32) + fileID := c.Param("file_id") var item model.FileItem - if err := bootstrap.DB.First(&item, fileID).Error; err != nil { + if err := bootstrap.DB.First(&item, "id = ?", fileID).Error; err != nil { c.JSON(http.StatusNotFound, model.ErrorResponse(model.CodeNotFound, "file not found")) return } var batch model.FileBatch - if err := bootstrap.DB.First(&batch, item.BatchID).Error; err != nil { + if err := bootstrap.DB.First(&batch, "id = ?", item.BatchID).Error; err != nil { c.JSON(http.StatusNotFound, model.ErrorResponse(model.CodeNotFound, "batch not found")) return } diff --git a/internal/api/public/upload.go b/internal/api/public/upload.go index f297f2d..8f13683 100644 --- a/internal/api/public/upload.go +++ b/internal/api/public/upload.go @@ -24,7 +24,7 @@ func NewUploadHandler() *UploadHandler { type UploadResponse struct { PickupCode string `json:"pickup_code"` ExpireAt *time.Time `json:"expire_at"` - BatchID uint `json:"batch_id"` + BatchID string `json:"batch_id"` } // Upload 上传文件并生成取件码 diff --git a/internal/model/file_batch.go b/internal/model/file_batch.go index 33f19b8..3478336 100644 --- a/internal/model/file_batch.go +++ b/internal/model/file_batch.go @@ -7,7 +7,7 @@ import ( ) type FileBatch struct { - ID uint `gorm:"primaryKey" json:"id"` + ID string `gorm:"primaryKey;type:varchar(36)" json:"id"` PickupCode string `gorm:"uniqueIndex;not null" json:"pickup_code"` Remark string `json:"remark"` ExpireType string `json:"expire_type"` // time / download / permanent diff --git a/internal/model/file_item.go b/internal/model/file_item.go index c766dbe..88e3bb2 100644 --- a/internal/model/file_item.go +++ b/internal/model/file_item.go @@ -5,8 +5,8 @@ import ( ) type FileItem struct { - ID uint `gorm:"primaryKey" json:"id"` - BatchID uint `gorm:"index;not null" json:"batch_id"` + ID string `gorm:"primaryKey;type:varchar(36)" json:"id"` + BatchID string `gorm:"index;not null;type:varchar(36)" json:"batch_id"` OriginalName string `json:"original_name"` StoragePath string `json:"storage_path"` Size int64 `json:"size"` diff --git a/internal/service/batch_service.go b/internal/service/batch_service.go index 9957e93..d7e2df2 100644 --- a/internal/service/batch_service.go +++ b/internal/service/batch_service.go @@ -57,9 +57,9 @@ func (s *BatchService) MarkAsExpired(batch *model.FileBatch) error { return s.db.Model(batch).Update("status", "expired").Error } -func (s *BatchService) DeleteBatch(ctx context.Context, batchID uint) error { +func (s *BatchService) DeleteBatch(ctx context.Context, batchID string) error { var batch model.FileBatch - if err := s.db.Preload("FileItems").First(&batch, batchID).Error; err != nil { + if err := s.db.Preload("FileItems").First(&batch, "id = ?", batchID).Error; err != nil { return err } @@ -80,7 +80,7 @@ func (s *BatchService) DeleteBatch(ctx context.Context, batchID uint) error { }) } -func (s *BatchService) IncrementDownloadCount(batchID uint) error { +func (s *BatchService) IncrementDownloadCount(batchID string) error { return s.db.Model(&model.FileBatch{}).Where("id = ?", batchID). UpdateColumn("download_count", gorm.Expr("download_count + ?", 1)).Error } diff --git a/internal/service/upload_service.go b/internal/service/upload_service.go index 085a367..819726a 100644 --- a/internal/service/upload_service.go +++ b/internal/service/upload_service.go @@ -34,6 +34,7 @@ func (s *UploadService) CreateBatch(ctx context.Context, files []*multipart.File // 2. 准备 Batch batch := &model.FileBatch{ + ID: uuid.New().String(), PickupCode: pickupCode, Remark: remark, ExpireType: expireType, @@ -69,6 +70,7 @@ func (s *UploadService) CreateTextBatch(ctx context.Context, content string, rem // 2. 准备 Batch batch := &model.FileBatch{ + ID: uuid.New().String(), PickupCode: pickupCode, Remark: remark, ExpireType: expireType, @@ -101,7 +103,7 @@ func (s *UploadService) applyExpire(batch *model.FileBatch, expireType string, e } } -func (s *UploadService) processFile(ctx context.Context, tx *gorm.DB, batchID uint, fileHeader *multipart.FileHeader) (*model.FileItem, error) { +func (s *UploadService) processFile(ctx context.Context, tx *gorm.DB, batchID string, fileHeader *multipart.FileHeader) (*model.FileItem, error) { file, err := fileHeader.Open() if err != nil { return nil, err @@ -110,7 +112,8 @@ func (s *UploadService) processFile(ctx context.Context, tx *gorm.DB, batchID ui // 生成唯一存储路径 ext := filepath.Ext(fileHeader.Filename) - storagePath := fmt.Sprintf("%d/%s%s", batchID, uuid.New().String(), ext) + fileID := uuid.New().String() + storagePath := fmt.Sprintf("%s/%s%s", batchID, fileID, ext) // 保存到存储层 if err := storage.GlobalStorage.Save(ctx, storagePath, file); err != nil { @@ -119,6 +122,7 @@ func (s *UploadService) processFile(ctx context.Context, tx *gorm.DB, batchID ui // 创建数据库记录 item := &model.FileItem{ + ID: fileID, BatchID: batchID, OriginalName: fileHeader.Filename, StoragePath: storagePath, diff --git a/internal/task/cleaner.go b/internal/task/cleaner.go index fe3f310..fd02738 100644 --- a/internal/task/cleaner.go +++ b/internal/task/cleaner.go @@ -53,7 +53,7 @@ func (c *Cleaner) Clean() { bootstrap.DB.Unscoped().Where("status IN ? OR deleted_at IS NOT NULL", []string{"expired", "deleted"}).Find(&toDelete) for _, batch := range toDelete { - fmt.Printf("Deep cleaning batch: %d\n", batch.ID) + fmt.Printf("Deep cleaning batch: %s\n", batch.ID) c.batchService.DeleteBatch(context.Background(), batch.ID) } }