feat: add Z12/Z13 bind APIs, stock import, and sync docs
Enable auto default sync channels on agent activate, bind-code/phone confirm flows, publish ALTER, and align admin/yuheng docs with the production bind path.
This commit is contained in:
109
platform/internal/dbsync/ensure_default_channel.go
Normal file
109
platform/internal/dbsync/ensure_default_channel.go
Normal file
@@ -0,0 +1,109 @@
|
||||
package dbsync
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// DefaultChannelOpts 创建公司默认同步通道(Z12c)。
|
||||
type DefaultChannelOpts struct {
|
||||
TenantID int64
|
||||
AgentID int64
|
||||
Name string
|
||||
RemoteDriver Driver
|
||||
RemoteDSN string
|
||||
OnlineDBID string // 空则用 channel id
|
||||
DatabaseName string
|
||||
}
|
||||
|
||||
// FindSystemDefaultChannel 返回该公司 IsSystemDefault 通道(若有多条取最新启用的)。
|
||||
func (s *FileStore) FindSystemDefaultChannel(tenantID int64) (*Channel, error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
list, err := s.readChannels()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var best *Channel
|
||||
for i := range list {
|
||||
ch := list[i]
|
||||
if ch.TenantID != tenantID || !ch.IsSystemDefault {
|
||||
continue
|
||||
}
|
||||
cp := ch
|
||||
if best == nil || cp.UpdatedAt.After(best.UpdatedAt) {
|
||||
best = &cp
|
||||
}
|
||||
}
|
||||
return best, nil
|
||||
}
|
||||
|
||||
// EnsureSystemDefaultChannel 若无默认同步通道则创建;返回通道(已存在则复用)。
|
||||
func (s *FileStore) EnsureSystemDefaultChannel(opts DefaultChannelOpts) (Channel, error) {
|
||||
if opts.TenantID <= 0 {
|
||||
return Channel{}, fmt.Errorf("tenant_id required")
|
||||
}
|
||||
if existing, err := s.FindSystemDefaultChannel(opts.TenantID); err == nil && existing != nil {
|
||||
ch := *existing
|
||||
if opts.AgentID > 0 && ch.AgentID == 0 {
|
||||
ch.AgentID = opts.AgentID
|
||||
saved, err := s.SaveChannel(ch)
|
||||
if err != nil {
|
||||
return Channel{}, err
|
||||
}
|
||||
return saved, nil
|
||||
}
|
||||
return ch, nil
|
||||
}
|
||||
driver := opts.RemoteDriver
|
||||
if driver == "" {
|
||||
driver = DriverPostgres
|
||||
}
|
||||
dsn := strings.TrimSpace(opts.RemoteDSN)
|
||||
if dsn == "" {
|
||||
driver = DriverSQLite
|
||||
dsn = filepath.ToSlash(filepath.Join(".", "data", "dbsync", fmt.Sprintf("tenant_%d_online.db", opts.TenantID)))
|
||||
dsn = "file:" + dsn + "?_pragma=busy_timeout(5000)"
|
||||
}
|
||||
name := strings.TrimSpace(opts.Name)
|
||||
if name == "" {
|
||||
name = fmt.Sprintf("公司默认同步 #%d", opts.TenantID)
|
||||
}
|
||||
ch := Channel{
|
||||
TenantID: opts.TenantID,
|
||||
Name: name,
|
||||
Enabled: false, // 默认同步通道供 agent push 落点;不启本地 outbox 轮询
|
||||
Direction: DirLocalToRemote,
|
||||
ConflictPolicy: PolicyLWWSource,
|
||||
IsSystemDefault: true,
|
||||
AgentID: opts.AgentID,
|
||||
Local: Endpoint{
|
||||
Driver: DriverSQLite,
|
||||
DSN: "file:./data/dbsync/local_placeholder.db?_pragma=busy_timeout(5000)",
|
||||
Tables: nil,
|
||||
},
|
||||
Remote: Endpoint{
|
||||
Driver: driver,
|
||||
DSN: dsn,
|
||||
Tables: nil,
|
||||
},
|
||||
PKColumns: map[string]string{},
|
||||
}
|
||||
if err := ValidateChannelConfig(&ch); err != nil {
|
||||
return Channel{}, err
|
||||
}
|
||||
saved, err := s.SaveChannel(ch)
|
||||
if err != nil {
|
||||
return Channel{}, err
|
||||
}
|
||||
return saved, nil
|
||||
}
|
||||
|
||||
// ResolveOnlineDBID 默认 online_db_id:优先显式值,否则通道 id。
|
||||
func ResolveOnlineDBID(explicit, channelID string) string {
|
||||
if s := strings.TrimSpace(explicit); s != "" {
|
||||
return s
|
||||
}
|
||||
return strings.TrimSpace(channelID)
|
||||
}
|
||||
@@ -51,6 +51,8 @@ type Channel struct {
|
||||
// AgentID / AppSlug:把通道挂到某个智能体及其模块,便于「模块数据进该智能体库」对照。
|
||||
AgentID int64 `json:"agent_id,omitempty"`
|
||||
AppSlug string `json:"app_slug,omitempty"`
|
||||
// IsSystemDefault:公司默认同步通道(Z12);表白名单可空(Z4 整库 push)
|
||||
IsSystemDefault bool `json:"is_system_default,omitempty"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
LastError string `json:"last_error,omitempty"`
|
||||
|
||||
@@ -14,6 +14,9 @@ func ValidateChannelConfig(ch *Channel) error {
|
||||
}
|
||||
tables := uniqueTables(ch.Local.Tables, ch.Remote.Tables)
|
||||
if len(tables) == 0 {
|
||||
if ch.IsSystemDefault {
|
||||
return nil // Z12 默认同步通道:不强制表白名单(Z4 整库)
|
||||
}
|
||||
return fmt.Errorf("同步表白名单为空:请至少在 local 或 remote 填写表名")
|
||||
}
|
||||
for _, t := range tables {
|
||||
|
||||
Reference in New Issue
Block a user