feat(admin, miniapp): 优化数据库迁移逻辑与小程序图片资源处理

- 为后端Admin新增migration_compat工具,支持离线迁移时的基线资源检查,重构全部alembic迁移脚本适配离线模式,避免重复创建已存在的数据库表和字段
- 完善初始数据库迁移脚本,添加默认部门种子数据,新增ConciergeAdvisor详情字段从JSON到JSONB的类型转换迁移
- 小程序端新增图片资源解析工具resolveNetworkImage,实现本地静态资源到CDN的自动映射,添加图片加载容错降级机制
- 完善HeroSlide类型定义,新增actionLabel字段,优化首页轮播组件的文本渲染与错误处理逻辑
- 新增对应测试用例验证迁移路径正确性与前端数据处理逻辑
- 新增临时 brainstorm UI对比草稿与开发临时状态文件
This commit is contained in:
duanshuwen committed 2026-08-27 11:37:03 +08:00
1 parent 96bcc7c032
commit 4e04feda5b
27 files changed
+2695 -377

No files matched your search

@@ -2,8 +2,9 @@
from datetime import datetime, timezone
from alembic import op
from alembic import context, op
import sqlalchemy as sa
from sqlalchemy import inspect
revision = "0025_admin_rbac"
@@ -37,10 +38,40 @@ MENU_ROWS = (
)
def is_offline_mode() -> bool:
try:
return context.is_offline_mode()
except (NameError, AttributeError):
return False
def _existing_table_names() -> set[str]:
if is_offline_mode():
# The initial baseline creates the current RBAC tables before this
# revision runs, but an offline connection cannot inspect them.
return set(RBAC_TABLES)
return set(inspect(op.get_bind()).get_table_names())
def _create_table_if_missing(existing_tables: set[str], table_name: str, *elements) -> None:
if table_name in existing_tables:
return
op.create_table(table_name, *elements)
existing_tables.add(table_name)
def _table_has_rows(table_name: str) -> bool:
if is_offline_mode():
return False
return op.get_bind().execute(sa.text(f'SELECT 1 FROM "{table_name}" LIMIT 1')).first() is not None
def upgrade() -> None:
now = datetime.now(timezone.utc).replace(tzinfo=None)
existing_tables = _existing_table_names()
op.create_table(
_create_table_if_missing(
existing_tables,
"AdminRole",
sa.Column("id", sa.String(), nullable=False),
sa.Column("name", sa.String(), nullable=False),
@@ -53,7 +84,8 @@ def upgrade() -> None:
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint("code", name="uq_AdminRole_code"),
)
op.create_table(
_create_table_if_missing(
existing_tables,
"AdminDepartment",
sa.Column("id", sa.String(), nullable=False),
sa.Column("parentId", sa.String(), nullable=True),
@@ -65,7 +97,8 @@ def upgrade() -> None:
sa.ForeignKeyConstraint(["parentId"], ["AdminDepartment.id"], ondelete="SET NULL"),
sa.PrimaryKeyConstraint("id"),
)
op.create_table(
_create_table_if_missing(
existing_tables,
"AdminMenu",
sa.Column("id", sa.String(), nullable=False),
sa.Column("parentId", sa.String(), nullable=True),
@@ -84,7 +117,8 @@ def upgrade() -> None:
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint("permissionCode", name="uq_AdminMenu_permissionCode"),
)
op.create_table(
_create_table_if_missing(
existing_tables,
"AdminUserRole",
sa.Column("userId", sa.String(), nullable=False),
sa.Column("roleId", sa.String(), nullable=False),
@@ -93,7 +127,8 @@ def upgrade() -> None:
sa.ForeignKeyConstraint(["userId"], ["AdminUser.id"], ondelete="CASCADE"),
sa.PrimaryKeyConstraint("userId", "roleId"),
)
op.create_table(
_create_table_if_missing(
existing_tables,
"AdminRoleMenu",
sa.Column("roleId", sa.String(), nullable=False),
sa.Column("menuId", sa.String(), nullable=False),
@@ -102,7 +137,8 @@ def upgrade() -> None:
sa.ForeignKeyConstraint(["roleId"], ["AdminRole.id"], ondelete="CASCADE"),
sa.PrimaryKeyConstraint("roleId", "menuId"),
)
op.create_table(
_create_table_if_missing(
existing_tables,
"AdminUserDepartment",
sa.Column("userId", sa.String(), nullable=False),
sa.Column("deptId", sa.String(), nullable=False),
@@ -111,7 +147,8 @@ def upgrade() -> None:
sa.ForeignKeyConstraint(["userId"], ["AdminUser.id"], ondelete="CASCADE"),
sa.PrimaryKeyConstraint("userId", "deptId"),
)
op.create_table(
_create_table_if_missing(
existing_tables,
"AdminRoleDepartment",
sa.Column("roleId", sa.String(), nullable=False),
sa.Column("deptId", sa.String(), nullable=False),
@@ -124,15 +161,18 @@ def upgrade() -> None:
role_table = sa.table("AdminRole", sa.column("id", sa.String), sa.column("name", sa.String), sa.column("code", sa.String), sa.column("dataScope", sa.String), sa.column("isActive", sa.Boolean), sa.column("createdAt", sa.DateTime), sa.column("updatedAt", sa.DateTime))
dept_table = sa.table("AdminDepartment", sa.column("id", sa.String), sa.column("name", sa.String), sa.column("sortOrder", sa.Integer), sa.column("isActive", sa.Boolean), sa.column("createdAt", sa.DateTime), sa.column("updatedAt", sa.DateTime))
menu_table = sa.table("AdminMenu", sa.column("id", sa.String), sa.column("name", sa.String), sa.column("type", sa.String), sa.column("path", sa.String), sa.column("componentKey", sa.String), sa.column("permissionCode", sa.String), sa.column("sortOrder", sa.Integer), sa.column("isVisible", sa.Boolean), sa.column("isActive", sa.Boolean), sa.column("createdAt", sa.DateTime), sa.column("updatedAt", sa.DateTime))
op.bulk_insert(role_table, [{"id": SUPER_ROLE_ID, "name": "超级管理员", "code": "super_admin", "dataScope": "all", "isActive": True, "createdAt": now, "updatedAt": now}])
op.bulk_insert(dept_table, [{"id": DEFAULT_DEPT_ID, "name": "默认部门", "sortOrder": 0, "isActive": True, "createdAt": now, "updatedAt": now}])
op.bulk_insert(
menu_table,
[
{"id": menu_id, "name": name, "type": menu_type, "path": path, "componentKey": component, "permissionCode": permission, "sortOrder": sort_order, "isVisible": True, "isActive": True, "createdAt": now, "updatedAt": now}
for menu_id, name, menu_type, path, component, permission, sort_order in MENU_ROWS
],
)
if not _table_has_rows("AdminRole"):
op.bulk_insert(role_table, [{"id": SUPER_ROLE_ID, "name": "超级管理员", "code": "super_admin", "dataScope": "all", "isActive": True, "createdAt": now, "updatedAt": now}])
if not _table_has_rows("AdminDepartment"):
op.bulk_insert(dept_table, [{"id": DEFAULT_DEPT_ID, "name": "默认部门", "sortOrder": 0, "isActive": True, "createdAt": now, "updatedAt": now}])
if not _table_has_rows("AdminMenu"):
op.bulk_insert(
menu_table,
[
{"id": menu_id, "name": name, "type": menu_type, "path": path, "componentKey": component, "permissionCode": permission, "sortOrder": sort_order, "isVisible": True, "isActive": True, "createdAt": now, "updatedAt": now}
for menu_id, name, menu_type, path, component, permission, sort_order in MENU_ROWS
],
)
op.execute(sa.text('INSERT INTO "AdminRoleMenu" ("roleId", "menuId", "createdAt") SELECT :role_id, "id", :created_at FROM "AdminMenu" ON CONFLICT DO NOTHING').bindparams(role_id=SUPER_ROLE_ID, created_at=now))
op.execute(sa.text('INSERT INTO "AdminRoleDepartment" ("roleId", "deptId", "createdAt") VALUES (:role_id, :dept_id, :created_at) ON CONFLICT DO NOTHING').bindparams(role_id=SUPER_ROLE_ID, dept_id=DEFAULT_DEPT_ID, created_at=now))
op.execute(sa.text('INSERT INTO "AdminUserRole" ("userId", "roleId", "createdAt") SELECT "id", :role_id, :created_at FROM "AdminUser" ON CONFLICT DO NOTHING').bindparams(role_id=SUPER_ROLE_ID, created_at=now))