hulai-admin-api/scripts/import_json_data.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

541 行
19 KiB
Python

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

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

from __future__ import annotations
"""把前端 JSON 文件中的数据导入到后端数据库。
UPSERT 逻辑:已存在则更新所有字段,不存在则插入。
重新运行脚本即可将 JSON 的最新数据同步到数据库。
Usage: cd hulai-admin-api && python -m scripts.import_json_data
"""
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)
# ─── Blog ────────────────────────────────────────────────────────────────────
def import_blog(db):
data = load_json("blog.json")
if not data:
return
articles = data.get("articles", [])
added = updated = 0
for i, a in enumerate(articles):
slug = a.get("id") or a.get("slug")
if not slug:
print(f" ⚠️ 博文缺少 id/slug,跳过: {a.get('title', '')[:30]}")
continue
# 章节内容合并成 HTML
content_parts = []
for ch in a.get("sections", []) or a.get("chapters", []):
if isinstance(ch, dict):
if ch.get("title"):
content_parts.append(f"<h2>{ch['title']}</h2>")
if ch.get("content"):
content_parts.append(ch["content"])
if ch.get("body"):
content_parts.append(ch["body"])
content = "\n".join(content_parts) if content_parts else a.get("content", "")
fields = dict(
title=a["title"],
cover_image=a.get("coverImage") or a.get("cover_image"),
summary=a.get("summary") or a.get("subtitle"),
content=content,
author=a.get("author"),
category=a.get("category"),
tags=a.get("tags"),
published_at=a.get("date") or a.get("publishedAt"),
is_visible=True,
sort_order=i,
)
existing = db.query(Blog).filter_by(slug=slug).first()
if existing:
for k, v in fields.items():
setattr(existing, k, v)
updated += 1
else:
db.add(Blog(slug=slug, **fields))
added += 1
print(f" blog_posts: 新增 {added} 条,更新 {updated} 条(共 {len(articles)} 条)")
# ─── Stories ─────────────────────────────────────────────────────────────────
def import_stories(db):
data = load_json("stories.json")
if not data:
return
stories = data.get("stories", [])
added = updated = 0
for i, s in enumerate(stories):
slug = s.get("slug") or s.get("id")
title = s.get("title", "")
# 章节内容合并
content_parts = []
for ch in s.get("chapters", []):
if isinstance(ch, dict):
if ch.get("title"):
content_parts.append(f"<h2>{ch['title']}</h2>")
body = ch.get("content") or ch.get("body", "")
if body:
content_parts.append(body)
content = "\n".join(content_parts) if content_parts else s.get("content", "")
fields = dict(
title=title,
cover_image=s.get("coverImage") or s.get("cover_image"),
summary=s.get("summary") or s.get("subtitle"),
content=content,
customer_name=s.get("customerName") or s.get("familyType"),
avatar=s.get("avatar"),
travel_date=s.get("travelDate"),
product_name=s.get("product"),
scenes=s.get("tags"),
is_visible=True,
sort_order=i,
)
# 优先按 slug 匹配,无 slug 则按 title
existing = db.query(Story).filter_by(title=title).first() if title else None
if existing:
for k, v in fields.items():
setattr(existing, k, v)
updated += 1
else:
db.add(Story(**fields))
added += 1
print(f" stories: 新增 {added} 条,更新 {updated} 条(共 {len(stories)} 条)")
# ─── News ─────────────────────────────────────────────────────────────────────
def import_news(db):
data = load_json("news.json")
if not data:
return
articles = data.get("articles", [])
added = updated = 0
for i, a in enumerate(articles):
slug = a.get("slug") or a.get("id")
title = a.get("title", "")
fields = dict(
title=title,
cover_image=a.get("coverImage") or a.get("cover_image"),
summary=a.get("summary"),
content=a.get("content", ""),
source=a.get("source"),
source_url=a.get("sourceUrl") or a.get("url"),
published_at=a.get("date") or a.get("publishedAt"),
is_visible=True,
sort_order=i,
)
existing = db.query(News).filter_by(title=title).first() if title else None
if existing:
for k, v in fields.items():
setattr(existing, k, v)
updated += 1
else:
db.add(News(**fields))
added += 1
print(f" news: 新增 {added} 条,更新 {updated} 条(共 {len(articles)} 条)")
# ─── Pricing ─────────────────────────────────────────────────────────────────
def import_pricing(db):
data = load_json("pricing.json")
if not data:
return
products = data.get("products", [])
added = updated = 0
for i, p in enumerate(products):
product_id = p.get("id") or p.get("slug")
fields = dict(
product_name=p.get("name", ""),
price_from=None,
price_unit=None,
price_label=p.get("priceLabel"),
description=p.get("priceNote") or p.get("audience"),
features=p.get("highlights"),
notes=None,
is_visible=True,
sort_order=i,
)
existing = None
if product_id:
existing = db.query(PricingItem).filter_by(product_slug=product_id).first()
if existing:
for k, v in fields.items():
setattr(existing, k, v)
updated += 1
else:
db.add(PricingItem(product_slug=product_id, **fields))
added += 1
print(f" pricing_items: 新增 {added} 条,更新 {updated} 条(共 {len(products)} 条)")
# ─── Qualifications ──────────────────────────────────────────────────────────
def import_qualifications(db):
data = load_json("qualifications.json")
if not data:
return
added = updated = 0
sort = 0
def upsert_qual(name, **fields):
nonlocal added, updated, sort
existing = db.query(Qualification).filter_by(title=name).first()
if existing:
for k, v in fields.items():
setattr(existing, k, v)
updated += 1
else:
db.add(Qualification(title=name, sort_order=sort, **fields))
added += 1
sort += 1
# 旅游经营许可证
for lic in data.get("licenses", []):
title = lic.get("title", "旅游经营许可证")
upsert_qual(
title,
issuer=lic.get("issuer"),
year=None,
image=None,
description=(
f"证件号:{lic.get('licenseNo', '')}"
f"持证主体:{lic.get('holder', '')}"
f"法人:{lic.get('legalPerson', '')}"
f"经营范围:{lic.get('businessScope', '')}"
f"统一信用代码:{lic.get('creditCode', '')}"
f"{lic.get('note', '')}"
),
category="license",
)
# 旅游责任险
insurance = data.get("insurance")
if insurance:
policies = insurance.get("policies", [])
policy_desc = "".join([
f"{p.get('insured','')}·{p.get('insurer','')}·保单号{p.get('policyNo','')}·保额{p.get('coverage','')}·有效期{p.get('period','')}"
for p in policies
])
upsert_qual(
insurance.get("title", "旅游责任险"),
issuer=policies[0].get("insurer") if policies else None,
year=None,
image=None,
description=f"{insurance.get('description', '')}{policy_desc}",
category="insurance",
)
# 荣誉奖项
for award in data.get("awards", []):
title = award.get("title", "")
if not title:
continue
upsert_qual(
title,
issuer=award.get("issuer") or award.get("platform"),
year=str(award.get("year", "")) if award.get("year") else None,
image=None,
description=award.get("description") or award.get("detail"),
category="award",
)
# 品牌传承
heritage = data.get("heritage")
if heritage:
milestones_text = "".join([
f"{m.get('year')}年:{m.get('event', '')}"
for m in heritage.get("milestones", [])
])
upsert_qual(
f"品牌资历:{heritage.get('brandYears', '')}年深耕呼伦贝尔",
issuer=None,
year=str(heritage.get("brandFounded")) if heritage.get("brandFounded") else None,
image=None,
description=f"{heritage.get('note', '')}{milestones_text}",
category="heritage",
)
print(f" qualifications: 新增 {added} 条,更新 {updated}")
# ─── Partners ────────────────────────────────────────────────────────────────
def import_partners(db):
data = load_json("partners.json")
if not data:
return
added = updated = 0
sort = 0
cat_map = {
"govPartners": "政府合作",
"associations": "行业协会",
"academicCoops": "校企合作",
"scenicPartners": "景区合作",
"hotelPartners": "酒店合作",
"platformEndorsements": "平台认证",
"mediaReports": "媒体报道",
}
for cat_key, cat_label in cat_map.items():
for item in data.get(cat_key, []):
name = item.get("name") or item.get("platform") or item.get("outlet", "")
if not name or name.startswith("⚠️"):
continue
if not item.get("verified", True):
continue
# 构建描述
desc_parts = []
for field in ["type", "relationship", "description", "detail", "status", "summary"]:
val = item.get(field)
if val and not str(val).startswith("⚠️"):
desc_parts.append(str(val))
if item.get("followers"):
desc_parts.append(f"粉丝:{item['followers']}")
if item.get("account"):
desc_parts.append(f"账号:{item['account']}")
if item.get("since"):
desc_parts.append(f"合作始于:{item['since']}")
fields = dict(
logo=None,
website=item.get("url"),
description="".join(desc_parts),
category=cat_label,
sort_order=sort,
)
existing = db.query(Partner).filter_by(name=name).first()
if existing:
for k, v in fields.items():
setattr(existing, k, v)
updated += 1
else:
db.add(Partner(name=name, **fields))
added += 1
sort += 1
print(f" partners: 新增 {added} 条,更新 {updated}")
# ─── Gallery ─────────────────────────────────────────────────────────────────
def import_gallery(db):
data = load_json("gallery.json")
if not data:
return
works = data.get("works", [])
added = updated = 0
for i, w in enumerate(works):
title = w.get("title")
tags = []
if w.get("category"):
tags.append(w["category"])
if w.get("season"):
tags.append(w["season"])
if w.get("costumeType"):
tags.append(w["costumeType"])
fields = dict(
image=w.get("image", ""),
photographer=None,
location=w.get("location"),
description=w.get("description"),
tags=tags if tags else None,
is_visible=True,
sort_order=i,
)
existing = db.query(GalleryItem).filter_by(title=title).first() if title else None
if existing:
for k, v in fields.items():
setattr(existing, k, v)
updated += 1
else:
db.add(GalleryItem(title=title, **fields))
added += 1
print(f" gallery_items: 新增 {added} 条,更新 {updated} 条(共 {len(works)} 条)")
# ─── Destination Details ──────────────────────────────────────────────────────
def import_destination_details(db):
data = load_json("destinations-detail.json")
if not data:
return
destinations = data.get("destinations", [])
added = updated = 0
for i, d in enumerate(destinations):
slug = d.get("id", "")
if not slug:
continue
# highlights 可能是 string list 或 dict list
raw_highlights = d.get("highlights", [])
if raw_highlights and isinstance(raw_highlights[0], str):
highlights = [{"text": h} for h in raw_highlights]
else:
highlights = raw_highlights
tags = []
if d.get("tag"):
tags.append(d["tag"])
fields = dict(
name=d.get("name", ""),
subtitle=d.get("subtitle"),
cover_image=d.get("heroImage") or d.get("coverImage"),
description=d.get("description", ""),
location=None,
best_season=(
d["bestSeason"].get("primary") if isinstance(d.get("bestSeason"), dict)
else d.get("bestSeason")
),
duration=None,
highlights=highlights,
gallery=None,
tags=tags if tags else None,
is_visible=True,
sort_order=i,
)
existing = db.query(DestinationDetail).filter_by(slug=slug).first()
if existing:
for k, v in fields.items():
setattr(existing, k, v)
updated += 1
else:
db.add(DestinationDetail(slug=slug, **fields))
added += 1
print(f" destination_details: 新增 {added} 条,更新 {updated} 条(共 {len(destinations)} 条)")
# ─── Selector Config ─────────────────────────────────────────────────────────
def import_selector(db):
data = load_json("selector.json")
if not data:
return
rules = {
"scoring": data.get("scoring", {}),
"matchReasons": data.get("matchReasons", {}),
}
existing = db.query(SelectorConfig).first()
if existing:
existing.questions = data.get("questions", [])
existing.rules = rules
print(" selector_config: ✓ 已更新")
else:
db.add(SelectorConfig(
questions=data.get("questions", []),
rules=rules,
))
print(" selector_config: ✓ 新增 1 条")
# ─── Courses Config ───────────────────────────────────────────────────────────
def import_courses(db):
data = load_json("courses.json")
if not data:
return
existing = db.query(CoursesConfig).first()
if existing:
existing.modules = data.get("modules", [])
existing.age_groups = data.get("ageGroups", [])
existing.faqs = data.get("faq", []) or data.get("faqs", [])
print(" courses_config: ✓ 已更新")
else:
db.add(CoursesConfig(
modules=data.get("modules", []),
age_groups=data.get("ageGroups", []),
faqs=data.get("faq", []) or data.get("faqs", []),
))
print(" courses_config: ✓ 新增 1 条")
# ─── Main ─────────────────────────────────────────────────────────────────────
def main():
print("确保数据库表存在...")
Base.metadata.create_all(bind=engine)
db = SessionLocal()
try:
print("\n开始 UPSERT JSON 数据到数据库:")
print("-" * 50)
import_blog(db)
import_stories(db)
import_news(db)
import_pricing(db)
import_qualifications(db)
import_partners(db)
import_gallery(db)
import_destination_details(db)
import_selector(db)
import_courses(db)
db.commit()
print("-" * 50)
print("\n✓ 数据同步完成!")
# 验证
print("\n数据库记录数验证:")
from app.models import (Blog, Story, News, PricingItem, Qualification,
Partner, GalleryItem, DestinationDetail,
SelectorConfig, CoursesConfig)
checks = [
("blog_posts", Blog),
("stories", Story),
("news", News),
("pricing_items", PricingItem),
("qualifications", Qualification),
("partners", Partner),
("gallery_items", GalleryItem),
("destination_details", DestinationDetail),
("selector_config", SelectorConfig),
("courses_config", CoursesConfig),
]
for name, model in checks:
cnt = db.query(model).count()
status = "" if cnt > 0 else "⚠ 空"
print(f" {status} {name}: {cnt}")
except Exception as e:
db.rollback()
print(f"\n✗ 导入失败: {e}")
import traceback
traceback.print_exc()
raise
finally:
db.close()
if __name__ == "__main__":
main()