import_db_snapshot.py 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  1. # -*- coding: utf-8 -*-
  2. """
  3. 导入 export_db_snapshot.py 生成的演示快照,并还原静态文件与训练元数据。
  4. 用法(空库或需覆盖演示数据时):
  5. cd BridgeDiseaseBackend-main
  6. $env:SQLALCHEMY_DATABASE_URI="mysql+pymysql://root:bridgedisease_root@127.0.0.1:3307/bridge_disease?charset=utf8mb4"
  7. python scripts/import_db_snapshot.py
  8. 可选:仅还原文件不导入 SQL
  9. python scripts/import_db_snapshot.py --files-only
  10. """
  11. from __future__ import annotations
  12. import argparse
  13. import os
  14. import shutil
  15. import subprocess
  16. import sys
  17. from pathlib import Path
  18. ROOT = Path(__file__).resolve().parents[1]
  19. SQL_FILE = ROOT / 'sql' / 'seed_snapshot.sql'
  20. SNAPSHOT_STATIC = ROOT / 'seed_assets' / 'snapshot' / 'static'
  21. SNAPSHOT_TRAINING = ROOT / 'seed_assets' / 'snapshot' / 'training_meta'
  22. STATIC_DST = ROOT / 'app' / 'static'
  23. TRAINING_DST = ROOT / 'data' / 'training_meta'
  24. sys.path.insert(0, str(ROOT))
  25. os.environ.setdefault(
  26. 'SQLALCHEMY_DATABASE_URI',
  27. 'mysql+pymysql://root:bridgedisease_root@127.0.0.1:3307/bridge_disease?charset=utf8mb4',
  28. )
  29. def _db_env():
  30. uri = os.environ['SQLALCHEMY_DATABASE_URI']
  31. body = uri.split('://', 1)[-1]
  32. auth, rest = body.split('@', 1)
  33. user, password = auth.split(':', 1)
  34. host_port, db_part = rest.split('/', 1)
  35. database = db_part.split('?', 1)[0]
  36. host, port = (host_port.split(':', 1) + ['3306'])[:2]
  37. return host, port, user, password, database
  38. def import_sql() -> None:
  39. if not SQL_FILE.is_file():
  40. print(f'缺少 {SQL_FILE},请先运行 export_db_snapshot.py', file=sys.stderr)
  41. sys.exit(1)
  42. host, port, user, password, database = _db_env()
  43. from app import create_app
  44. from app.models import db
  45. app = create_app()
  46. from sqlalchemy import text
  47. with app.app_context():
  48. db.create_all()
  49. # 清空业务表(保留表结构)
  50. db.session.execute(text('SET FOREIGN_KEY_CHECKS=0'))
  51. for table in ('detection', 'operation', 'media', 'model', 'user'):
  52. db.session.execute(text(f'TRUNCATE TABLE `{table}`'))
  53. db.session.commit()
  54. db.session.execute(text('SET FOREIGN_KEY_CHECKS=1'))
  55. db.session.commit()
  56. print('[ok] 已清空 user/model/media/detection/operation')
  57. cmd = [
  58. 'mysql',
  59. f'--host={host}',
  60. f'--port={port}',
  61. f'-u{user}',
  62. f'-p{password}',
  63. '--default-character-set=utf8mb4',
  64. database,
  65. ]
  66. try:
  67. with open(SQL_FILE, 'rb') as f:
  68. subprocess.run(cmd, stdin=f, check=True)
  69. except FileNotFoundError:
  70. docker_cmd = [
  71. 'docker',
  72. 'exec',
  73. '-i',
  74. 'bridge-disease-mysql',
  75. 'mysql',
  76. f'-u{user}',
  77. f'-p{password}',
  78. '--default-character-set=utf8mb4',
  79. database,
  80. ]
  81. with open(SQL_FILE, 'rb') as f:
  82. subprocess.run(docker_cmd, stdin=f, check=True)
  83. print(f'[ok] 已导入 {SQL_FILE}')
  84. def restore_files() -> None:
  85. if not SNAPSHOT_STATIC.is_dir():
  86. print(f'缺少 {SNAPSHOT_STATIC},跳过静态文件还原')
  87. return
  88. STATIC_DST.mkdir(parents=True, exist_ok=True)
  89. for child in SNAPSHOT_STATIC.iterdir():
  90. dst = STATIC_DST / child.name
  91. if dst.exists():
  92. shutil.rmtree(dst)
  93. shutil.copytree(child, dst)
  94. n = sum(1 for _ in dst.rglob('*') if _.is_file())
  95. print(f'[ok] 静态 {child.name} -> {dst} ({n} files)')
  96. if SNAPSHOT_TRAINING.is_dir():
  97. TRAINING_DST.mkdir(parents=True, exist_ok=True)
  98. for f in SNAPSHOT_TRAINING.glob('*.json'):
  99. shutil.copy2(f, TRAINING_DST / f.name)
  100. print(f'[ok] training_meta -> {TRAINING_DST}')
  101. def main() -> int:
  102. parser = argparse.ArgumentParser()
  103. parser.add_argument('--files-only', action='store_true', help='仅还原静态与 training_meta')
  104. parser.add_argument('--sql-only', action='store_true', help='仅导入 SQL')
  105. args = parser.parse_args()
  106. if not args.files_only:
  107. import_sql()
  108. if not args.sql_only:
  109. restore_files()
  110. print('\n完成。请启动后端并刷新前端(admin / Admin123456)。')
  111. return 0
  112. if __name__ == '__main__':
  113. sys.exit(main())