package dbsync import ( "encoding/json" "fmt" "os" "path/filepath" "strings" "time" "github.com/google/uuid" ) // Binding 本机库 ↔ 线上库映射(P1;不挡推送,供登记/查询)。 type Binding struct { ID string `json:"id"` TenantID int64 `json:"tenant_id"` UserID int64 `json:"user_id,omitempty"` LocalDatabaseID string `json:"local_database_id"` OnlineDBID string `json:"online_db_id"` ChannelID string `json:"channel_id,omitempty"` Note string `json:"note,omitempty"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } func (s *FileStore) bindingPath() string { return filepath.Join(s.dir, "bindings.json") } func (s *FileStore) EnsureBinding(b Binding) (Binding, error) { s.mu.Lock() defer s.mu.Unlock() list, err := s.readBindingsUnlocked() if err != nil { return b, err } b.LocalDatabaseID = strings.TrimSpace(b.LocalDatabaseID) b.OnlineDBID = strings.TrimSpace(b.OnlineDBID) if b.TenantID <= 0 { return b, fmt.Errorf("tenant_id required") } if b.LocalDatabaseID == "" || b.OnlineDBID == "" { return b, fmt.Errorf("local_database_id and online_db_id required") } now := time.Now().UTC() for i := range list { if list[i].TenantID == b.TenantID && list[i].LocalDatabaseID == b.LocalDatabaseID { if b.UserID > 0 && list[i].UserID > 0 && list[i].UserID != b.UserID { continue } list[i].OnlineDBID = b.OnlineDBID if b.ChannelID != "" { list[i].ChannelID = b.ChannelID } if b.Note != "" { list[i].Note = b.Note } if b.UserID > 0 { list[i].UserID = b.UserID } list[i].UpdatedAt = now if err := s.writeBindingsUnlocked(list); err != nil { return b, err } return list[i], nil } } if b.ID == "" { b.ID = uuid.NewString() } b.CreatedAt = now b.UpdatedAt = now list = append(list, b) if err := s.writeBindingsUnlocked(list); err != nil { return b, err } return b, nil } func (s *FileStore) ListBindings(tenantID int64, localDatabaseID string) ([]Binding, error) { s.mu.Lock() defer s.mu.Unlock() list, err := s.readBindingsUnlocked() if err != nil { return nil, err } out := make([]Binding, 0) for _, b := range list { if b.TenantID != tenantID { continue } if localDatabaseID != "" && b.LocalDatabaseID != localDatabaseID { continue } out = append(out, b) } return out, nil } func (s *FileStore) GetBinding(tenantID int64, localDatabaseID string) (*Binding, error) { list, err := s.ListBindings(tenantID, localDatabaseID) if err != nil { return nil, err } if len(list) == 0 { return nil, fmt.Errorf("binding not found") } cp := list[0] return &cp, nil } func (s *FileStore) readBindingsUnlocked() ([]Binding, error) { path := s.bindingPath() b, err := os.ReadFile(path) if err != nil { if os.IsNotExist(err) { return []Binding{}, nil } return nil, err } if len(b) == 0 { return []Binding{}, nil } var list []Binding if err := json.Unmarshal(b, &list); err != nil { return nil, err } return list, nil } func (s *FileStore) writeBindingsUnlocked(list []Binding) error { path := s.bindingPath() raw, err := json.MarshalIndent(list, "", " ") if err != nil { return err } tmp := path + ".tmp" if err := os.WriteFile(tmp, raw, 0o600); err != nil { return err } return os.Rename(tmp, path) }