package httpapi import ( "errors" "net/http" "strings" "git.nianxx.cn/wangxuming/NianAIGC/backend/internal/jobs" "git.nianxx.cn/wangxuming/NianAIGC/backend/internal/layercompositions" ) type LayerCompositionsConfig struct { MaxJSONBytes int64 } type layerCompositionsHandler struct { platform *PlatformAuthorizer jobs *jobs.Service compositions *layercompositions.Service maxJSONBytes int64 } func NewLayerCompositionsHandler(platform *PlatformAuthorizer, jobService *jobs.Service, compositions *layercompositions.Service, config LayerCompositionsConfig) (http.Handler, error) { if platform == nil || jobService == nil || compositions == nil { return nil, errors.New("layer composition HTTP dependencies are not configured") } if config.MaxJSONBytes <= 0 { config.MaxJSONBytes = 1 << 20 } return &layerCompositionsHandler{platform: platform, jobs: jobService, compositions: compositions, maxJSONBytes: config.MaxJSONBytes}, nil } func (handler *layerCompositionsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { jobID, matched := matchLayerCompositionRoute(r.URL.Path) if !matched { http.NotFound(w, r) return } if r.Method == http.MethodOptions { w.Header().Set("Allow", "GET, PUT") w.WriteHeader(http.StatusNoContent) return } if r.Method != http.MethodGet && r.Method != http.MethodPut { w.Header().Set("Allow", "GET, PUT") w.WriteHeader(http.StatusMethodNotAllowed) return } session, err := handler.platform.Authorize(r, PlatformApp) if err != nil { writeLayerCompositionError(w, err) return } job, err := handler.jobs.Get(r.Context(), jobs.Scope{OwnerID: session.User.ID}, jobID) if err != nil || job.Capability != "image.generate" { writeJSON(w, http.StatusNotFound, map[string]string{"error": "图层任务不存在"}) return } if r.Method == http.MethodGet { composition, found, err := handler.compositions.Get(r.Context(), session.User.ID, jobID) if err != nil { writeLayerCompositionError(w, err) return } if !found { writeJSON(w, http.StatusOK, map[string]any{"composition": nil}) return } writeJSON(w, http.StatusOK, map[string]any{"composition": composition}) return } input := struct { Version int64 `json:"version"` BaseAssetID string `json:"baseAssetId"` BaseVisible bool `json:"baseVisible"` Layers []layercompositions.Layer `json:"layers"` }{} if !decodeJobJSON(w, r, handler.maxJSONBytes, &input, false) { return } if !containsString(job.OutputAssetIDs, input.BaseAssetID) { writeJSON(w, http.StatusBadRequest, map[string]string{"error": "底图必须来自当前图层拆分任务"}) return } saved, err := handler.compositions.Save(r.Context(), session.User.ID, jobID, input.Version, layercompositions.Composition{ BaseAssetID: input.BaseAssetID, BaseVisible: input.BaseVisible, Layers: input.Layers, }) if err != nil { writeLayerCompositionError(w, err) return } writeJSON(w, http.StatusOK, map[string]any{"composition": saved}) } func matchLayerCompositionRoute(path string) (string, bool) { const prefix = "/api/layer-compositions/" if !strings.HasPrefix(path, prefix) { return "", false } jobID := strings.TrimPrefix(path, prefix) return jobID, jobID != "" && !strings.Contains(jobID, "/") } func writeLayerCompositionError(w http.ResponseWriter, err error) { switch { case errors.Is(err, layercompositions.ErrConflict): writeJSON(w, http.StatusConflict, map[string]string{"error": "图层工程已在其他页面更新,请重试"}) case errors.Is(err, layercompositions.ErrInvalid), errors.Is(err, layercompositions.ErrAssetNotFound): writeJSON(w, http.StatusBadRequest, map[string]string{"error": err.Error()}) default: writeJobError(w, err, false) } } func containsString(values []string, wanted string) bool { for _, value := range values { if value == wanted { return true } } return false }