initialize.py 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. import asyncio
  2. import json
  3. from typing import Any
  4. from sqlalchemy import func, select
  5. from sqlalchemy.ext.asyncio import AsyncSession
  6. from app.api.v1.module_system.dept.model import DeptModel
  7. from app.api.v1.module_system.dict.model import DictDataModel, DictTypeModel
  8. from app.api.v1.module_system.menu.model import MenuModel
  9. from app.api.v1.module_system.params.model import ParamsModel
  10. from app.api.v1.module_system.position.model import PositionModel
  11. from app.api.v1.module_system.role.model import RoleModel
  12. from app.api.v1.module_system.tenant.model import TenantModel
  13. from app.api.v1.module_system.user.model import UserModel, UserRolesModel
  14. from app.config.path_conf import SCRIPT_DIR
  15. from app.core.database import async_db_session, create_tables
  16. from app.core.logger import log
  17. class InitializeData:
  18. """
  19. 初始化数据库和基础数据
  20. """
  21. def __init__(self) -> None:
  22. """
  23. 初始化数据库和基础数据
  24. """
  25. # 按照依赖关系排序:先创建基础表,再创建关联表
  26. self.prepare_init_models = [
  27. TenantModel,
  28. MenuModel,
  29. ParamsModel,
  30. DeptModel,
  31. RoleModel,
  32. DictTypeModel,
  33. DictDataModel,
  34. PositionModel,
  35. UserModel,
  36. UserRolesModel,
  37. ]
  38. async def __init_create_table(self) -> None:
  39. """
  40. 初始化表结构(第一阶段)
  41. """
  42. try:
  43. # 使用引擎创建所有表
  44. # await drop_tables()
  45. await create_tables()
  46. except asyncio.exceptions.TimeoutError:
  47. log.error("❌️ 数据库表结构初始化超时")
  48. raise
  49. except Exception as e:
  50. log.error(f"❌️ 数据库表结构初始化失败: {e!s}")
  51. raise
  52. async def __init_data(self, db: AsyncSession) -> None:
  53. """
  54. 初始化基础数据
  55. 参数:
  56. - db (AsyncSession): 异步数据库会话。
  57. """
  58. # 存储字典类型数据的映射,用于后续字典数据的初始化
  59. dict_type_mapping = {}
  60. for model in self.prepare_init_models:
  61. table_name = model.__tablename__
  62. # 检查表中是否已经有数据
  63. count_result = await db.execute(select(func.count()).select_from(model))
  64. existing_count = count_result.scalar()
  65. if existing_count and existing_count > 0:
  66. log.warning(
  67. f"⚠️ 跳过 {table_name} 表数据初始化(表已存在 {existing_count} 条记录)"
  68. )
  69. continue
  70. data = await self.__get_data(table_name)
  71. if not data:
  72. log.warning(f"⚠️ 跳过 {table_name} 表,无初始化数据")
  73. continue
  74. try:
  75. # 特殊处理具有嵌套 children 数据的表
  76. if table_name in ["sys_dept", "sys_menu"]:
  77. # 获取对应的模型类
  78. model_class = DeptModel if table_name == "sys_dept" else MenuModel
  79. objs = self.__create_objects_with_children(data, model_class)
  80. # 处理字典类型表,保存类型映射
  81. elif table_name == "sys_dict_type":
  82. objs = []
  83. for item in data:
  84. obj = model(**item)
  85. objs.append(obj)
  86. dict_type_mapping[item["dict_type"]] = obj
  87. # 处理字典数据表,添加dict_type_id关联
  88. elif table_name == "sys_dict_data":
  89. objs = []
  90. for item in data:
  91. dict_type = item.get("dict_type")
  92. if dict_type in dict_type_mapping:
  93. # 添加dict_type_id关联
  94. item["dict_type_id"] = dict_type_mapping[dict_type].id
  95. else:
  96. log.warning(f"⚠️ 未找到字典类型 {dict_type},跳过该字典数据")
  97. continue
  98. objs.append(model(**item))
  99. else:
  100. # 表为空,直接插入全部数据
  101. objs = [model(**item) for item in data]
  102. db.add_all(objs)
  103. await db.flush()
  104. log.info(f"✅️ 已向 {table_name} 表写入初始化数据")
  105. except Exception as e:
  106. log.error(f"❌️ 初始化 {table_name} 表数据失败: {e!s}")
  107. raise
  108. def __create_objects_with_children(self, data: list[dict], model_class: type) -> list:
  109. """
  110. 通用递归创建对象函数,处理嵌套的 children 数据
  111. 参数:
  112. - data (list[dict]): 包含嵌套 children 数据的列表。
  113. - model_class: 对应的 SQLAlchemy 模型类。
  114. 返回:
  115. - list: 包含创建的对象的列表。
  116. """
  117. def create_object(obj_data: dict) -> Any:
  118. """
  119. 由单条 dict 递归构建模型实例(含 children)。
  120. 参数:
  121. - obj_data (dict): 行数据,可含嵌套 children。
  122. 返回:
  123. - Any: SQLAlchemy 模型实例。
  124. """
  125. # 分离 children 数据
  126. children_data = obj_data.pop("children", [])
  127. # 创建当前对象
  128. obj = model_class(**obj_data)
  129. # 递归处理子对象
  130. if children_data:
  131. obj.children = [create_object(child) for child in children_data]
  132. return obj
  133. objs = [create_object(item) for item in data]
  134. return objs
  135. async def __get_data(self, filename: str) -> list[dict]:
  136. """
  137. 读取初始化数据文件
  138. 参数:
  139. - filename (str): 文件名(不包含扩展名)。
  140. 返回:
  141. - list[dict]: 解析后的 JSON 数据列表。
  142. """
  143. json_path = SCRIPT_DIR / f"{filename}.json"
  144. if not json_path.exists():
  145. return []
  146. try:
  147. with open(json_path, encoding="utf-8") as f:
  148. return json.loads(f.read())
  149. except json.JSONDecodeError as e:
  150. log.error(f"❌️ 解析 {json_path} 失败: {e!s}")
  151. raise
  152. except Exception as e:
  153. log.error(f"❌️ 读取 {json_path} 失败: {e!s}")
  154. raise
  155. async def init_db(self) -> None:
  156. """
  157. 执行完整初始化流程:建表并导入种子数据。
  158. 返回:
  159. - None
  160. """
  161. # 先创建表结构
  162. await self.__init_create_table()
  163. # 再初始化数据
  164. async with async_db_session() as session:
  165. async with session.begin():
  166. await self.__init_data(session)