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

134 lines
4.7 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.

"""Create the concierge advisor domain.
Revision ID: 0016_concierge_advisors
Revises: 0015_wanfa
"""
from datetime import datetime
import json
from alembic import context, op
import sqlalchemy as sa
from sqlalchemy import inspect
from app.migration_compat import baseline_table_names
revision = "0016_concierge_advisors"
down_revision = "0015_wanfa"
branch_labels = None
depends_on = None
SEED_ADVISORS = (
{
"id": "advisor-amanda",
"avatar": "https://images.unsplash.com/photo-1494790108377-be9c29b29330?auto=format&fit=crop&fm=jpg&q=80&w=240&h=240",
"name": "Amanda",
"role": "SENIOR TRAVEL ADVISOR",
"details": [
{"icon": "calendar", "label": "服务经验:8年"},
{"icon": "navigate", "label": "擅长领域:瀑降 / 探洞"},
],
"qrImage": "https://api.qrserver.com/v1/create-qr-code/?size=300x300&data=WonderQ%20Concierge",
"isActive": True,
"sortOrder": 0,
},
{
"id": "advisor-mia",
"avatar": "https://images.unsplash.com/photo-1531123897727-8f129e1688ce?auto=format&fit=crop&fm=jpg&q=80&w=240&h=240",
"name": "Mia",
"role": "LIFESTYLE TRAVEL ADVISOR",
"details": [
{"icon": "calendar", "label": "服务经验:6年"},
{"icon": "navigate", "label": "擅长领域:溯溪 / 人文探索"},
],
"qrImage": "https://api.qrserver.com/v1/create-qr-code/?size=300x300&data=WonderQ%20Concierge",
"isActive": True,
"sortOrder": 1,
},
{
"id": "advisor-leo",
"avatar": "https://images.unsplash.com/photo-1500648767791-00dcc994a43e?auto=format&fit=crop&fm=jpg&q=80&w=240&h=240",
"name": "Leo",
"role": "WILD TRAVEL ADVISOR",
"details": [
{"icon": "calendar", "label": "服务经验:9年"},
{"icon": "navigate", "label": "擅长领域:洞穴探险 / 山地徒步"},
],
"qrImage": "https://api.qrserver.com/v1/create-qr-code/?size=300x300&data=WonderQ%20Concierge",
"isActive": True,
"sortOrder": 2,
},
)
def is_offline_mode() -> bool:
try:
return context.is_offline_mode()
except (NameError, AttributeError):
return False
def _seed_advisors() -> None:
offline = is_offline_mode()
details_type = sa.Text() if offline else sa.JSON()
table = sa.table(
"ConciergeAdvisor",
sa.column("id", sa.String()),
sa.column("avatar", sa.String()),
sa.column("name", sa.String()),
sa.column("role", sa.String()),
sa.column("details", details_type),
sa.column("qrImage", sa.String()),
sa.column("isActive", sa.Boolean()),
sa.column("sortOrder", sa.Integer()),
sa.column("createdAt", sa.DateTime()),
sa.column("updatedAt", sa.DateTime()),
)
timestamp = datetime(2026, 1, 1)
rows = [{**advisor, "createdAt": timestamp, "updatedAt": timestamp} for advisor in SEED_ADVISORS]
if offline:
rows = [{**advisor, "details": json.dumps(advisor["details"], ensure_ascii=False)} for advisor in rows]
op.bulk_insert(table, rows)
def upgrade() -> None:
if is_offline_mode():
existing_tables = baseline_table_names()
else:
bind = op.get_bind()
existing_tables = set(inspect(bind).get_table_names())
if "ConciergeAdvisor" not in existing_tables:
op.create_table(
"ConciergeAdvisor",
sa.Column("id", sa.String(), nullable=False),
sa.Column("avatar", sa.String(), nullable=False),
sa.Column("name", sa.String(), nullable=False),
sa.Column("role", sa.String(), nullable=False),
sa.Column("details", sa.JSON(), nullable=False),
sa.Column("qrImage", sa.String(), nullable=False),
sa.Column("isActive", sa.Boolean(), nullable=False, server_default=sa.true()),
sa.Column("sortOrder", sa.Integer(), nullable=False, server_default="0"),
sa.Column("createdAt", sa.DateTime(), nullable=False),
sa.Column("updatedAt", sa.DateTime(), nullable=False),
sa.PrimaryKeyConstraint("id"),
)
op.create_index("ix_ConciergeAdvisor_isActive", "ConciergeAdvisor", ["isActive"])
op.create_index("ix_ConciergeAdvisor_sortOrder", "ConciergeAdvisor", ["sortOrder"])
if is_offline_mode():
_seed_advisors()
return
advisor_table = sa.table("ConciergeAdvisor", sa.column("id", sa.String()))
if bind.execute(sa.select(advisor_table.c.id).limit(1)).first() is None:
_seed_advisors()
def downgrade() -> None:
bind = op.get_bind()
existing_tables = set(inspect(bind).get_table_names())
if "ConciergeAdvisor" in existing_tables:
op.drop_table("ConciergeAdvisor")