package sync import ( "context" "fmt" "strings" "gitea.redpowerfuture.com/red-future/common/db/gfdb" "github.com/sirupsen/logrus" ) // ColumnDef 列定义 type ColumnDef struct { Name string `json:"name"` Type string `json:"type"` Comment string `json:"comment,omitempty"` } // TableDefinition 表结构定义 type TableDefinition struct { TableName string `json:"table_name"` Columns []ColumnDef `json:"columns"` ConflictKeys []string `json:"conflict_keys,omitempty"` } // ParseTableDefinition 解析 table_definition JSON func ParseTableDefinition(raw map[string]interface{}) (*TableDefinition, error) { td := &TableDefinition{} name, _ := raw["table_name"].(string) if name == "" { return nil, fmt.Errorf("table_definition 缺少 table_name") } td.TableName = name colsRaw, _ := raw["columns"].([]interface{}) for _, c := range colsRaw { cm, _ := c.(map[string]interface{}) if cm == nil { continue } n, _ := cm["name"].(string) t, _ := cm["type"].(string) comment, _ := cm["comment"].(string) if n == "" || t == "" { continue } td.Columns = append(td.Columns, ColumnDef{Name: n, Type: t, Comment: comment}) } if keys, _ := raw["conflict_keys"].([]interface{}); keys != nil { for _, k := range keys { if s, ok := k.(string); ok { td.ConflictKeys = append(td.ConflictKeys, s) } } } if len(td.Columns) == 0 { return nil, fmt.Errorf("table_definition 列定义为空") } return td, nil } // EnsureTable 确保表存在 func EnsureTable(ctx context.Context, td *TableDefinition) error { sql := buildCreateSQL(td) logrus.Infof("建表: %s", td.TableName) _, err := gfdb.DB(ctx).Exec(ctx, sql) if err != nil { return fmt.Errorf("建表失败 [%s]: %w", td.TableName, err) } logrus.Infof("表 %s 已就绪", td.TableName) return nil } func buildCreateSQL(td *TableDefinition) string { cols := []string{ "id BIGSERIAL PRIMARY KEY", "tenant_id BIGINT NOT NULL DEFAULT 0", "creator VARCHAR(64) DEFAULT ''", "created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()", "updater VARCHAR(64) DEFAULT ''", "updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()", "deleted_at TIMESTAMP WITH TIME ZONE", } for _, c := range td.Columns { cols = append(cols, fmt.Sprintf("%s %s", c.Name, c.Type)) } cols = append(cols, "raw_data JSONB DEFAULT '{}'") // 添加复合唯一索引(用于 ON CONFLICT upsert) var constraints []string if len(td.ConflictKeys) > 0 { ck := strings.Join(td.ConflictKeys, ", ") indexName := fmt.Sprintf("uq_%s_conflict", td.TableName) constraints = append(constraints, fmt.Sprintf("CREATE UNIQUE INDEX IF NOT EXISTS %s ON %s (%s)", indexName, td.TableName, ck)) } sql := fmt.Sprintf("CREATE TABLE IF NOT EXISTS %s (\n %s\n);\n", td.TableName, strings.Join(cols, ",\n ")) // 添加唯一索引 if len(constraints) > 0 { sql += strings.Join(constraints, ";\n") + ";\n" } // 添加字段注释(COMMENT ON COLUMN) for _, c := range td.Columns { if c.Comment != "" { escaped := strings.ReplaceAll(c.Comment, "'", "''") sql += fmt.Sprintf("COMMENT ON COLUMN %s.%s IS '%s';\n", td.TableName, c.Name, escaped) } } return sql }