From 1b68363bfd790bfa150ed95a80a08ebc31943d44 Mon Sep 17 00:00:00 2001 From: bwstudio Date: Thu, 10 Sep 2026 18:59:31 +0800 Subject: [PATCH] =?UTF-8?q?fix(migration):=20Cookie=E8=8F=9C=E5=8D=95?= =?UTF-8?q?=E8=BF=81=E7=A7=BB=E6=94=B9=E4=B8=BA=E6=8C=89=E4=B8=9A=E5=8A=A1?= =?UTF-8?q?=E9=94=AE=E6=9F=A5=E6=89=BE=E5=B9=B6=E6=94=AF=E6=8C=81=20id=20?= =?UTF-8?q?=E9=A1=BA=E5=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 原迁移只按固定 id(76-84) 判断是否存在:若线上 admin_power 的 这些 id 已被其它菜单占用,升级会静默跳过,菜单依旧不出现, 而 start.sh 里 flask db upgrade 的失败/跳过只打 WARN,不易察觉。 调整: - 先按 code / name+type 业务键查找,命中则仅补 open_type、不重复插入 - 固定 id 被占用时顺延到 max(id)+1,并用 id_map 修正父子 parent_id 关联 已用临时库验证两种场景: - 76-84 被占用 -> 菜单落在 85-93,父子关联与 open_type 正确 - 重复执行 -> 仍为 9 条,不重复插入 --- .../d4e5f6a7b8c9_add_cookie_manager_menus.py | 86 +++++++++++++------ 1 file changed, 60 insertions(+), 26 deletions(-) diff --git a/migrations/versions/d4e5f6a7b8c9_add_cookie_manager_menus.py b/migrations/versions/d4e5f6a7b8c9_add_cookie_manager_menus.py index 4f371b9..5837d49 100644 --- a/migrations/versions/d4e5f6a7b8c9_add_cookie_manager_menus.py +++ b/migrations/versions/d4e5f6a7b8c9_add_cookie_manager_menus.py @@ -44,37 +44,71 @@ MENUS = [ def upgrade(): + """幂等补齐 Cookie 管理菜单。 + + 与"仅按固定 id 判断"不同:先按业务键(code / name+type)查找; + 固定 id 若已被其它菜单占用则自动顺延到 max(id)+1, + 避免线上 id 分配与本地不一致时静默跳过、菜单仍然不出现。 + """ conn = op.get_bind() now = datetime.datetime.now() - for mid, name, mtype, code, url, open_type, parent_id, icon, sort, enable in MENUS: - row = conn.execute( - sa.text("SELECT id, open_type FROM admin_power WHERE id = :id"), - {"id": mid}, + def next_free_id(preferred): + exist = conn.execute( + sa.text("SELECT id FROM admin_power WHERE id = :i"), {"i": preferred} ).fetchone() + if exist is None: + return preferred + mx = conn.execute(sa.text("SELECT MAX(id) FROM admin_power")).scalar() or 0 + return int(mx) + 1 - if row is None: - conn.execute( - sa.text( - "INSERT INTO admin_power " - "(id, name, type, code, url, open_type, parent_id, icon, sort, " - " enable, create_time, update_time) " - "VALUES (:id, :name, :type, :code, :url, :open_type, :parent_id, " - " :icon, :sort, :enable, :ct, :ut)" - ), - { - "id": mid, "name": name, "type": mtype, "code": code, - "url": url, "open_type": open_type, "parent_id": parent_id, - "icon": icon, "sort": sort, "enable": enable, - "ct": now, "ut": now, - }, - ) - elif not row[1] and open_type: - # 历史数据:菜单已存在但打开方式为空,补齐 _iframe - conn.execute( - sa.text("UPDATE admin_power SET open_type = :ot WHERE id = :id"), - {"ot": open_type, "id": mid}, - ) + def find_by_code(code): + if not code: + return None + return conn.execute( + sa.text("SELECT id FROM admin_power WHERE code = :c"), {"c": code} + ).scalar() + + def find_by_name_type(name, mtype): + return conn.execute( + sa.text("SELECT id FROM admin_power WHERE name = :n AND type = :t"), + {"n": name, "t": mtype}, + ).scalar() + + id_map = {} # 迁移里声明的固定 id -> 实际落库的 id(用于修正 parent_id) + + for mid, name, mtype, code, url, open_type, parent_id, icon, sort, enable in MENUS: + found = find_by_code(code) or find_by_name_type(name, mtype) + if found is not None: + id_map[mid] = int(found) + if open_type: + conn.execute( + sa.text( + "UPDATE admin_power SET open_type = :ot WHERE id = :id " + "AND (open_type IS NULL OR open_type = '')" + ), + {"ot": open_type, "id": int(found)}, + ) + continue + + real_parent = id_map.get(parent_id, parent_id) if parent_id else 0 + new_id = next_free_id(mid) + conn.execute( + sa.text( + "INSERT INTO admin_power " + "(id, name, type, code, url, open_type, parent_id, icon, sort, " + " enable, create_time, update_time) " + "VALUES (:id, :name, :type, :code, :url, :open_type, :parent_id, " + " :icon, :sort, :enable, :ct, :ut)" + ), + { + "id": new_id, "name": name, "type": mtype, "code": code, + "url": url, "open_type": open_type, "parent_id": real_parent, + "icon": icon, "sort": sort, "enable": enable, + "ct": now, "ut": now, + }, + ) + id_map[mid] = new_id def downgrade():