Files
FastapiAdmin/backend/app/scripts/initialize.py
T
zhangtao ccaa94e1f2 refactor(config): 优化演示环境中IP白名单和中间件请求IP获取逻辑
- 演示模式中增加演示白名单IP "117.10.167.220"
- 中间件中优先从X-Forwarded-For请求头获取客户端真实IP,优化IP判断逻辑
- 从日志中打印用户名称,方便追踪演示环境操作用户
- 补充IP白名单和路径白名单的判断条件,非白名单用户禁止操作
- 初始化插件中的数据库连接和初始化逻辑调整,改为单独会话完成,提升代码清晰度
- 初始化脚本中更新PostgreSQL序列时仅处理含id字段的模型,避免关联表模型错误
- 修改开发环境配置,切换数据库类型为MySQL,更新相关连接配置,匹配MySQL默认端口及用户信息
- 删除MySQL数据库的完整导出脚本,确保代码库无冗余数据库备份文件
2025-09-08 22:04:00 +08:00

188 lines
7.1 KiB
Python

# -*- coding: utf-8 -*-
import uuid
import json
from pathlib import Path
from typing import Dict, List
from sqlalchemy import inspect, select, func, text
from sqlalchemy.ext.asyncio import AsyncSession
from app.core.base_model import MappedBase
from app.core.logger import logger
from app.config.setting import settings
from app.api.v1.module_system.user.model import UserModel, UserRolesModel, UserPositionsModel
from app.api.v1.module_system.role.model import RoleModel, RoleDeptsModel, RoleMenusModel
from app.api.v1.module_system.position.model import PositionModel
from app.api.v1.module_system.dept.model import DeptModel
from app.api.v1.module_system.menu.model import MenuModel
from app.api.v1.module_system.log.model import OperationLogModel
from app.api.v1.module_system.notice.model import NoticeModel
from app.api.v1.module_system.config.model import ConfigModel
from app.api.v1.module_system.dict.model import DictTypeModel, DictDataModel
from app.api.v1.module_monitor.job.model import JobModel, JobLogModel
from app.api.v1.module_example.demo.model import DemoModel
from app.api.v1.module_application.myapp.model import ApplicationModel
class InitializeData:
"""
初始化数据库和基础数据
"""
def __init__(self) -> None:
# 按照依赖关系排序:先创建基础表,再创建关联表
self.prepare_init_models = [
# 部门表(自引用,需要先创建)
DeptModel,
# 菜单表(自引用,需要先创建)
MenuModel,
# 用户表(依赖部门和角色)
UserModel,
# 角色表(依赖菜单和部门)
RoleModel,
# 岗位表(无外键依赖)
PositionModel,
# 基础表(无外键依赖)
ConfigModel,
DictTypeModel,
NoticeModel,
OperationLogModel,
DictDataModel,
JobModel,
JobLogModel,
DemoModel,
ApplicationModel,
# 关联表(依赖基础表)
UserPositionsModel,
UserRolesModel,
RoleDeptsModel,
RoleMenusModel,
]
# 需要更新序列的模型(排除关联表模型,因为它们没有id字段)
self.models_with_id = [
DeptModel,
MenuModel,
UserModel,
RoleModel,
PositionModel,
ConfigModel,
DictTypeModel,
NoticeModel,
OperationLogModel,
DictDataModel,
JobModel,
JobLogModel,
DemoModel,
ApplicationModel,
]
self.created_tables = set()
async def __get_existing_tables(self, db: AsyncSession) -> List[str]:
return await db.run_sync(
lambda sync_db: inspect(sync_db.get_bind()).get_table_names()
)
async def __init_model(self, db: AsyncSession) -> None:
"""初始化数据库表结构"""
try:
# 获取所有模型元数据
metadata = MappedBase.metadata
# 只创建不存在的表
for table in metadata.sorted_tables:
if table.name not in await self.__get_existing_tables(db):
await db.run_sync(lambda sync_db: table.create(sync_db.bind))
self.created_tables.add(table.name)
logger.info(f"已创建表: {table.name}")
await self.__init_data(db)
except Exception as e:
logger.error(f"初始化数据库结构失败: {str(e)}")
raise
async def __init_data(self, db: AsyncSession) -> None:
"""初始化基础数据"""
for model in self.prepare_init_models:
table_name = model.__tablename__
# 检查表中是否已经有数据
count_result = await db.execute(select(func.count()).select_from(model))
existing_count = count_result.scalar()
if existing_count > 0:
logger.warning(f"跳过 {table_name} 表数据初始化(表已存在 {existing_count} 条记录)")
continue
data = await self.__get_data(table_name)
if not data:
logger.warning(f"跳过 {table_name} 表,无初始化数据")
continue
try:
# 表为空,直接插入全部数据
objs = [model(**item) for item in data]
db.add_all(objs)
await db.flush()
logger.info(f"已向 {table_name} 表写入 {len(objs)} 条记录")
except Exception as e:
logger.error(f"初始化 {table_name} 表数据失败: {str(e)}")
raise
# 更新 PostgreSQL 序列值,确保自增 ID 正确
if settings.DATABASE_TYPE == "postgresql":
await self.__update_postgresql_sequences(db)
async def __update_postgresql_sequences(self, db: AsyncSession) -> None:
"""更新 PostgreSQL 序列值,确保自增 ID 正确"""
try:
# 为每个有初始化数据的表更新序列值(只处理有id字段的模型)
for model in self.models_with_id:
table_name = model.__tablename__
# 检查表中是否有数据
count_result = await db.execute(select(func.count()).select_from(model))
existing_count = count_result.scalar()
if existing_count > 0:
# 检查模型是否有id属性
if not hasattr(model, 'id'):
continue
# 获取表中最大的 ID 值
max_id_result = await db.execute(select(func.max(model.id)).select_from(model))
max_id = max_id_result.scalar()
if max_id is not None:
# 更新序列值
sequence_name = f"{table_name}_id_seq"
await db.execute(text(f"SELECT setval('{sequence_name}', {max_id}, true)"))
logger.info(f"已更新 {table_name} 表的序列 {sequence_name} 值为 {max_id}")
await db.commit()
except Exception as e:
logger.error(f"更新 PostgreSQL 序列值失败: {str(e)}")
raise
async def __get_data(self, filename: str) -> List[Dict]:
"""读取初始化数据文件"""
json_path = Path.joinpath(settings.SCRIPT_DIR, f'{filename}.json')
if not json_path.exists():
return []
try:
with open(json_path, 'r', encoding='utf-8') as f:
return json.loads(f.read())
except json.JSONDecodeError as e:
logger.error(f"解析 {json_path} 失败: {str(e)}")
raise
except Exception as e:
logger.error(f"读取 {json_path} 失败: {str(e)}")
raise
async def init_db(self, db: AsyncSession) -> None:
"""
执行完整初始化流程
"""
await self.__init_model(db)