package httpapi import ( "context" "encoding/json" "errors" "io" "knowledge-governance/internal/qaopsmirror" "net/http" "strings" ) type qaMirrorSyncStore interface { GetSyncStatus(context.Context) (qaopsmirror.Status, error) EnqueueSync(context.Context, string, string) (qaopsmirror.SyncReceipt, bool, error) } func qaRecordsSync(w http.ResponseWriter, r *http.Request, c Config) { if !authorizeQAOps(w, r) { return } if r.URL.RawQuery != "" { writeJSON(w, 400, map[string]string{"error": "同步入口不接受查询参数"}) return } store, ok := c.QAOps.(qaMirrorSyncStore) if !ok || !c.QAOpsEnabled { writeJSON(w, 503, map[string]string{"error": "本地问答同步未接线,未创建任务"}) return } if r.Method == http.MethodGet { state, e := store.GetSyncStatus(r.Context()) if e != nil { qaOpsError(w, e) return } writeJSON(w, 200, map[string]any{"data": state}) return } if !authorizeQAIntent(w, r, "qa-records-sync") { return } key := r.Header.Get("Idempotency-Key") if key == "" || len(key) > 128 || strings.TrimSpace(key) != key { writeJSON(w, 400, map[string]string{"error": "同步请求需要有效 Idempotency-Key,重试须复用原标识"}) return } var body *struct{} d := json.NewDecoder(http.MaxBytesReader(w, r.Body, 1024)) d.DisallowUnknownFields() if d.Decode(&body) != nil || body == nil || d.Decode(&struct{}{}) != io.EOF { writeJSON(w, 400, map[string]string{"error": "同步请求体必须是空对象"}) return } identity, _ := sessionFrom(r.Context()) receipt, replayed, e := store.EnqueueSync(r.Context(), key, identity.Username) if e != nil { status := 503 message := "同步未成功入队,已有本地数据保持不变" if errors.Is(e, qaopsmirror.ErrInvalid) { status = 400 message = "同步请求标识无效" } if errors.Is(e, qaopsmirror.ErrConflict) { status = 409 message = "同步标识已用于另一操作,请重新发起" } if errors.Is(e, qaopsmirror.ErrWorkerUnavailable) { message = "问答同步执行器未在线,未创建任务" } writeJSON(w, status, map[string]string{"error": message}) return } operation := "created" if replayed { operation = "replayed" } writeJSON(w, 202, map[string]any{"data": receipt, "operation": operation}) }