Files
FastapiAdmin/backend/app/scripts/initialize.py
T
zhangtao 5ff72b086f refactor: 重构项目模块结构与代码细节优化
1.  调整后端模块路由与插件文件结构,迁移部分模块代码至api/v1目录
2.  优化代码注释格式与文档字符串,简化冗余代码
3.  添加监控仪表盘、工单评论等新模块功能
4.  修复前端部分组件交互逻辑与类型定义
5.  清理冗余的初始化数据与废弃配置文件
6.  新增租户注册表单字段与国际化支持
7.  优化HTTP请求拦截器与快捷键功能
2026-07-10 00:18:11 +08:00

193 lines
7.9 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.
import asyncio
import json
import re
from datetime import datetime, time
from typing import Any
from sqlalchemy import func, select
from sqlalchemy.ext.asyncio import AsyncSession
from app.api.v1.module_platform.menu.model import MenuModel
from app.api.v1.module_platform.package.model import PackageMenuModel, PackageModel
from app.api.v1.module_platform.tenant.model import TenantModel, TenantUserModel
from app.api.v1.module_system.dept.model import DeptModel
from app.api.v1.module_system.dict.model import DictDataModel, DictTypeModel
from app.api.v1.module_system.params.model import ParamsModel
from app.api.v1.module_system.role.model import RoleModel
from app.api.v1.module_system.user.model import UserModel, UserRolesModel
from app.api.v1.module_system.versions.model import VersionModel
from app.config.path_conf import SCRIPT_DIR
from app.core.database import async_db_session, create_tables
from app.core.logger import logger
class InitializeData:
"""初始化数据库和基础数据"""
_DATETIME_RE = re.compile(r"^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$")
_DATE_RE = re.compile(r"^\d{4}-\d{2}-\d{2}$")
_TIME_RE = re.compile(r"^\d{2}:\d{2}:\d{2}(\.\d+)?$")
# 按依赖关系排序:先基础表,再关联表
prepare_init_models: list[type] = [
# ── 平台管理:基础表 ──
PackageModel,
TenantModel,
MenuModel,
# ── 系统管理:基础表 ──
ParamsModel,
DeptModel,
RoleModel,
DictTypeModel,
DictDataModel,
UserModel,
# ── 关联表 ──
UserRolesModel,
TenantUserModel,
PackageMenuModel,
# ── 版本管理 ──
VersionModel,
]
# 树形模型:JSON 含嵌套 children,需递归创建对象
_RECURSIVE_TABLES: set[str] = {"platform_menu", "sys_dept"}
async def init_db(self) -> None:
"""建表并导入种子数据"""
try:
await create_tables()
except asyncio.exceptions.TimeoutError:
logger.error("❌️ 数据库表结构初始化超时")
raise
async with async_db_session() as session, session.begin():
await self.__init_data(session)
async def __init_data(self, db: AsyncSession) -> None:
"""按依赖顺序初始化各表种子数据"""
dict_type_mapping: dict[str, Any] = {} # dict_type → DictTypeModel 实例
for model in self.prepare_init_models:
table_name = model.__tablename__
data = await self.__load_json(table_name)
if not data:
logger.info(f"⏭️ 跳过 {table_name} 表,无初始化数据")
continue
try:
# 树形表(platform_menu / sys_dept):递归创建含 children 的对象
if table_name in self._RECURSIVE_TABLES:
count = await db.execute(select(func.count()).select_from(model))
if count.scalar():
logger.info(f"⏭️ 跳过 {table_name} 表数据初始化(表已有数据)")
continue
objs = self.__create_objects_with_children(data, model)
db.add_all(objs)
await db.flush()
logger.info(f"✅️ 已向 {table_name} 写入初始化数据")
continue
# 字典类型表:存储类型映射供字典数据使用
if table_name == "sys_dict_type":
count = await db.execute(select(func.count()).select_from(model))
if count.scalar():
logger.info(f"⏭️ 跳过 {table_name} 表数据初始化(表已有数据)")
continue
objs = []
for item in data:
obj = model(**item)
objs.append(obj)
dict_type_mapping[item["dict_type"]] = obj
db.add_all(objs)
await db.flush()
logger.info(f"✅️ 已向 {table_name} 写入初始化数据")
continue
# 字典数据表:关联 dict_type_id
if table_name == "sys_dict_data":
count = await db.execute(select(func.count()).select_from(model))
if count.scalar():
logger.info(f"⏭️ 跳过 {table_name} 表数据初始化(表已有数据)")
continue
objs = []
for item in data:
dict_type_str = item.get("dict_type")
if dict_type_str not in dict_type_mapping:
logger.warning(f"⚠️ 未找到字典类型 {dict_type_str},跳过")
continue
item["dict_type_id"] = dict_type_mapping[dict_type_str].id
objs.append(model(**item))
db.add_all(objs)
await db.flush()
logger.info(f"✅️ 已向 {table_name} 写入初始化数据")
continue
# 普通表:空表时插入,已有数据跳过
count = await db.execute(select(func.count()).select_from(model))
if count.scalar():
logger.info(f"⏭️ 跳过 {table_name} 表数据初始化(表已有数据)")
continue
objs = [model(**item) for item in data]
db.add_all(objs)
await db.flush()
logger.info(f"✅️ 已向 {table_name} 写入初始化数据")
except Exception:
logger.error(f"❌️ 初始化 {table_name} 表数据失败")
raise
@staticmethod
def __create_objects_with_children(data: list[dict], model_class: type) -> list:
"""递归创建树形模型实例,处理嵌套 children 并注入 parent_id"""
def _create(obj_data: dict) -> Any:
children_data = obj_data.pop("children", [])
# JSON 中子节点 parent_id 通常为 null,先按原始值创建
obj = model_class(**obj_data)
if children_data:
obj.children = [_create(child) for child in children_data]
return obj
return [_create(item) for item in data]
async def __load_json(self, filename: str) -> list[dict]:
"""读取并解析种子数据 JSON 文件"""
json_path = SCRIPT_DIR / f"{filename}.json"
if not json_path.exists():
return []
try:
with open(json_path, encoding="utf-8") as f:
raw = json.loads(f.read())
return [self._parse_date_strings(item) for item in raw]
except json.JSONDecodeError as e:
logger.error(f"❌️ 解析 {json_path} 失败: {e!s}")
raise
except Exception as e:
logger.error(f"❌️ 读取 {json_path} 失败: {e!s}")
raise
@classmethod
def _parse_date_strings(cls, data: dict) -> dict:
"""递归转换 JSON 中的日期时间字符串为 datetime 对象(兼容 PostgreSQL"""
result = {}
for key, value in data.items():
if isinstance(value, str):
if cls._DATETIME_RE.match(value):
result[key] = datetime.strptime(value, "%Y-%m-%d %H:%M:%S")
elif cls._DATE_RE.match(value):
result[key] = datetime.strptime(value, "%Y-%m-%d").date()
elif cls._TIME_RE.match(value):
result[key] = time.fromisoformat(value)
else:
result[key] = value
elif isinstance(value, dict):
result[key] = cls._parse_date_strings(value)
else:
result[key] = value
return result