Files
ai_site/platform/internal/logic/applogic/sync_heal.go
whm 4b6c90904b fix: Z35 map Chinese display names to stable physical DB names
Keep Chinese on Binding for UI; Agents use user_{id} or db{hash}; sanitize on attach/heal/publish.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-07 02:12:55 +08:00

138 lines
4.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package applogic
import (
"context"
"fmt"
"log"
"strings"
"sync"
"aijianzhan/platform/internal/agentstore"
"aijianzhan/platform/internal/dbsync"
"aijianzhan/platform/internal/schema"
"aijianzhan/platform/internal/svc"
)
// HealResult Z12h 自愈结果。
type HealResult struct {
ChannelID string `json:"channel_id"`
BindingsFixed int `json:"bindings_fixed"`
AgentsFixed int `json:"agents_fixed"`
ChannelCreated bool `json:"channel_created,omitempty"`
}
var healTenantMu sync.Map // tenantID -> *sync.Mutex避免 SyncPage 并行列表双建默认通道
func lockTenantHeal(tenantID int64) func() {
v, _ := healTenantMu.LoadOrStore(tenantID, &sync.Mutex{})
mu := v.(*sync.Mutex)
mu.Lock()
return mu.Unlock
}
// channelAlive 通道是否仍属于该租户。
func channelAlive(store *dbsync.FileStore, tenantID int64, channelID string) bool {
id := strings.TrimSpace(channelID)
if id == "" || store == nil || tenantID <= 0 {
return false
}
_, err := store.GetChannelForTenant(id, tenantID)
return err == nil
}
func agentNeedsSyncHeal(store *dbsync.FileStore, a agentstore.Account) bool {
// Z35非法物理库名如中文昵称也要纠正否则发布 EnsureDatabase 400
if dn := strings.TrimSpace(a.DatabaseName); dn != "" && !schema.ValidDBName(dn) {
return true
}
cid := strings.TrimSpace(a.ChannelID)
alive := channelAlive(store, a.TenantID, cid)
if cid != "" {
if !alive {
return true // 挂死通道
}
return strings.TrimSpace(a.OnlineDBID) == "" // 通道在但缺 online
}
// 无通道:仅 active与 Z12c 启用即绑一致),勿动 pending
return a.Status == agentstore.StatusActive || a.Status == ""
}
// HealTenantSyncBind Z12h确保公司默认同步通道存在并运行重挂挂死通道的 Agent/Binding。
// online_db_id 有值则保留(同一 remote 上的逻辑库名不变);仅改 channel_id。
func HealTenantSyncBind(ctx context.Context, svcCtx *svc.ServiceContext, tenantID int64) (*HealResult, error) {
if svcCtx == nil || svcCtx.DBSync == nil || tenantID <= 0 {
return nil, nil
}
unlock := lockTenantHeal(tenantID)
defer unlock()
cfg := svcCtx.Config.DBSync
driver := dbsync.Driver(strings.TrimSpace(cfg.DefaultRemoteDriver))
if driver == "" {
driver = dbsync.DriverPostgres
}
before, _ := svcCtx.DBSync.Store().FindSystemDefaultChannel(tenantID)
ch, err := svcCtx.DBSync.EnsureAndStartSystemDefaultChannel(dbsync.DefaultChannelOpts{
TenantID: tenantID,
RemoteDriver: driver,
RemoteDSN: strings.TrimSpace(cfg.DefaultRemoteDSN),
Name: fmt.Sprintf("公司默认同步 #%d", tenantID),
})
if err != nil {
return nil, err
}
out := &HealResult{
ChannelID: ch.ID,
ChannelCreated: before == nil || before.ID != ch.ID,
}
// Binding通道空或已删除 → 改挂默认通道(保留 online_db_id
list, err := svcCtx.DBSync.Store().ListBindings(tenantID, "")
if err == nil {
for _, b := range list {
if channelAlive(svcCtx.DBSync.Store(), tenantID, b.ChannelID) {
continue
}
b.ChannelID = ch.ID
if strings.TrimSpace(b.OnlineDBID) == "" {
b.OnlineDBID = dbsync.ResolveOnlineDBID("", ch.ID)
}
if _, e := svcCtx.DBSync.Store().EnsureBinding(b); e != nil {
log.Printf("dbsync heal binding tenant=%d local=%s: %v", tenantID, b.LocalDatabaseID, e)
continue
}
out.BindingsFixed++
}
}
// Agent挂死通道 / 缺 online / active 无通道 → AttachSyncBind保留 online_db_id
if svcCtx.Agents != nil {
agents, err := svcCtx.Agents.List(ctx, tenantID)
if err == nil {
for i := range agents {
a := agents[i]
if !agentNeedsSyncHeal(svcCtx.DBSync.Store(), a) {
continue
}
online := strings.TrimSpace(a.OnlineDBID)
if online == "" {
online = dbsync.ResolveOnlineDBID("", ch.ID)
}
cid := strings.TrimSpace(a.ChannelID)
if !channelAlive(svcCtx.DBSync.Store(), tenantID, cid) {
cid = ch.ID
}
dbName := schema.SafeSyncDatabaseName(a.DatabaseName, a.AgentID, 0)
if _, e := svcCtx.Agents.AttachSyncBind(ctx, a.AgentID, tenantID, cid, online, dbName, a.Status == agentstore.StatusActive || a.Status == ""); e != nil {
log.Printf("dbsync heal agent tenant=%d agent=%d: %v", tenantID, a.AgentID, e)
continue
}
out.AgentsFixed++
}
}
}
if out.BindingsFixed > 0 || out.AgentsFixed > 0 || out.ChannelCreated {
log.Printf("dbsync heal tenant=%d channel=%s bindings=%d agents=%d created=%v",
tenantID, ch.ID, out.BindingsFixed, out.AgentsFixed, out.ChannelCreated)
}
return out, nil
}