From a80559d353f2080d7536cd655c03a0ba6160065f Mon Sep 17 00:00:00 2001 From: whm <973418690@qq.com> Date: Fri, 7 Aug 2026 01:41:20 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20Z35=20=E7=A6=81=E6=AD=A2=E7=94=A8?= =?UTF-8?q?=E4=B8=AD=E6=96=87=E6=98=B5=E7=A7=B0=E4=BD=9C=20Postgres=20?= =?UTF-8?q?=E5=BA=93=E5=90=8D=EF=BC=8C=E5=8F=91=E5=B8=83=E6=97=B6=E7=BA=A0?= =?UTF-8?q?=E6=AD=A3=E8=84=8F=E6=95=B0=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 绑定 database_name 改为 user_{id};publish 遇非法库名 Sanitize 并回写,避免 EnsureDatabase 400。 Co-authored-by: Cursor --- platform/internal/logic/applogic/publish.go | 19 ++++++++++--- .../internal/logic/applogic/yuheng_ticket.go | 10 ++++--- platform/internal/schema/runner.go | 18 ++++++++++++ 联调后修改意见-宇恒松离线.md | 28 +++++++++++++++++-- 4 files changed, 65 insertions(+), 10 deletions(-) diff --git a/platform/internal/logic/applogic/publish.go b/platform/internal/logic/applogic/publish.go index 77227d2..bd665c8 100644 --- a/platform/internal/logic/applogic/publish.go +++ b/platform/internal/logic/applogic/publish.go @@ -7,9 +7,10 @@ import ( "strings" "time" + "aijianzhan/platform/internal/agentcap" + "aijianzhan/platform/internal/agentstore" "aijianzhan/platform/internal/audit" "aijianzhan/platform/internal/authx" - "aijianzhan/platform/internal/agentcap" "aijianzhan/platform/internal/blueprint" "aijianzhan/platform/internal/meta" "aijianzhan/platform/internal/schema" @@ -264,16 +265,26 @@ func (l *PublishLogic) Publish(slug string, req *types.PublishReq) (*types.Publi schemaName := bp.AssignSchemaName(tenantID) dbName := bp.AssignDatabaseName(tenantID) - // Z8c:智能体若绑定了 database_name,新建模块优先落到该库(database_per_app) + // Z8c:智能体若绑定了合法 database_name,新建模块优先落到该库(database_per_app) + // Z35:中文昵称等非法名不得用于 EnsureDatabase,回落 AssignDatabaseName if authx.Role(l.ctx) == authx.RoleAgent && l.svcCtx.Agents != nil { if aid := authx.AgentID(l.ctx); aid > 0 { if acc, err := l.svcCtx.Agents.Get(l.ctx, tenantID, aid); err == nil && acc != nil { - if dn := strings.TrimSpace(acc.DatabaseName); dn != "" { + if dn := strings.TrimSpace(acc.DatabaseName); dn != "" && schema.ValidDBName(dn) { if !exists || existing.DatabaseName == "" { bp.Storage.Mode = "database_per_app" schemaName = bp.AssignSchemaName(tenantID) dbName = dn } + } else if dn != "" && !schema.ValidDBName(dn) { + // 历史脏数据(如 DisplayName 当库名):纠正智能体落点库名,避免反复 400 + safeName := schema.SanitizeDBName(tenantID, slug) + _, _ = l.svcCtx.Agents.Update(l.ctx, tenantID, aid, agentstore.UpdateInput{ + DatabaseName: &safeName, + }) + bp.Storage.Mode = "database_per_app" + schemaName = bp.AssignSchemaName(tenantID) + dbName = safeName } } } @@ -294,7 +305,7 @@ func (l *PublishLogic) Publish(slug string, req *types.PublishReq) (*types.Publi schemaName = existing.SchemaName bp.Storage.SchemaName = existing.SchemaName } - if existing.DatabaseName != "" { + if existing.DatabaseName != "" && schema.ValidDBName(existing.DatabaseName) { dbName = existing.DatabaseName } ddl, err = schema.BuildPostgresDDL(bp) diff --git a/platform/internal/logic/applogic/yuheng_ticket.go b/platform/internal/logic/applogic/yuheng_ticket.go index 17ca911..0c1aa5f 100644 --- a/platform/internal/logic/applogic/yuheng_ticket.go +++ b/platform/internal/logic/applogic/yuheng_ticket.go @@ -191,10 +191,12 @@ func (l *AuthLogic) bindUserHostSync(u *userstore.User, hostKey, name, localID, return nil, "", fmt.Errorf("host_key 已绑定其它公司") } online := fmt.Sprintf("%s_u%d", dbsync.ResolveOnlineDBID("", ch.ID), u.UserID) - dbName := strings.TrimSpace(u.DisplayName) - if dbName == "" { - dbName = fmt.Sprintf("user_%d", u.UserID) + // DisplayName 可中文;Postgres 物理库名必须 [a-z][a-z0-9_]{1,47}(Z35) + display := strings.TrimSpace(u.DisplayName) + if display == "" { + display = fmt.Sprintf("user_%d", u.UserID) } + dbName := fmt.Sprintf("user_%d", u.UserID) updated, err := l.svcCtx.Agents.AttachSyncBind(l.ctx, acc.AgentID, u.TenantID, ch.ID, online, dbName, true) if err != nil { return nil, "", err @@ -214,7 +216,7 @@ func (l *AuthLogic) bindUserHostSync(u *userstore.User, hostKey, name, localID, OnlineDBID: online, ChannelID: ch.ID, DatabaseName: dbName, - DisplayName: dbName, + DisplayName: display, Note: note, }) return updated, secret, nil diff --git a/platform/internal/schema/runner.go b/platform/internal/schema/runner.go index 0db5f02..0626b51 100644 --- a/platform/internal/schema/runner.go +++ b/platform/internal/schema/runner.go @@ -90,3 +90,21 @@ func SanitizeDBName(tenantID int64, slug string) string { } return name } + +// ValidDBName 是否可作为 Postgres 物理库名(CREATE DATABASE)。 +func ValidDBName(dbName string) bool { + return dbNameRe.MatchString(strings.TrimSpace(dbName)) +} + +// SafePhysicalDBName 优先用 preferred;非法则用 fallback;仍非法则 user_fallback。 +func SafePhysicalDBName(preferred, fallback string) string { + p := strings.TrimSpace(preferred) + if ValidDBName(p) { + return p + } + f := strings.TrimSpace(fallback) + if ValidDBName(f) { + return f + } + return "appdb_default" +} diff --git a/联调后修改意见-宇恒松离线.md b/联调后修改意见-宇恒松离线.md index 65e680f..c9f00b4 100644 --- a/联调后修改意见-宇恒松离线.md +++ b/联调后修改意见-宇恒松离线.md @@ -30,6 +30,7 @@ | **Z23 表单推送失败禁止空等** | **宇恒已改完;智建无需** | `ask_fields_via_chat` 推送失败立即抛错;进行中再选中会话给出提示;对齐 ty_host≥1.2.35(§5.21) | | **Z24 已绑定勿 force 清落点** | **宇恒已改完;智建无需** | 启动自动引导绑定时禁止 `force_rebind` 清 CHANNEL;仅用户点「换绑」才清(§5.22) | | **Z34 / Z34b 换机恢复** | **宇恒半程 + 智建已落实** | 有手机静默恢复(Z34);无手机 `POST …/restore-by-host`(Z34b · §5.32) | +| **Z35 库名非法中文** | **智建已改完** | 绑定 DisplayName 不再作 PG 库名;publish 纠正脏数据(§5.33) | | **仍关注** | 用法 | 通道断了靠智建自愈;表数据靠宇恒双向指纹 / 数据恢复;不是「再点启动」 | ### 0.0 修改流程(冻结) @@ -96,6 +97,7 @@ | **Z12h / Z12h-1 / Z12h-2** | `HealTenantSyncBind`:Ensure 默认通道 + 重挂 Agent/Binding;默认同步**可删**,删后立即 Ensure+heal;列表/换票/`agents/me`/ensure Binding 触发;见 §5.9 | | **数据恢复(§5.13)** | 成功同步后线上库快照(latest+previous);SyncPage「数据恢复」;`GET …/checkpoint` + `POST …/restore` | | **Z34b** | `POST /api/v1/auth/yuheng/restore-by-host`:HMAC 凭票(phone 可空)按 `host_key` 恢复已 sync_bound 落点并轮换密钥;见 §5.32 | +| **Z35** | 绑定 `database_name=user_{id}`;publish 拒绝中文库名并纠正脏数据;见 §5.33 | > 产品一句:**账号已绑定 ⇒ 落点信息固定;通道没了平台自动补,终端无需手填 channel_id。** @@ -103,7 +105,7 @@ | 优先级 | 编号 | 项 | 说明 | |--------|------|----|------| -| — | — | **无阻塞开发项** | Z34b 已合入;宇恒接入 restore-by-host 后联调;生产 pull 见 B′ | +| — | — | (Z35 已合入见 B) | 其余无阻塞开发项;生产 pull 见 B′ | #### B‴. 智建 · 本次明确不改(Z15) @@ -119,7 +121,7 @@ | 优先级 | 项 | 说明 | |--------|----|------| -| **P0** | **生产 pull 本批** | Z10d + fingerprint + Z14c + **Z12h** + **数据恢复** + **Z34b**;`bash ./restart.sh --pull` | +| **P0** | **生产 pull 本批** | Z10d + fingerprint + Z14c + **Z12h** + **数据恢复** + **Z34b** + **Z35**;`bash ./restart.sh --pull` | | **P0** | 成员手机 | 「宇信达」绑 **`13531041944`**;勿超管号 | | **P0** | 通道表白名单 | **勿**「填入测试默认」;形态 B 可空 | | **P1** | 凭票 Secret | `YuhengTicket.Secret` ≡ `YXD_YUHENG_TICKET_SECRET` | @@ -1082,6 +1084,28 @@ POST /api/v1/agent/sync/channels/{id}/pull | **智建** | ✅ Z34b + gateway 路由 + 文档;生产 pull | | **宇恒** | 接入 `restore-by-host`(优先于手机静默路径);联调 | +### 5.33 【Z35 · 2026-08-07】绑定勿用中文 DisplayName 作 Postgres 库名 + +**状态**:**智建已改完;宇恒无需**(报错来自平台 publish EnsureDatabase)。 + +#### 现象 + +模块发布 `HTTP 400: ensure database: invalid database name: 婷婷管理员`。 + +#### 根因 + +`bindUserHostSync` 把成员 `DisplayName` 写入智能体 `database_name`;publish Z8c 用该字段 `CREATE DATABASE`,而物理库名须匹配 `^[a-z][a-z0-9_]{1,47}$`。 + +#### 智建改 + +1. 绑定:`DatabaseName=user_{userID}`;`Binding.DisplayName` 仍用昵称。 +2. publish:仅当 `ValidDBName` 才采用智能体库名;非法则 `SanitizeDBName` 并回写纠正。 +3. 已存在非法 `existing.DatabaseName` 不再沿用。 + +#### 验收 + +昵称含中文的账号发布模块 → 2xx;智能体 `database_name` 为合法英文/数字下划线。 + ## 6. 联系与附件 - **待改清单(优先看)**:§0.2(**A′ 宇恒接入 restore-by-host · B′ 运维 pull**);**改代码前先写本意见**(§0.0);配合见 **§0.3**