feat(admin, miniapp): 优化数据库迁移逻辑与小程序图片资源处理
- 为后端Admin新增migration_compat工具,支持离线迁移时的基线资源检查,重构全部alembic迁移脚本适配离线模式,避免重复创建已存在的数据库表和字段 - 完善初始数据库迁移脚本,添加默认部门种子数据,新增ConciergeAdvisor详情字段从JSON到JSONB的类型转换迁移 - 小程序端新增图片资源解析工具resolveNetworkImage,实现本地静态资源到CDN的自动映射,添加图片加载容错降级机制 - 完善HeroSlide类型定义,新增actionLabel字段,优化首页轮播组件的文本渲染与错误处理逻辑 - 新增对应测试用例验证迁移路径正确性与前端数据处理逻辑 - 新增临时 brainstorm UI对比草稿与开发临时状态文件
This commit is contained in:
1 parent
96bcc7c032
commit
4e04feda5b
27 files changed
+2695
-377
No files matched your search
@@ -1,7 +1,8 @@
|
||||
"""Add department and creator ownership to operator-managed resources."""
|
||||
|
||||
from alembic import op
|
||||
from alembic import context, op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy import inspect
|
||||
|
||||
|
||||
revision = "0026_admin_ownership"
|
||||
@@ -31,56 +32,140 @@ OWNED_TABLES = (
|
||||
)
|
||||
|
||||
|
||||
def is_offline_mode() -> bool:
|
||||
try:
|
||||
return context.is_offline_mode()
|
||||
except (NameError, AttributeError):
|
||||
return False
|
||||
|
||||
|
||||
def _schema_snapshot() -> tuple[set[str], dict[str, dict[str, dict]], dict[str, set[str]], dict[str, list[dict]]]:
|
||||
if is_offline_mode():
|
||||
return set(), {}, {}, {}
|
||||
|
||||
inspector = inspect(op.get_bind())
|
||||
table_names = set(inspector.get_table_names())
|
||||
columns = {
|
||||
table_name: {column["name"]: column for column in inspector.get_columns(table_name)}
|
||||
for table_name in table_names
|
||||
}
|
||||
indexes = {
|
||||
table_name: {
|
||||
index["name"]
|
||||
for index in inspector.get_indexes(table_name)
|
||||
if index.get("name")
|
||||
}
|
||||
for table_name in table_names
|
||||
}
|
||||
foreign_keys = {
|
||||
table_name: inspector.get_foreign_keys(table_name)
|
||||
for table_name in table_names
|
||||
}
|
||||
return table_names, columns, indexes, foreign_keys
|
||||
|
||||
|
||||
def _has_foreign_key(
|
||||
foreign_keys: list[dict],
|
||||
constrained_column: str,
|
||||
referred_table: str,
|
||||
) -> bool:
|
||||
return any(
|
||||
foreign_key.get("constrained_columns") == [constrained_column]
|
||||
and foreign_key.get("referred_table") == referred_table
|
||||
and foreign_key.get("referred_columns") == ["id"]
|
||||
for foreign_key in foreign_keys
|
||||
)
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
table_names, columns, indexes, foreign_keys = _schema_snapshot()
|
||||
|
||||
for table_name in OWNED_TABLES:
|
||||
op.add_column(
|
||||
table_name,
|
||||
sa.Column(
|
||||
"deptId",
|
||||
sa.String(),
|
||||
nullable=True,
|
||||
server_default=sa.text(f"'{DEFAULT_DEPT_ID}'"),
|
||||
),
|
||||
)
|
||||
op.add_column(table_name, sa.Column("createdById", sa.String(), nullable=True))
|
||||
op.create_index(f"ix_{table_name}_deptId", table_name, ["deptId"])
|
||||
op.create_index(f"ix_{table_name}_createdById", table_name, ["createdById"])
|
||||
op.create_foreign_key(
|
||||
f"fk_{table_name}_deptId",
|
||||
table_name,
|
||||
"AdminDepartment",
|
||||
["deptId"],
|
||||
["id"],
|
||||
ondelete="RESTRICT",
|
||||
)
|
||||
op.create_foreign_key(
|
||||
f"fk_{table_name}_createdById",
|
||||
table_name,
|
||||
"AdminUser",
|
||||
["createdById"],
|
||||
["id"],
|
||||
ondelete="SET NULL",
|
||||
)
|
||||
if table_name not in table_names:
|
||||
continue
|
||||
|
||||
table_columns = columns[table_name]
|
||||
added_dept_column = "deptId" not in table_columns
|
||||
added_creator_column = "createdById" not in table_columns
|
||||
if added_dept_column:
|
||||
op.add_column(
|
||||
table_name,
|
||||
sa.Column(
|
||||
"deptId",
|
||||
sa.String(),
|
||||
nullable=True,
|
||||
server_default=sa.text(f"'{DEFAULT_DEPT_ID}'"),
|
||||
),
|
||||
)
|
||||
table_columns["deptId"] = {"name": "deptId", "nullable": True}
|
||||
if added_creator_column:
|
||||
op.add_column(table_name, sa.Column("createdById", sa.String(), nullable=True))
|
||||
table_columns["createdById"] = {"name": "createdById", "nullable": True}
|
||||
|
||||
table_indexes = indexes[table_name]
|
||||
if f"ix_{table_name}_deptId" not in table_indexes:
|
||||
op.create_index(f"ix_{table_name}_deptId", table_name, ["deptId"])
|
||||
table_indexes.add(f"ix_{table_name}_deptId")
|
||||
if f"ix_{table_name}_createdById" not in table_indexes:
|
||||
op.create_index(f"ix_{table_name}_createdById", table_name, ["createdById"])
|
||||
table_indexes.add(f"ix_{table_name}_createdById")
|
||||
|
||||
table_foreign_keys = foreign_keys[table_name]
|
||||
if not _has_foreign_key(table_foreign_keys, "deptId", "AdminDepartment"):
|
||||
op.create_foreign_key(
|
||||
f"fk_{table_name}_deptId",
|
||||
table_name,
|
||||
"AdminDepartment",
|
||||
["deptId"],
|
||||
["id"],
|
||||
ondelete="RESTRICT",
|
||||
)
|
||||
table_foreign_keys.append(
|
||||
{
|
||||
"constrained_columns": ["deptId"],
|
||||
"referred_table": "AdminDepartment",
|
||||
"referred_columns": ["id"],
|
||||
}
|
||||
)
|
||||
if not _has_foreign_key(table_foreign_keys, "createdById", "AdminUser"):
|
||||
op.create_foreign_key(
|
||||
f"fk_{table_name}_createdById",
|
||||
table_name,
|
||||
"AdminUser",
|
||||
["createdById"],
|
||||
["id"],
|
||||
ondelete="SET NULL",
|
||||
)
|
||||
table_foreign_keys.append(
|
||||
{
|
||||
"constrained_columns": ["createdById"],
|
||||
"referred_table": "AdminUser",
|
||||
"referred_columns": ["id"],
|
||||
}
|
||||
)
|
||||
quoted_table = f'"{table_name}"'
|
||||
op.execute(
|
||||
sa.text(f'UPDATE {quoted_table} SET "deptId" = :dept_id WHERE "deptId" IS NULL').bindparams(
|
||||
dept_id=DEFAULT_DEPT_ID
|
||||
needs_dept_backfill = added_dept_column or table_columns["deptId"].get("nullable") is True
|
||||
if needs_dept_backfill:
|
||||
op.execute(
|
||||
sa.text(f'UPDATE {quoted_table} SET "deptId" = :dept_id WHERE "deptId" IS NULL').bindparams(
|
||||
dept_id=DEFAULT_DEPT_ID
|
||||
)
|
||||
)
|
||||
)
|
||||
op.alter_column(
|
||||
table_name,
|
||||
"deptId",
|
||||
existing_type=sa.String(),
|
||||
nullable=False,
|
||||
server_default=sa.text(f"'{DEFAULT_DEPT_ID}'"),
|
||||
)
|
||||
op.execute(
|
||||
sa.text(
|
||||
f'UPDATE {quoted_table} SET "createdById" = '
|
||||
'(SELECT "id" FROM "AdminUser" ORDER BY "createdAt" ASC LIMIT 1) '
|
||||
'WHERE "createdById" IS NULL'
|
||||
op.alter_column(
|
||||
table_name,
|
||||
"deptId",
|
||||
existing_type=sa.String(),
|
||||
nullable=False,
|
||||
server_default=sa.text(f"'{DEFAULT_DEPT_ID}'"),
|
||||
)
|
||||
if added_creator_column:
|
||||
op.execute(
|
||||
sa.text(
|
||||
f'UPDATE {quoted_table} SET "createdById" = '
|
||||
'(SELECT "id" FROM "AdminUser" ORDER BY "createdAt" ASC LIMIT 1) '
|
||||
'WHERE "createdById" IS NULL'
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
|
||||
Reference in new issue
Block a user