Files
pear-admin-flask/migrations/versions/d4e5f6a7b8c9_add_cookie_manager_menus.py
T
bwstudio 1b68363bfd 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 条,不重复插入
2026-09-10 18:59:31 +08:00

131 lines
4.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""add cookie manager menus
「Cookie管理」一级菜单 + 二级「登录用户」/「登录网站」及各权限点,
此前只在本地库里手工 INSERT 过,从未随迁移分发。
由于 data/ 目录被 .gitignore 排除、部署环境数据库是独立 volume,
只同步代码无法带上这些菜单行,导致线上后台看不到 Cookie 管理模块。
本迁移以幂等方式补齐:
- 记录不存在 → 插入
- 已存在但 open_type 为空 → 补齐为 _iframe(兼容早期手工插入的数据)
不依赖数据库方言的 UPSERT 语法,SQLite / MySQL 均可执行。
Revision ID: d4e5f6a7b8c9
Revises: b2c3d4e5f6a7
"""
from alembic import op
import sqlalchemy as sa
import datetime
# revision identifiers, used by Alembic.
revision = 'd4e5f6a7b8c9'
down_revision = 'b2c3d4e5f6a7'
branch_labels = None
depends_on = None
# (id, name, type, code, url, open_type, parent_id, icon, sort, enable)
MENUS = [
(76, 'Cookie管理', '0', '', '', '', 0, 'layui-icon layui-icon-ear-phone', 5, 1),
(77, '登录用户', '1', 'system:cookie-user:main', '/system/cookie-user/', '_iframe',
76, 'layui-icon layui-icon-username', 1, 1),
(78, '登录用户新增', '2', 'system:cookie-user:add', '', '', 77, '', 1, 1),
(79, '登录用户编辑', '2', 'system:cookie-user:edit', '', '', 77, '', 2, 1),
(80, '登录用户删除', '2', 'system:cookie-user:remove', '', '', 77, '', 3, 1),
(81, '登录网站', '1', 'system:cookie-site:main', '/system/cookie-site/', '_iframe',
76, 'layui-icon layui-icon-web', 2, 1),
(82, '登录网站新增', '2', 'system:cookie-site:add', '', '', 81, '', 1, 1),
(83, '登录网站编辑', '2', 'system:cookie-site:edit', '', '', 81, '', 2, 1),
(84, '登录网站删除', '2', 'system:cookie-site:remove', '', '', 81, '', 3, 1),
]
def upgrade():
"""幂等补齐 Cookie 管理菜单。
与"仅按固定 id 判断"不同:先按业务键(code / name+type)查找;
固定 id 若已被其它菜单占用则自动顺延到 max(id)+1,
避免线上 id 分配与本地不一致时静默跳过、菜单仍然不出现。
"""
conn = op.get_bind()
now = datetime.datetime.now()
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
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():
conn = op.get_bind()
ids = [m[0] for m in MENUS]
# 先清角色-权限关联,避免外键/脏数据残留
conn.execute(
sa.text("DELETE FROM admin_role_power WHERE power_id IN :ids").bindparams(
sa.bindparam("ids", expanding=True)
),
{"ids": ids},
)
conn.execute(
sa.text("DELETE FROM admin_power WHERE id IN :ids").bindparams(
sa.bindparam("ids", expanding=True)
),
{"ids": ids},
)