package admin import ( "FileRelay/internal/bootstrap" "FileRelay/internal/model" "FileRelay/internal/service" "net/http" "strconv" "time" "github.com/gin-gonic/gin" ) type BatchHandler struct { batchService *service.BatchService } func NewBatchHandler() *BatchHandler { return &BatchHandler{ batchService: service.NewBatchService(), } } type ListBatchesResponse struct { Total int64 `json:"total"` Page int `json:"page"` PageSize int `json:"page_size"` Data []model.FileBatch `json:"data"` } type UpdateBatchRequest struct { Remark string `json:"remark"` ExpireType string `json:"expire_type"` ExpireAt *time.Time `json:"expire_at"` MaxDownloads int `json:"max_downloads"` Status string `json:"status"` } // ListBatches 获取批次列表 // @Summary 获取批次列表 // @Description 分页查询所有文件批次,支持按状态过滤和取件码模糊搜索 // @Tags Admin // @Security AdminAuth // @Param page query int false "页码 (默认 1)" // @Param page_size query int false "每页数量 (默认 20)" // @Param status query string false "状态 (active/expired/deleted)" // @Param pickup_code query string false "取件码 (模糊搜索)" // @Produce json // @Success 200 {object} model.Response{data=ListBatchesResponse} // @Failure 401 {object} model.Response // @Router /admin/batches [get] func (h *BatchHandler) ListBatches(c *gin.Context) { page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20")) if page < 1 { page = 1 } if pageSize < 1 { pageSize = 20 } status := c.Query("status") pickupCode := c.Query("pickup_code") query := bootstrap.DB.Model(&model.FileBatch{}) if status != "" { query = query.Where("status = ?", status) } if pickupCode != "" { query = query.Where("pickup_code LIKE ?", "%"+pickupCode+"%") } var total int64 query.Count(&total) var batches []model.FileBatch err := query.Offset((page - 1) * pageSize).Limit(pageSize).Order("created_at DESC").Find(&batches).Error if err != nil { c.JSON(http.StatusInternalServerError, model.ErrorResponse(model.CodeInternalError, err.Error())) return } c.JSON(http.StatusOK, model.SuccessResponse(ListBatchesResponse{ Total: total, Page: page, PageSize: pageSize, Data: batches, })) } // GetBatch 获取批次详情 // @Summary 获取批次详情 // @Description 根据批次 ID 获取批次信息及关联的文件列表 // @Tags Admin // @Security AdminAuth // @Param batch_id path int true "批次 ID" // @Produce json // @Success 200 {object} model.Response{data=model.FileBatch} // @Failure 404 {object} model.Response // @Router /admin/batch/{batch_id} [get] 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 { c.JSON(http.StatusNotFound, model.ErrorResponse(model.CodeNotFound, "batch not found")) return } c.JSON(http.StatusOK, model.SuccessResponse(batch)) } // UpdateBatch 修改批次信息 // @Summary 修改批次信息 // @Description 允许修改备注、过期策略、最大下载次数、状态等 // @Tags Admin // @Security AdminAuth // @Accept json // @Produce json // @Param batch_id path int true "批次 ID" // @Param request body UpdateBatchRequest true "修改内容" // @Success 200 {object} model.Response{data=model.FileBatch} // @Failure 400 {object} model.Response // @Router /admin/batch/{batch_id} [put] 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 { c.JSON(http.StatusNotFound, model.ErrorResponse(model.CodeNotFound, "batch not found")) return } var input UpdateBatchRequest if err := c.ShouldBindJSON(&input); err != nil { c.JSON(http.StatusBadRequest, model.ErrorResponse(model.CodeBadRequest, err.Error())) return } updates := make(map[string]interface{}) updates["remark"] = input.Remark updates["expire_type"] = input.ExpireType updates["expire_at"] = input.ExpireAt updates["max_downloads"] = input.MaxDownloads updates["status"] = input.Status if err := bootstrap.DB.Model(&batch).Updates(updates).Error; err != nil { c.JSON(http.StatusInternalServerError, model.ErrorResponse(model.CodeInternalError, err.Error())) return } c.JSON(http.StatusOK, model.SuccessResponse(batch)) } // DeleteBatch 删除批次 // @Summary 删除批次 // @Description 标记批次为已删除,并物理删除关联的存储文件 // @Tags Admin // @Security AdminAuth // @Param batch_id path int true "批次 ID" // @Produce json // @Success 200 {object} model.Response // @Failure 500 {object} model.Response // @Router /admin/batch/{batch_id} [delete] func (h *BatchHandler) DeleteBatch(c *gin.Context) { idStr := c.Param("batch_id") id, _ := strconv.ParseUint(idStr, 10, 32) if err := h.batchService.DeleteBatch(c.Request.Context(), uint(id)); err != nil { c.JSON(http.StatusInternalServerError, model.ErrorResponse(model.CodeInternalError, err.Error())) return } c.JSON(http.StatusOK, model.SuccessResponse(map[string]interface{}{})) }