package handler import ( "encoding/json" "net/http" "aijianzhan/platform/internal/authx" "aijianzhan/platform/internal/dbsync" "aijianzhan/platform/internal/svc" "github.com/zeromicro/go-zero/rest/httpx" "github.com/zeromicro/go-zero/rest/pathvar" ) // agent 同步只读白名单 + 推远程 A(形态 B);需 JWT 含「数据同步」权限(人类管理员或智能体均可)。 func agentSyncWhitelistHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { if !requireDBSync(svcCtx, w) { return } ch, err := svcCtx.DBSync.Store().GetChannelForTenant(pathvar.Vars(r)["id"], syncTenantID(r)) if err != nil { authx.WriteError(w, http.StatusNotFound, err.Error()) return } tables := uniqueStringSlice(ch.Local.Tables, ch.Remote.Tables) httpx.OkJson(w, map[string]any{ "channel_id": ch.ID, "name": ch.Name, "enabled": ch.Enabled, "direction": ch.Direction, "conflict_policy": ch.ConflictPolicy, "tables": tables, "pk_columns": ch.PKColumns, "hint": "本机 agent 缓存此表白名单;仅白名单表走 local_dbsync", }) } } func agentSyncPushHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { if !requireDBSync(svcCtx, w) { return } ch, err := svcCtx.DBSync.Store().GetChannelForTenant(pathvar.Vars(r)["id"], syncTenantID(r)) if err != nil { authx.WriteError(w, http.StatusNotFound, err.Error()) return } var item dbsync.PushItem if err := json.NewDecoder(r.Body).Decode(&item); err != nil { authx.WriteError(w, http.StatusBadRequest, err.Error()) return } res, err := dbsync.PushToRemote(r.Context(), ch, svcCtx.DBSync.Store(), item) if err != nil { authx.WriteError(w, http.StatusBadRequest, err.Error()) return } httpx.OkJson(w, map[string]any{"success": true, "result": res}) } } func agentSyncPushBatchHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { if !requireDBSync(svcCtx, w) { return } ch, err := svcCtx.DBSync.Store().GetChannelForTenant(pathvar.Vars(r)["id"], syncTenantID(r)) if err != nil { authx.WriteError(w, http.StatusNotFound, err.Error()) return } var body struct { Items []dbsync.PushItem `json:"items"` } if err := json.NewDecoder(r.Body).Decode(&body); err != nil { authx.WriteError(w, http.StatusBadRequest, err.Error()) return } if len(body.Items) == 0 { authx.WriteError(w, http.StatusBadRequest, "items required") return } if len(body.Items) > 100 { authx.WriteError(w, http.StatusBadRequest, "items limit 100") return } results, err := dbsync.PushBatchToRemote(r.Context(), ch, svcCtx.DBSync.Store(), body.Items) if err != nil { httpx.OkJson(w, map[string]any{ "success": false, "error": err.Error(), "results": results, }) return } httpx.OkJson(w, map[string]any{"success": true, "results": results}) } } func uniqueStringSlice(a, b []string) []string { seen := map[string]struct{}{} var out []string for _, xs := range [][]string{a, b} { for _, s := range xs { if s == "" { continue } if _, ok := seen[s]; ok { continue } seen[s] = struct{}{} out = append(out, s) } } return out }