package httpapi import ( "io" "net/http" "strings" "github.com/easyai/easyai-ai-gateway/apps/api/internal/clients" "github.com/easyai/easyai-ai-gateway/apps/api/internal/runner" ) const maxGatewayUploadBytes = 256 << 20 // uploadFile godoc // @Summary 上传文件 // @Description 上传文件到配置的文件存储通道;没有启用通道时回退到本地静态上传目录。单文件最大 256MiB。 // @Tags files // @Accept multipart/form-data // @Produce json // @Security BearerAuth // @Param file formData file true "要上传的文件" // @Param source formData string false "上传来源标识" default(ai-gateway-openapi) // @Success 200 {object} FileUploadResponse // @Failure 400 {object} ErrorEnvelope // @Failure 401 {object} ErrorEnvelope // @Failure 502 {object} ErrorEnvelope // @Failure 503 {object} ErrorEnvelope // @Router /api/v1/files/upload [post] // @Router /v1/files/upload [post] func (s *Server) uploadFile(w http.ResponseWriter, r *http.Request) { r.Body = http.MaxBytesReader(w, r.Body, maxGatewayUploadBytes) if err := r.ParseMultipartForm(32 << 20); err != nil { writeError(w, http.StatusBadRequest, "invalid multipart upload") return } file, header, err := r.FormFile("file") if err != nil { writeError(w, http.StatusBadRequest, "file is required") return } defer file.Close() payload, err := io.ReadAll(file) if err != nil { writeError(w, http.StatusBadRequest, "read upload file failed") return } contentType := strings.TrimSpace(header.Header.Get("Content-Type")) if contentType == "" && len(payload) > 0 { contentType = http.DetectContentType(payload) } upload, err := s.runner.UploadFile(r.Context(), runner.FileUploadPayload{ Bytes: payload, ContentType: contentType, FileName: header.Filename, Source: firstNonEmptyFormValue(r, "source", "ai-gateway-openapi"), }) if err != nil { s.logger.Error("upload file failed", "error", err) status := http.StatusBadGateway if clients.ErrorCode(err) == "upload_no_channel" { status = http.StatusServiceUnavailable } writeError(w, status, err.Error()) return } writeJSON(w, http.StatusOK, upload) } func firstNonEmptyFormValue(r *http.Request, key string, fallback string) string { if value := strings.TrimSpace(r.FormValue(key)); value != "" { return value } return fallback }