fix(migration): Cookie菜单迁移改为按业务键查找并支持 id 顺延
原迁移只按固定 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 条,不重复插入
This commit is contained in:
@@ -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():
|
||||
|
||||
Reference in New Issue
Block a user