hulai-admin-api/scripts/migrate_json_to_db.py
刘涛 8c0a5f489d Initial commit: hulai admin API backend
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-22 22:01:23 +08:00

525 行
20 KiB
Python

此文件含有模棱两可的 Unicode 字符

此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。

from __future__ import annotations
"""将现有 JSON 数据导入 MySQL
Usage: cd hulai-admin-api && python -m scripts.migrate_json_to_db
"""
import json
import os
import sys
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from app.config import settings
from app.database import SessionLocal, engine, Base
from app.models import *
def load_json(filename):
path = os.path.join(settings.NUXT_DATA_PATH, filename)
if not os.path.exists(path):
print(f" ⚠️ 文件不存在: {path},跳过")
return None
with open(path, "r", encoding="utf-8") as f:
return json.load(f)
def migrate_brand(db):
data = load_json("brand.json")
if not data:
return
if db.query(Brand).first():
print(" brand: 已存在,跳过")
return
brand = Brand(
name=data["name"], full_name=data.get("fullName"), domain=data.get("domain"),
url=data.get("url"), slogan_emotional=data.get("slogan", {}).get("emotional"),
slogan_functional=data.get("slogan", {}).get("functional"),
icp_entity=data.get("icpEntity"), icp=data.get("icp"),
icp_url=data.get("icpUrl"), e_contract=data.get("eContract"),
)
db.add(brand)
db.flush()
for i, s in enumerate(data.get("trustStats", [])):
db.add(BrandTrustStat(brand_id=brand.id, value=s["value"], unit=s.get("unit"), label=s["label"], sort_order=i))
for i, d in enumerate(data.get("differentiators", [])):
db.add(BrandDifferentiator(brand_id=brand.id, title=d["title"], description=d.get("description"), sort_order=i))
print(" brand: ✓")
def migrate_products(db):
data = load_json("products.json")
if not data:
return
if db.query(ProductConfig).first():
print(" products: 已存在,跳过")
return
config = ProductConfig(narrative=data.get("narrative"), pricing_philosophy=data.get("pricingPhilosophy"))
db.add(config)
db.flush()
for i, v in enumerate(data.get("versions", [])):
pv = ProductVersion(
config_id=config.id, version_id=v["id"], name=v["name"],
days=v["days"], nights=v["nights"], audience=v.get("audience"),
description=v.get("description"), tag=v.get("tag"), sort_order=i,
)
db.add(pv)
db.flush()
for j, h in enumerate(v.get("highlights", [])):
db.add(ProductVersionHighlight(version_id=pv.id, text=h, sort_order=j))
camp_data = data.get("summerCamp")
if camp_data:
camp = SummerCamp(
config_id=config.id, name=camp_data.get("name"),
positioning=camp_data.get("positioning"),
days=camp_data.get("days"), nights=camp_data.get("nights"),
sessions_json=camp_data.get("sessions"),
difference_from_v9=camp_data.get("differenceFromV9"),
)
db.add(camp)
db.flush()
for i, p in enumerate(camp_data.get("principles", [])):
db.add(SummerCampPrinciple(camp_id=camp.id, text=p, sort_order=i))
for i, a in enumerate(camp_data.get("activities", [])):
db.add(SummerCampActivity(camp_id=camp.id, text=a, sort_order=i))
for i, it in enumerate(camp_data.get("itinerary", [])):
db.add(SummerCampItinerary(camp_id=camp.id, text=it, sort_order=i))
for i, f in enumerate(camp_data.get("faq", [])):
db.add(SummerCampFaq(camp_id=camp.id, question=f.get("q") or f.get("question", ""), answer=f.get("a") or f.get("answer", ""), sort_order=i))
guide_data = data.get("selectionGuide")
if guide_data:
db.add(SelectionGuide(
config_id=config.id,
by_vacation_length=guide_data.get("byVacationLength"),
by_child_age=guide_data.get("byChildAge"),
by_preference=guide_data.get("byPreference"),
))
print(" products: ✓")
def migrate_faq(db):
data = load_json("faq.json")
if not data:
return
if db.query(FaqCategory).first():
print(" faq: 已存在,跳过")
return
for i, cat in enumerate(data.get("categories", [])):
c = FaqCategory(category_id=cat["id"], name=cat["name"], sort_order=i)
db.add(c)
db.flush()
for j, q in enumerate(cat.get("questions", [])):
db.add(FaqQuestion(
category_id=c.id, question_id=q["id"], question=q["question"],
answer=q["answer"], related_links=q.get("relatedLinks"), sort_order=j,
))
print(" faq: ✓")
def migrate_reviews(db):
data = load_json("reviews.json")
if not data:
return
if db.query(Review).first():
print(" reviews: 已存在,跳过")
return
summary = data.get("summary", {})
db.add(ReviewSummary(
total_count=summary.get("totalCount", 0),
approval_rate=summary.get("approvalRate"),
keywords=summary.get("keywords"),
))
for i, r in enumerate(data.get("items", [])):
db.add(Review(
nickname=r["nickname"], travel_date=r.get("travelDate"),
product_version=r.get("productVersion"), screenshot=r.get("screenshot"),
content=r["content"], scenes=r.get("scenes"), concerns=r.get("concerns"),
sort_order=i, is_visible=True,
))
print(" reviews: ✓")
def migrate_about(db):
data = load_json("about.json")
if not data:
return
if db.query(AboutStory).first():
print(" about: 已存在,跳过")
return
story = data.get("story", {})
db.add(AboutStory(title=story.get("title"), content=story.get("content"), founding_moment=story.get("foundingMoment")))
for i, s in enumerate(data.get("subsidiaries", [])):
db.add(AboutSubsidiary(name=s["name"], role=s.get("role"), established=s.get("established"), sort_order=i))
for i, c in enumerate(data.get("certifications", [])):
db.add(AboutCertification(title=c["title"], detail=c.get("detail"), sort_order=i))
tm = data.get("trademark", {})
if tm:
db.add(AboutTrademark(name=tm.get("name"), scope=tm.get("scope"), holder=tm.get("holder"), description=tm.get("description")))
for i, c in enumerate(data.get("copyrights", [])):
db.add(AboutCopyright(
name=c["name"], reg_no=c.get("regNo"), category=c.get("category"),
holder=c.get("holder"), date=c.get("date"), description=c.get("description"), sort_order=i,
))
for i, g in enumerate(data.get("guarantees", [])):
db.add(AboutGuarantee(title=g["title"], detail=g.get("detail"), sort_order=i))
xhs = data.get("xiaohongshu", {})
if xhs:
db.add(AboutXiaohongshu(
account=xhs.get("account"), verified=xhs.get("verified", False),
verified_type=xhs.get("verifiedType"), followers=xhs.get("followers"),
likes=xhs.get("likes"), awards=xhs.get("awards"), tagline=xhs.get("tagline"),
tags=xhs.get("tags"), description=xhs.get("description"),
))
team = data.get("team", {})
if team:
db.add(AboutTeam(summary=team.get("summary")))
culture = data.get("culture", {})
if culture:
c = AboutCulture(transparency=culture.get("transparency"))
db.add(c)
db.flush()
for i, v in enumerate(culture.get("values", [])):
db.add(AboutCultureValue(culture_id=c.id, name=v["name"], expression=v.get("expression"), sort_order=i))
print(" about: ✓")
def migrate_contact(db):
data = load_json("contact.json")
if not data:
return
if db.query(ContactChannel).first():
print(" contact: 已存在,跳过")
return
for i, ch in enumerate(data.get("channels", [])):
db.add(ContactChannel(
type=ch["type"], label=ch["label"], value=ch.get("value"),
qr_image=ch.get("qrImage"), is_primary=ch.get("primary", False),
description=ch.get("description"), sort_order=i,
))
notice = data.get("securityNotice")
if notice:
db.add(ContactConfig(security_notice=notice))
print(" contact: ✓")
def migrate_seo(db):
data = load_json("seo.json")
if not data:
return
if db.query(SeoPage).first():
print(" seo: 已存在,跳过")
return
for key, page in data.get("pages", {}).items():
db.add(SeoPage(
page_key=key, title=page.get("title"), description=page.get("description"),
h1=page.get("h1"), og_image=page.get("ogImage"),
))
print(" seo: ✓")
def migrate_navigation(db):
data = load_json("navigation.json")
if not data:
return
if db.query(NavHeader).first():
print(" navigation: 已存在,跳过")
return
for i, h in enumerate(data.get("header", [])):
db.add(NavHeader(text=h["text"], to_path=h["to"], sort_order=i))
for i, g in enumerate(data.get("footer", [])):
group = NavFooterGroup(title=g["title"], sort_order=i)
db.add(group)
db.flush()
for j, l in enumerate(g.get("links", [])):
db.add(NavFooterLink(group_id=group.id, text=l["text"], to_path=l["to"], sort_order=j))
print(" navigation: ✓")
def migrate_versions(db):
data = load_json("versions.json")
if not data:
return
if db.query(VersionConfig).first():
print(" versions: 已存在,跳过")
return
stats = data.get("stats", {})
quote = data.get("quote", {})
config = VersionConfig(
stats_iterations=stats.get("iterations"), stats_years=stats.get("years"),
stats_guests=stats.get("guests"),
quote_text=quote.get("text") if quote else None,
quote_author=quote.get("author") if quote else None,
)
db.add(config)
db.flush()
for i, u in enumerate(data.get("upgrades2026", [])):
db.add(VersionUpgrade(config_id=config.id, tag=u.get("tag"), name=u.get("name"), description=u.get("description"), reason=u.get("reason"), sort_order=i))
for i, h in enumerate(data.get("highlights", [])):
db.add(VersionHighlight(config_id=config.id, label=h.get("label"), text=h.get("text"), sort_order=i))
cmp = data.get("compareV8V9", {})
if cmp:
db.add(VersionCompare(config_id=config.id, headers=cmp.get("headers"), rows=cmp.get("rows")))
for i, t in enumerate(data.get("timeline", [])):
tl = VersionTimeline(
config_id=config.id, version=t["version"], date=t.get("date"),
title=t.get("title"), reason=t.get("reason"), sort_order=i,
)
db.add(tl)
db.flush()
for j, ch in enumerate(t.get("changes", [])):
db.add(VersionTimelineChange(timeline_id=tl.id, type=ch["type"], text=ch["text"], sort_order=j))
for i, p in enumerate(data.get("philosophy", [])):
db.add(VersionPhilosophy(config_id=config.id, label=p.get("label"), text=p.get("text"), sort_order=i))
print(" versions: ✓")
def migrate_guides(db):
data = load_json("guides.json")
if not data:
return
if db.query(GuideConfig).first():
print(" guides: 已存在,跳过")
return
config = GuideConfig(page_intro=data.get("pageIntro"))
db.add(config)
db.flush()
for i, s in enumerate(data.get("sections", [])):
# Extract common fields, store the rest as JSON data
common_keys = {"id", "title", "subtitle", "icon", "content"}
extra_data = {k: v for k, v in s.items() if k not in common_keys}
db.add(GuideSection(
config_id=config.id, section_id=s["id"], title=s.get("title"),
subtitle=s.get("subtitle"), icon=s.get("icon"), content=s.get("content"),
data=extra_data if extra_data else None, sort_order=i,
))
print(" guides: ✓")
def migrate_images(db):
data = load_json("images.json")
if not data:
return
if db.query(SiteImage).first():
print(" images: 已存在,跳过")
return
# 图片元数据label 和建议尺寸
meta = {
("logo", "main"): ("品牌Logo", "200x200px正方形透明PNG", "网站头部和底部的品牌标识"),
("logo", "square"): ("方形Logo", "200x200px正方形", "社交媒体头像等场景"),
("hero", "background"): ("首页大图", "1920x1080px16:9横图", "首页顶部全屏背景图"),
("brandStory", "photo"): ("品牌故事配图", "800x600px4:3横图", "关于我们-品牌故事旁配图"),
("mascot", "front"): ("IP形象正面", "560x760px竖版透明PNG", "关于我们-IP展示正面形象"),
("mascot", "banner"): ("IP场景横幅", "1600x600px宽横幅", "关于我们-IP草原场景插画"),
("mascot", "stickerQr"): ("表情包二维码", "320x320px正方形", "微信表情包扫码下载"),
("mascot", "xiaomengma"): ("小蒙马IP", "400x400px正方形透明PNG", "夏令营相关页面的小蒙马形象"),
("team", "guideGroup"): ("领队合影", "800x600px4:3横图", "团队介绍-领队团队合影"),
("team", "routeSurvey"): ("踩线照片", "800x600px4:3横图", "团队介绍-线路踩线工作照"),
("team", "guideTraining"): ("培训照片", "800x600px4:3横图", "团队介绍-领队培训照片"),
("team", "ranchStore"): ("牧场团队", "800x600px4:3横图", "团队介绍-牧场商贸团队照"),
("xiaohongshu", "storefront"): ("小红书店铺", "1200x400px3:1横幅", "关于我们-小红书店铺展示横幅"),
("summerCamp", "river"): ("夏令营河景", "800x600px4:3横图", "夏令营页面-河景团照"),
("summerCamp", "grassland"): ("夏令营草原", "800x600px4:3横图", "夏令营页面-草原活动照"),
("summerCamp", "birch"): ("夏令营白桦林", "800x600px4:3横图", "夏令营页面-白桦林研学照"),
("summerCamp", "graduation"): ("夏令营结营", "800x600px4:3横图", "夏令营页面-结营合影"),
}
for group_name, items in data.items():
if isinstance(items, dict):
for key, path in items.items():
m = meta.get((group_name, key), (f"{group_name}.{key}", "", ""))
db.add(SiteImage(
group_name=group_name, image_key=key, image_path=path,
label=m[0], size_hint=m[1], description=m[2],
))
print(" images: ✓")
def _migrate_seasonal(db, season, filename):
data = load_json(filename)
if not data:
return
if db.query(SeasonalProductConfig).filter_by(season=season).first():
print(f" {filename}: 已存在,跳过")
return
config = SeasonalProductConfig(
season=season, narrative=data.get("narrative"), style=data.get("style"),
)
db.add(config)
db.flush()
for i, v in enumerate(data.get("versions", [])):
db.add(SeasonalProductVersion(
season=season, version_id=v["id"], name=v["name"],
days=v["days"], nights=v["nights"], tag=v.get("tag"),
line=v.get("line"), route=v.get("route"), audience=v.get("audience"),
description=v.get("description"), highlights=v.get("highlights", []),
itinerary=v.get("itinerary", []), sort_order=i,
))
for cat_key in ["highlights", "southHighlights", "northHighlights"]:
cat_map = {"highlights": "shared", "southHighlights": "south", "northHighlights": "north"}
cat = cat_map[cat_key]
for i, h in enumerate(data.get(cat_key, [])):
db.add(SeasonalProductHighlight(
season=season, category=cat, title=h["title"],
description=h.get("description"), sort_order=i,
))
for i, t in enumerate(data.get("timeline", [])):
db.add(SeasonalProductTimeline(
season=season, version=t.get("version"), date=t.get("date"),
title=t.get("title"), changes=t.get("changes", []),
reason=t.get("reason"), sort_order=i,
))
print(f" {filename}: ✓")
def migrate_autumn_products(db):
_migrate_seasonal(db, "autumn", "autumn-products.json")
def migrate_winter_products(db):
_migrate_seasonal(db, "winter", "winter-products.json")
def migrate_winter_camp(db):
data = load_json("winter-camp.json")
if not data:
return
if db.query(WinterCampConfig).first():
print(" winter-camp: 已存在,跳过")
return
config = WinterCampConfig(
name=data.get("name"), positioning=data.get("positioning"),
days=data.get("days"), nights=data.get("nights"),
max_families=data.get("maxFamilies"), total_sessions=data.get("totalSessions"),
age_range=data.get("ageRange"), deposit=data.get("deposit"),
season=data.get("season"), route=data.get("route"),
why_hulunbuir=data.get("whyHulunbuir"), closing_note=data.get("closingNote"),
photographer=data.get("photographer"), winter_clothing=data.get("winterClothing"),
camp_advantages=data.get("campAdvantages", []),
service_config=data.get("serviceConfig", []),
camp_essentials=data.get("campEssentials", []),
)
db.add(config)
db.flush()
for i, h in enumerate(data.get("hotels", [])):
db.add(WinterCampHotel(name=h["name"], star=h.get("star"), nights=h.get("nights"), description=h.get("description"), sort_order=i))
for i, d in enumerate(data.get("itinerary", [])):
db.add(WinterCampItinerary(day=d["day"], title=d.get("title"), summary=d.get("summary"), highlights=d.get("highlights", []), hotel=d.get("hotel"), sort_order=i))
for i, f in enumerate(data.get("faq", [])):
db.add(WinterCampFaq(question=f["q"], answer=f["a"], sort_order=i))
print(" winter-camp: ✓")
def migrate_destinations(db):
data = load_json("destinations.json")
if not data:
return
if db.query(DestinationConfig).first():
print(" destinations: 已存在,跳过")
return
closing = data.get("closing", {})
honest = data.get("honestNote", {})
config = DestinationConfig(
title=data.get("title"), subtitle=data.get("subtitle"), intro=data.get("intro"),
closing_title=closing.get("title"), closing_text=closing.get("text"),
data_sources=data.get("dataSources"),
honest_title=honest.get("title"), honest_subtitle=honest.get("subtitle"),
)
db.add(config)
db.flush()
for i, d in enumerate(data.get("destinations", [])):
db.add(DestinationItem(dest_id=d.get("id", f"dest-{i}"), name=d["name"], tag=d.get("tag"), highlight=d.get("highlight", False), sort_order=i))
for i, dim in enumerate(data.get("dimensions", [])):
db.add(DestinationDimension(label=dim["label"], icon=dim.get("icon"), values=dim.get("values", []), sort_order=i))
for i, h in enumerate(honest.get("items", [])):
db.add(DestinationHonestItem(text=h, sort_order=i))
print(" destinations: ✓")
def main():
print("创建数据库表...")
Base.metadata.create_all(bind=engine)
db = SessionLocal()
try:
print("\n开始迁移数据:")
migrate_brand(db)
migrate_products(db)
migrate_faq(db)
migrate_reviews(db)
migrate_about(db)
migrate_contact(db)
migrate_seo(db)
migrate_navigation(db)
migrate_versions(db)
migrate_guides(db)
migrate_images(db)
migrate_autumn_products(db)
migrate_winter_products(db)
migrate_winter_camp(db)
migrate_destinations(db)
db.commit()
print("\n✓ 数据迁移完成!")
except Exception as e:
db.rollback()
print(f"\n✗ 迁移失败: {e}")
raise
finally:
db.close()
if __name__ == "__main__":
main()