You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
263 lines
9.8 KiB
263 lines
9.8 KiB
package console
|
|
|
|
import (
|
|
"fmt"
|
|
"sort"
|
|
"strings"
|
|
|
|
"yunyan/comm"
|
|
"yunyan/lego/sys/log"
|
|
"yunyan/lego/sys/postgres"
|
|
)
|
|
|
|
// ============================ 作用域归一:全局层 → 每个应用一套 ============================
|
|
//
|
|
// 2026-09-14 之前第三方服务配置是「全局默认(app_name='') + 应用覆盖」:应用没配的服务回退到全局行。
|
|
// 两台机上的全部服务/区域分叉/会议编排/会议模板都在全局层,没有一个应用真正有自己的一套。
|
|
// 按要求改成**每个应用一套、互不干涉**:应用 A 删掉某个服务,不会悄悄回退到别人那份。
|
|
//
|
|
// 迁移把全局层的行分给每个已启用的应用:
|
|
// - **第一个应用(app_registry 里 id 最小的)直接把 app_name 从 '' 改成它**,行本身不动——
|
|
// 行 id/row_id 全部保留,svc_health、echomeet_record 等按 id 引用的数据不受影响;
|
|
// - 其余应用各拷一份(新 id),已存在的 (app, key) 不覆盖。
|
|
// 跑完之后全局层为空,运行时那些 `OR app_name=''` 的回退分支再也匹配不到任何行。
|
|
//
|
|
// 幂等:全局层空了就整体跳过。每次启动跑。
|
|
//
|
|
// ⚠️ 作用域键是 app_registry.**app_name**(= 应用中心的应用名,如 EAIMAR / deepGlass),不是
|
|
// app_registry.name(部署名,如 deepglass)。运行时用 ANALYZE_APP_NAME 找自己的行,
|
|
// 实测它等于 app_name;用错列会让 deepGlass 一个服务都读不到。
|
|
//
|
|
// 涉及的表(都以 app_name 为作用域键):
|
|
// svc_config / svc_region_override / echomeet_orch / echomeet_orch_setting / echomeet_template(source=public)
|
|
|
|
func migrateGlobalScopeToApps(apps []*AppRegistry) {
|
|
targets := scopeTargetApps(apps)
|
|
if len(targets) == 0 {
|
|
log.Infof("console.scope: 没有已启用的应用,全局层原样保留")
|
|
return
|
|
}
|
|
first, rest := targets[0], targets[1:]
|
|
|
|
n1, err := migrateScopeSvcConfig(first, rest)
|
|
if err != nil {
|
|
log.Warnf("console.scope: svc_config 归一失败: %v", err)
|
|
}
|
|
n2, err := migrateScopeRegionOverride(first, rest)
|
|
if err != nil {
|
|
log.Warnf("console.scope: svc_region_override 归一失败: %v", err)
|
|
}
|
|
n3, err := migrateScopeEchoOrch(first, rest)
|
|
if err != nil {
|
|
log.Warnf("console.scope: echomeet_orch 归一失败: %v", err)
|
|
}
|
|
n4, err := migrateScopeEchoOrchSetting(first, rest)
|
|
if err != nil {
|
|
log.Warnf("console.scope: echomeet_orch_setting 归一失败: %v", err)
|
|
}
|
|
n5, err := migrateScopeMeetTemplate(first, rest)
|
|
if err != nil {
|
|
log.Warnf("console.scope: echomeet_template 归一失败: %v", err)
|
|
}
|
|
if n1+n2+n3+n4+n5 > 0 {
|
|
log.Infof("console.scope: 全局层已分给各应用(首个应用 %s 原行改名,其余 %v 各拷一份):"+
|
|
"svc_config %d / 区域分叉 %d / 会议编排 %d / 编排开关 %d / 会议模板 %d",
|
|
first, rest, n1, n2, n3, n4, n5)
|
|
}
|
|
}
|
|
|
|
// scopeTargetApps 已启用应用的 app_name 去重,按 app_registry.id 升序(决定谁拿原行)。
|
|
func scopeTargetApps(apps []*AppRegistry) []string {
|
|
sort.Slice(apps, func(i, j int) bool { return apps[i].Id < apps[j].Id })
|
|
seen := map[string]bool{}
|
|
out := make([]string, 0, len(apps))
|
|
for _, a := range apps {
|
|
n := strings.TrimSpace(a.AppName)
|
|
if !a.Enabled || n == "" || seen[n] {
|
|
continue
|
|
}
|
|
seen[n] = true
|
|
out = append(out, n)
|
|
}
|
|
return out
|
|
}
|
|
|
|
// migrateScopeSvcConfig 服务实例。返回处理的全局行数。
|
|
func migrateScopeSvcConfig(first string, rest []string) (int, error) {
|
|
rows := make([]*ThirdSvcConfig, 0)
|
|
if err := postgres.Find(comm.TableSvcConfig, &rows, "app_name=?", ""); err != nil && err != postgres.ErrNoDocuments {
|
|
return 0, err
|
|
}
|
|
if len(rows) == 0 {
|
|
return 0, nil
|
|
}
|
|
for _, r := range rows {
|
|
for _, app := range rest {
|
|
exist := &ThirdSvcConfig{}
|
|
if err := postgres.FindOne(comm.TableSvcConfig, exist, "app_name=? AND id=?", app, r.Id); err == nil {
|
|
continue
|
|
}
|
|
cp := *r
|
|
cp.RowId = 0
|
|
cp.AppName = app
|
|
if err := postgres.Insert(comm.TableSvcConfig, &cp); err != nil {
|
|
return 0, fmt.Errorf("拷贝 %s → %s: %w", r.Id, app, err)
|
|
}
|
|
}
|
|
}
|
|
// 拷完再改名(逐行):首个应用已有同 id 的(上次跑到一半留下的)就删掉全局行,否则改名。
|
|
for _, r := range rows {
|
|
exist := &ThirdSvcConfig{}
|
|
if postgres.FindOne(comm.TableSvcConfig, exist, "app_name=? AND id=?", first, r.Id) == nil {
|
|
_ = postgres.Delete(comm.TableSvcConfig, "row_id=?", r.RowId)
|
|
continue
|
|
}
|
|
if err := postgres.Table(comm.TableSvcConfig).Where("row_id=?", r.RowId).Update("app_name", first).Error; err != nil {
|
|
return 0, err
|
|
}
|
|
}
|
|
return len(rows), nil
|
|
}
|
|
|
|
func migrateScopeRegionOverride(first string, rest []string) (int, error) {
|
|
rows := make([]*SvcRegionOverride, 0)
|
|
if err := postgres.Find(comm.TableSvcRegionOverride, &rows, "app_name=?", ""); err != nil && err != postgres.ErrNoDocuments {
|
|
return 0, err
|
|
}
|
|
if len(rows) == 0 {
|
|
return 0, nil
|
|
}
|
|
for _, r := range rows {
|
|
for _, app := range rest {
|
|
exist := &SvcRegionOverride{}
|
|
if err := postgres.FindOne(comm.TableSvcRegionOverride, exist,
|
|
"app_name=? AND svc_id=? AND region=?", app, r.SvcId, r.Region); err == nil {
|
|
continue
|
|
}
|
|
cp := *r
|
|
cp.Id = 0
|
|
cp.AppName = app
|
|
if err := postgres.Insert(comm.TableSvcRegionOverride, &cp); err != nil {
|
|
return 0, fmt.Errorf("拷贝 %s@%d → %s: %w", r.SvcId, r.Region, app, err)
|
|
}
|
|
}
|
|
}
|
|
for _, r := range rows {
|
|
exist := &SvcRegionOverride{}
|
|
if postgres.FindOne(comm.TableSvcRegionOverride, exist, "app_name=? AND svc_id=? AND region=?", first, r.SvcId, r.Region) == nil {
|
|
_ = postgres.Delete(comm.TableSvcRegionOverride, "id=?", r.Id)
|
|
continue
|
|
}
|
|
if err := postgres.Table(comm.TableSvcRegionOverride).Where("id=?", r.Id).Update("app_name", first).Error; err != nil {
|
|
return 0, err
|
|
}
|
|
}
|
|
return len(rows), nil
|
|
}
|
|
|
|
func migrateScopeEchoOrch(first string, rest []string) (int, error) {
|
|
rows := make([]*EchoOrchestration, 0)
|
|
if err := postgres.Find(comm.TableEchomeetOrch, &rows, "app_name=?", ""); err != nil && err != postgres.ErrNoDocuments {
|
|
return 0, err
|
|
}
|
|
if len(rows) == 0 {
|
|
return 0, nil
|
|
}
|
|
for _, r := range rows {
|
|
for _, app := range rest {
|
|
exist := &EchoOrchestration{}
|
|
if err := postgres.FindOne(comm.TableEchomeetOrch, exist,
|
|
"app_name=? AND region=? AND kind=? AND svc_id=?", app, r.Region, r.Kind, r.SvcId); err == nil {
|
|
continue
|
|
}
|
|
cp := *r
|
|
cp.RowId = 0
|
|
cp.AppName = app
|
|
if err := postgres.Insert(comm.TableEchomeetOrch, &cp); err != nil {
|
|
return 0, err
|
|
}
|
|
}
|
|
}
|
|
for _, r := range rows {
|
|
exist := &EchoOrchestration{}
|
|
if postgres.FindOne(comm.TableEchomeetOrch, exist, "app_name=? AND region=? AND kind=? AND svc_id=?", first, r.Region, r.Kind, r.SvcId) == nil {
|
|
_ = postgres.Delete(comm.TableEchomeetOrch, "row_id=?", r.RowId)
|
|
continue
|
|
}
|
|
if err := postgres.Table(comm.TableEchomeetOrch).Where("row_id=?", r.RowId).Update("app_name", first).Error; err != nil {
|
|
return 0, err
|
|
}
|
|
}
|
|
return len(rows), nil
|
|
}
|
|
|
|
func migrateScopeEchoOrchSetting(first string, rest []string) (int, error) {
|
|
rows := make([]*EchoOrchSetting, 0)
|
|
if err := postgres.Find(comm.TableEchomeetOrchSetting, &rows, "app_name=?", ""); err != nil && err != postgres.ErrNoDocuments {
|
|
return 0, err
|
|
}
|
|
if len(rows) == 0 {
|
|
return 0, nil
|
|
}
|
|
for _, r := range rows {
|
|
for _, app := range rest {
|
|
exist := &EchoOrchSetting{}
|
|
if err := postgres.FindOne(comm.TableEchomeetOrchSetting, exist,
|
|
"app_name=? AND region=?", app, r.Region); err == nil {
|
|
continue
|
|
}
|
|
cp := *r
|
|
cp.RowId = 0
|
|
cp.AppName = app
|
|
if err := postgres.Insert(comm.TableEchomeetOrchSetting, &cp); err != nil {
|
|
return 0, err
|
|
}
|
|
}
|
|
}
|
|
for _, r := range rows {
|
|
exist := &EchoOrchSetting{}
|
|
if postgres.FindOne(comm.TableEchomeetOrchSetting, exist, "app_name=? AND region=?", first, r.Region) == nil {
|
|
_ = postgres.Delete(comm.TableEchomeetOrchSetting, "row_id=?", r.RowId)
|
|
continue
|
|
}
|
|
if err := postgres.Table(comm.TableEchomeetOrchSetting).Where("row_id=?", r.RowId).Update("app_name", first).Error; err != nil {
|
|
return 0, err
|
|
}
|
|
}
|
|
return len(rows), nil
|
|
}
|
|
|
|
// migrateScopeMeetTemplate 公共会议模板。表结构是 pb 生成的 struct(没有 AppName 字段),
|
|
// app_name 列由 ensureConfigTables 用 ALTER 补上,这里走裸 SQL 按列名拷贝。
|
|
// 首个应用同样原行改名——客户端记录里存的模板 id 得保住。
|
|
func migrateScopeMeetTemplate(first string, rest []string) (int, error) {
|
|
pg := postgres.GetSys()
|
|
var n int64
|
|
if err := pg.Raw("SELECT COUNT(*) FROM " + comm.TableEchomeetTemplate +
|
|
" WHERE source='public' AND COALESCE(app_name,'')=''").Scan(&n).Error; err != nil {
|
|
return 0, err
|
|
}
|
|
if n == 0 {
|
|
return 0, nil
|
|
}
|
|
cols := "tid, source, title, description, language, ttype, tags, icon, outline, template, sort"
|
|
for _, app := range rest {
|
|
// 已经有这个应用的同 tid+language 就跳过(NOT EXISTS),其余整批拷。
|
|
if err := pg.Exec("INSERT INTO "+comm.TableEchomeetTemplate+" ("+cols+", app_name) "+
|
|
"SELECT "+cols+", ? FROM "+comm.TableEchomeetTemplate+" g "+
|
|
"WHERE g.source='public' AND COALESCE(g.app_name,'')='' AND NOT EXISTS ("+
|
|
"SELECT 1 FROM "+comm.TableEchomeetTemplate+" x WHERE x.source='public' AND x.app_name=? AND x.tid=g.tid AND x.language=g.language)",
|
|
app, app).Error; err != nil {
|
|
return 0, err
|
|
}
|
|
}
|
|
// 首个应用:已有同 tid+language 的删全局行,其余改名。
|
|
if err := pg.Exec("DELETE FROM "+comm.TableEchomeetTemplate+" g WHERE g.source='public' AND COALESCE(g.app_name,'')='' AND EXISTS ("+
|
|
"SELECT 1 FROM "+comm.TableEchomeetTemplate+" x WHERE x.source='public' AND x.app_name=? AND x.tid=g.tid AND x.language=g.language)", first).Error; err != nil {
|
|
return 0, err
|
|
}
|
|
if err := pg.Exec("UPDATE "+comm.TableEchomeetTemplate+" SET app_name=? WHERE source='public' AND COALESCE(app_name,'')=''", first).Error; err != nil {
|
|
return 0, err
|
|
}
|
|
return int(n), nil
|
|
}
|
|
|