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():
|
def upgrade():
|
||||||
|
"""幂等补齐 Cookie 管理菜单。
|
||||||
|
|
||||||
|
与"仅按固定 id 判断"不同:先按业务键(code / name+type)查找;
|
||||||
|
固定 id 若已被其它菜单占用则自动顺延到 max(id)+1,
|
||||||
|
避免线上 id 分配与本地不一致时静默跳过、菜单仍然不出现。
|
||||||
|
"""
|
||||||
conn = op.get_bind()
|
conn = op.get_bind()
|
||||||
now = datetime.datetime.now()
|
now = datetime.datetime.now()
|
||||||
|
|
||||||
for mid, name, mtype, code, url, open_type, parent_id, icon, sort, enable in MENUS:
|
def next_free_id(preferred):
|
||||||
row = conn.execute(
|
exist = conn.execute(
|
||||||
sa.text("SELECT id, open_type FROM admin_power WHERE id = :id"),
|
sa.text("SELECT id FROM admin_power WHERE id = :i"), {"i": preferred}
|
||||||
{"id": mid},
|
|
||||||
).fetchone()
|
).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:
|
def find_by_code(code):
|
||||||
conn.execute(
|
if not code:
|
||||||
sa.text(
|
return None
|
||||||
"INSERT INTO admin_power "
|
return conn.execute(
|
||||||
"(id, name, type, code, url, open_type, parent_id, icon, sort, "
|
sa.text("SELECT id FROM admin_power WHERE code = :c"), {"c": code}
|
||||||
" enable, create_time, update_time) "
|
).scalar()
|
||||||
"VALUES (:id, :name, :type, :code, :url, :open_type, :parent_id, "
|
|
||||||
" :icon, :sort, :enable, :ct, :ut)"
|
def find_by_name_type(name, mtype):
|
||||||
),
|
return conn.execute(
|
||||||
{
|
sa.text("SELECT id FROM admin_power WHERE name = :n AND type = :t"),
|
||||||
"id": mid, "name": name, "type": mtype, "code": code,
|
{"n": name, "t": mtype},
|
||||||
"url": url, "open_type": open_type, "parent_id": parent_id,
|
).scalar()
|
||||||
"icon": icon, "sort": sort, "enable": enable,
|
|
||||||
"ct": now, "ut": now,
|
id_map = {} # 迁移里声明的固定 id -> 实际落库的 id(用于修正 parent_id)
|
||||||
},
|
|
||||||
)
|
for mid, name, mtype, code, url, open_type, parent_id, icon, sort, enable in MENUS:
|
||||||
elif not row[1] and open_type:
|
found = find_by_code(code) or find_by_name_type(name, mtype)
|
||||||
# 历史数据:菜单已存在但打开方式为空,补齐 _iframe
|
if found is not None:
|
||||||
conn.execute(
|
id_map[mid] = int(found)
|
||||||
sa.text("UPDATE admin_power SET open_type = :ot WHERE id = :id"),
|
if open_type:
|
||||||
{"ot": open_type, "id": mid},
|
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():
|
def downgrade():
|
||||||
|
|||||||
Reference in New Issue
Block a user