mirror of
https://github.com/fastapiadmin/FastapiAdmin.git
synced 2026-09-21 12:52:26 +00:00
fix(myapp): 调整权限验证为myapp相关权限
- 修改应用详情、列表、创建、更新、删除及状态修改接口的权限从system改为myapp fix(resource): 修复资源服务中文件URL生成逻辑 - 重新实现URL拼接逻辑,避免多余的斜杠及错误的协议前缀 - 确保base_url
This commit is contained in:
@@ -24,7 +24,7 @@ MyAppRouter = APIRouter(route_class=OperationLogRoute, prefix="/application", ta
|
||||
@MyAppRouter.get("/detail/{id}", summary="获取应用详情", description="获取应用详情")
|
||||
async def get_obj_detail_controller(
|
||||
id: int = Path(..., description="应用ID"),
|
||||
auth: AuthSchema = Depends(AuthPermission(permissions=["application:system:query"]))
|
||||
auth: AuthSchema = Depends(AuthPermission(permissions=["application:myapp:query"]))
|
||||
) -> JSONResponse:
|
||||
result_dict = await ApplicationService.get_application_detail_service(id=id, auth=auth)
|
||||
logger.info(f"获取应用详情成功 {id}")
|
||||
@@ -34,7 +34,7 @@ async def get_obj_detail_controller(
|
||||
async def get_obj_list_controller(
|
||||
page: PaginationQueryParams = Depends(),
|
||||
search: ApplicationQueryParams = Depends(),
|
||||
auth: AuthSchema = Depends(AuthPermission(permissions=["application:system:query"]))
|
||||
auth: AuthSchema = Depends(AuthPermission(permissions=["application:myapp:query"]))
|
||||
) -> JSONResponse:
|
||||
result_dict_list = await ApplicationService.get_application_list_service(auth=auth, search=search, order_by=page.order_by)
|
||||
result_dict = await PaginationService.get_page_obj(data_list=result_dict_list, page_no=page.page_no, page_size=page.page_size)
|
||||
@@ -44,7 +44,7 @@ async def get_obj_list_controller(
|
||||
@MyAppRouter.post("/create", summary="创建应用", description="创建应用")
|
||||
async def create_obj_controller(
|
||||
data: ApplicationCreateSchema,
|
||||
auth: AuthSchema = Depends(AuthPermission(permissions=["application:system:create"]))
|
||||
auth: AuthSchema = Depends(AuthPermission(permissions=["application:myapp:create"]))
|
||||
) -> JSONResponse:
|
||||
result_dict = await ApplicationService.create_application_service(auth=auth, data=data)
|
||||
logger.info(f"创建应用成功: {result_dict}")
|
||||
@@ -54,7 +54,7 @@ async def create_obj_controller(
|
||||
async def update_obj_controller(
|
||||
data: ApplicationUpdateSchema,
|
||||
id: int = Path(..., description="应用ID"),
|
||||
auth: AuthSchema = Depends(AuthPermission(permissions=["application:system:update"]))
|
||||
auth: AuthSchema = Depends(AuthPermission(permissions=["application:myapp:update"]))
|
||||
) -> JSONResponse:
|
||||
result_dict = await ApplicationService.update_application_service(auth=auth, id=id, data=data)
|
||||
logger.info(f"修改应用成功: {result_dict}")
|
||||
@@ -63,7 +63,7 @@ async def update_obj_controller(
|
||||
@MyAppRouter.delete("/delete", summary="删除应用", description="删除应用")
|
||||
async def delete_obj_controller(
|
||||
ids: list[int] = Body(..., description="ID列表"),
|
||||
auth: AuthSchema = Depends(AuthPermission(permissions=["application:system:delete"]))
|
||||
auth: AuthSchema = Depends(AuthPermission(permissions=["application:myapp:delete"]))
|
||||
) -> JSONResponse:
|
||||
await ApplicationService.delete_application_service(auth=auth, ids=ids)
|
||||
logger.info(f"删除应用成功: {ids}")
|
||||
@@ -72,7 +72,7 @@ async def delete_obj_controller(
|
||||
@MyAppRouter.patch("/available/setting", summary="批量修改应用状态", description="批量修改应用状态")
|
||||
async def batch_set_available_obj_controller(
|
||||
data: BatchSetAvailable,
|
||||
auth: AuthSchema = Depends(AuthPermission(permissions=["application:system:patch"]))
|
||||
auth: AuthSchema = Depends(AuthPermission(permissions=["application:myapp:patch"]))
|
||||
) -> JSONResponse:
|
||||
await ApplicationService.set_application_available_service(auth=auth, data=data)
|
||||
logger.info(f"批量修改应用状态成功: {data.ids}")
|
||||
|
||||
@@ -146,7 +146,13 @@ class ResourceService:
|
||||
# 生成HTTP URL路径而不是文件系统路径
|
||||
if base_url:
|
||||
from urllib.parse import urljoin
|
||||
http_url = urljoin(base_url.rstrip('/') + '/', f"{settings.STATIC_URL.lstrip('/')}/{relative_path}".lstrip('/')).replace('\\', '/').replace('//', '/')
|
||||
base_part = base_url.rstrip('/')
|
||||
static_part = settings.STATIC_URL.lstrip('/')
|
||||
relative_part = relative_path.lstrip('/')
|
||||
# 手动构建URL而不是使用urljoin,避免双斜杠问题
|
||||
if base_part.endswith(':') or (len(base_part) > 0 and base_part[-1] not in ['/', ':']):
|
||||
base_part += '/'
|
||||
http_url = f"{base_part}{static_part}/{relative_part}".replace('\\', '/').replace('//', '/').replace(':/', '://')
|
||||
else:
|
||||
http_url = f"{settings.STATIC_URL}/{relative_path}".replace('\\', '/').replace('//', '/')
|
||||
|
||||
@@ -186,7 +192,12 @@ class ResourceService:
|
||||
# 对于根目录,返回静态URL路径
|
||||
if base_url:
|
||||
from urllib.parse import urljoin
|
||||
display_path = urljoin(base_url.rstrip('/') + '/', settings.STATIC_URL.lstrip('/'))
|
||||
# 修复URL生成逻辑
|
||||
base_part = base_url.rstrip('/')
|
||||
static_part = settings.STATIC_URL.lstrip('/')
|
||||
if base_part.endswith(':') or (len(base_part) > 0 and base_part[-1] not in ['/', ':']):
|
||||
base_part += '/'
|
||||
display_path = f"{base_part}{static_part}".replace('//', '/').replace(':/', '://')
|
||||
else:
|
||||
display_path = settings.STATIC_URL
|
||||
else:
|
||||
@@ -197,13 +208,24 @@ class ResourceService:
|
||||
relative_path = os.path.relpath(safe_path, resource_root)
|
||||
if base_url:
|
||||
from urllib.parse import urljoin
|
||||
display_path = urljoin(base_url.rstrip('/') + '/', f"{settings.STATIC_URL.lstrip('/')}/{relative_path}".lstrip('/')).replace('\\', '/').replace('//', '/')
|
||||
# 修复URL生成逻辑
|
||||
base_part = base_url.rstrip('/')
|
||||
static_part = settings.STATIC_URL.lstrip('/')
|
||||
relative_part = relative_path.lstrip('/')
|
||||
if base_part.endswith(':') or (len(base_part) > 0 and base_part[-1] not in ['/', ':']):
|
||||
base_part += '/'
|
||||
display_path = f"{base_part}{static_part}/{relative_part}".replace('\\', '/').replace('//', '/').replace(':/', '://')
|
||||
else:
|
||||
display_path = f"{settings.STATIC_URL}/{relative_path}".replace('\\', '/').replace('//', '/')
|
||||
except ValueError:
|
||||
if base_url:
|
||||
from urllib.parse import urljoin
|
||||
display_path = urljoin(base_url.rstrip('/') + '/', settings.STATIC_URL.lstrip('/'))
|
||||
# 修复URL生成逻辑
|
||||
base_part = base_url.rstrip('/')
|
||||
static_part = settings.STATIC_URL.lstrip('/')
|
||||
if base_part.endswith(':') or (len(base_part) > 0 and base_part[-1] not in ['/', ':']):
|
||||
base_part += '/'
|
||||
display_path = f"{base_part}{static_part}".replace('//', '/').replace(':/', '://')
|
||||
else:
|
||||
display_path = settings.STATIC_URL
|
||||
|
||||
@@ -466,14 +488,27 @@ class ResourceService:
|
||||
# 如果提供了base_url,使用它生成完整URL,否则使用settings.STATIC_URL
|
||||
if base_url:
|
||||
from urllib.parse import urljoin
|
||||
file_url = urljoin(base_url.rstrip('/') + '/', f"{settings.STATIC_URL.lstrip('/')}/{file_url_path}".lstrip('/'))
|
||||
# 修复URL生成逻辑
|
||||
base_part = base_url.rstrip('/')
|
||||
static_part = settings.STATIC_URL.lstrip('/')
|
||||
file_url_part = file_url_path.lstrip('/')
|
||||
if base_part.endswith(':') or (len(base_part) > 0 and base_part[-1] not in ['/', ':']):
|
||||
base_part += '/'
|
||||
file_url = f"{base_part}{static_part}/{file_url_part}".replace('//', '/').replace(':/', '://')
|
||||
else:
|
||||
file_url = f"{settings.STATIC_URL}/{file_url_path}".replace('//', '/')
|
||||
except ValueError:
|
||||
# 如果无法计算相对路径,使用文件名
|
||||
filename = os.path.basename(file_path)
|
||||
if base_url:
|
||||
from urllib.parse import urljoin
|
||||
file_url = urljoin(base_url.rstrip('/') + '/', f"{settings.STATIC_URL.lstrip('/')}/{filename}".lstrip('/'))
|
||||
# 修复URL生成逻辑
|
||||
base_part = base_url.rstrip('/')
|
||||
static_part = settings.STATIC_URL.lstrip('/')
|
||||
filename_part = filename.lstrip('/')
|
||||
if base_part.endswith(':') or (len(base_part) > 0 and base_part[-1] not in ['/', ':']):
|
||||
base_part += '/'
|
||||
file_url = f"{base_part}{static_part}/{filename_part}".replace('//', '/').replace(':/', '://')
|
||||
else:
|
||||
file_url = f"{settings.STATIC_URL}/{filename}"
|
||||
|
||||
@@ -511,7 +546,13 @@ class ResourceService:
|
||||
# 生成HTTP URL
|
||||
if base_url:
|
||||
from urllib.parse import urljoin
|
||||
http_url = urljoin(base_url.rstrip('/') + '/', f"{settings.STATIC_URL.lstrip('/')}/{relative_path}".lstrip('/')).replace('\\', '/').replace('//', '/')
|
||||
# 修复URL生成逻辑
|
||||
base_part = base_url.rstrip('/')
|
||||
static_part = settings.STATIC_URL.lstrip('/')
|
||||
relative_part = relative_path.lstrip('/')
|
||||
if base_part.endswith(':') or (len(base_part) > 0 and base_part[-1] not in ['/', ':']):
|
||||
base_part += '/'
|
||||
http_url = f"{base_part}{static_part}/{relative_part}".replace('\\', '/').replace('//', '/').replace(':/', '://')
|
||||
else:
|
||||
http_url = f"{settings.STATIC_URL}/{relative_path}".replace('\\', '/').replace('//', '/')
|
||||
logger.info(f"生成文件访问URL: {http_url}")
|
||||
@@ -521,7 +562,13 @@ class ResourceService:
|
||||
filename = os.path.basename(safe_path)
|
||||
if base_url:
|
||||
from urllib.parse import urljoin
|
||||
http_url = urljoin(base_url.rstrip('/') + '/', f"{settings.STATIC_URL.lstrip('/')}/{filename}".lstrip('/'))
|
||||
# 修复URL生成逻辑
|
||||
base_part = base_url.rstrip('/')
|
||||
static_part = settings.STATIC_URL.lstrip('/')
|
||||
filename_part = filename.lstrip('/')
|
||||
if base_part.endswith(':') or (len(base_part) > 0 and base_part[-1] not in ['/', ':']):
|
||||
base_part += '/'
|
||||
http_url = f"{base_part}{static_part}/{filename_part}".replace('//', '/').replace(':/', '://')
|
||||
else:
|
||||
http_url = f"{settings.STATIC_URL}/{filename}"
|
||||
logger.info(f"生成文件访问URL: {http_url}")
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
from redis import asyncio as aioredis
|
||||
from motor.motor_asyncio import AsyncIOMotorClient
|
||||
from fastapi import FastAPI
|
||||
from sqlalchemy import create_engine, text
|
||||
from sqlalchemy import create_engine, text, Engine
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from sqlalchemy.ext.asyncio import (
|
||||
create_async_engine,
|
||||
@@ -27,11 +27,14 @@ from sqlalchemy.exc import (
|
||||
from app.core.logger import logger
|
||||
from app.config.setting import settings
|
||||
from app.core.exceptions import CustomException
|
||||
from app.core.base_model import MappedBase
|
||||
|
||||
# 同步数据库引擎
|
||||
engine = create_engine(
|
||||
engine: Engine = create_engine(
|
||||
url=settings.DB_URI,
|
||||
echo=settings.DATABASE_ECHO,
|
||||
pool_pre_ping=settings.POOL_PRE_PING,
|
||||
pool_recycle=settings.POOL_RECYCLE,
|
||||
)
|
||||
# 同步数据库会话工厂
|
||||
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
||||
@@ -67,33 +70,18 @@ def session_connect() -> AsyncSession:
|
||||
except Exception as e:
|
||||
raise CustomException(msg=f"数据库连接失败: {e}")
|
||||
|
||||
async def test_db_connection(session: AsyncSession) -> bool:
|
||||
async def init_create_table():
|
||||
"""
|
||||
应用启动时初始化数据库连接
|
||||
|
||||
:return:
|
||||
"""
|
||||
try:
|
||||
# 执行简单查询测试连接是否真实可用
|
||||
await session.execute(text("SELECT 1"))
|
||||
return True
|
||||
except OperationalError as e:
|
||||
raise CustomException(msg=f"数据库操作失败: {e},请检查数据库服务是否正常运行")
|
||||
except TimeoutError as e:
|
||||
raise CustomException(msg=f"数据库连接超时: {e},请检查网络连接或增加连接超时时间")
|
||||
except DisconnectionError as e:
|
||||
raise CustomException(msg=f"数据库连接中断: {e},请检查数据库服务状态")
|
||||
except InterfaceError as e:
|
||||
raise CustomException(msg=f"数据库接口错误: {e},请检查数据库驱动配置")
|
||||
except ProgrammingError as e:
|
||||
raise CustomException(msg=f"SQL语句错误: {e},请检查SQL语法")
|
||||
except IntegrityError as e:
|
||||
raise CustomException(msg=f"数据完整性错误: {e},请检查数据约束条件")
|
||||
except DataError as e:
|
||||
raise CustomException(msg=f"数据类型错误: {e},请检查数据格式")
|
||||
except InternalError as e:
|
||||
raise CustomException(msg=f"数据库内部错误: {e},请联系数据库管理员")
|
||||
except NotSupportedError as e:
|
||||
raise CustomException(msg=f"数据库不支持该操作: {e},请检查数据库版本")
|
||||
except InvalidRequestError as e:
|
||||
raise CustomException(msg=f"无效的数据库请求: {e},请检查请求参数")
|
||||
async with async_engine.begin() as conn:
|
||||
await conn.run_sync(MappedBase.metadata.create_all)
|
||||
logger.info('数据库连接成功...')
|
||||
except Exception as e:
|
||||
raise CustomException(msg=f"数据库操作异常: {e},请联系管理员")
|
||||
raise CustomException(msg=f"数据库连接失败: {e}")
|
||||
|
||||
async def redis_connect(app: FastAPI, status: bool) -> aioredis.Redis:
|
||||
"""创建或关闭Redis连接"""
|
||||
|
||||
@@ -21,7 +21,8 @@ from app.api.v1.module_system.auth.schema import AuthSchema
|
||||
async def db_getter() -> AsyncGenerator[AsyncSession, None]:
|
||||
"""获取数据库会话连接"""
|
||||
async with session_connect() as session:
|
||||
yield session
|
||||
async with session.begin():
|
||||
yield session
|
||||
|
||||
async def redis_getter(request: Request) -> Redis:
|
||||
"""获取Redis连接"""
|
||||
|
||||
@@ -105,22 +105,23 @@ class OperationLogRoute(APIRoute):
|
||||
pass
|
||||
else:
|
||||
async with session_connect() as session:
|
||||
auth = AuthSchema(db=session)
|
||||
await OperationLogService.create_log_service(data=OperationLogCreateSchema(
|
||||
type = log_type,
|
||||
request_path = request.url.path,
|
||||
request_method = request.method,
|
||||
request_payload = payload,
|
||||
request_ip = request_ip,
|
||||
login_location=login_location,
|
||||
request_os = user_agent.os.family,
|
||||
request_browser = user_agent.browser.family,
|
||||
response_code = response.status_code,
|
||||
response_json = response_data.decode(),
|
||||
process_time = process_time,
|
||||
description = route.summary,
|
||||
creator_id = current_user_id
|
||||
), auth = auth)
|
||||
async with session.begin():
|
||||
auth = AuthSchema(db=session)
|
||||
await OperationLogService.create_log_service(data=OperationLogCreateSchema(
|
||||
type = log_type,
|
||||
request_path = request.url.path,
|
||||
request_method = request.method,
|
||||
request_payload = payload,
|
||||
request_ip = request_ip,
|
||||
login_location=login_location,
|
||||
request_os = user_agent.os.family,
|
||||
request_browser = user_agent.browser.family,
|
||||
response_code = response.status_code,
|
||||
response_json = response_data.decode(),
|
||||
process_time = process_time,
|
||||
description = route.summary,
|
||||
creator_id = current_user_id
|
||||
), auth = auth)
|
||||
|
||||
return response
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ from app.core.exceptions import (
|
||||
ResponseValidationHandle,
|
||||
ResponseValidationError
|
||||
)
|
||||
from app.core.database import session_connect, test_db_connection
|
||||
from app.core.database import session_connect, init_create_table
|
||||
from app.scripts.initialize import InitializeData
|
||||
from app.api.v1.module_system.config.service import ConfigService
|
||||
from app.api.v1.module_system.dict.service import DictDataService
|
||||
@@ -46,30 +46,31 @@ async def lifespan(app: FastAPI) -> AsyncGenerator[Any, Any]:
|
||||
logger.info(settings.BANNER + '\n' + f'{settings.TITLE} 服务开始启动...')
|
||||
|
||||
try:
|
||||
# 初始化数据库表
|
||||
await init_create_table()
|
||||
|
||||
# 使用单个会话完成所有初始化操作
|
||||
async with session_connect() as session:
|
||||
# 测试数据库连接
|
||||
await test_db_connection(session)
|
||||
logger.info("数据库连接成功...")
|
||||
async with session.begin():
|
||||
|
||||
# 初始化数据库
|
||||
await InitializeData().init_db(db=session)
|
||||
logger.info("初始化数据完成...")
|
||||
# 初始化数据库数据
|
||||
await InitializeData().init_db(db=session)
|
||||
logger.info("初始化数据完成...")
|
||||
|
||||
# 初始化全局事件
|
||||
await import_modules_async(modules=settings.EVENT_LIST, desc="全局事件", app=app, status=True)
|
||||
|
||||
# 初始化系统配置
|
||||
await ConfigService().init_config_service(redis=app.state.redis, db=session)
|
||||
logger.info("初始化系统配置完成...")
|
||||
|
||||
# 初始化数据字典
|
||||
await DictDataService().init_dict_service(redis=app.state.redis, db=session)
|
||||
logger.info('初始化数据字典完成...')
|
||||
|
||||
# 初始化定时任务
|
||||
await SchedulerUtil.init_system_scheduler(db=session)
|
||||
logger.info('初始化定时任务完成...')
|
||||
# 初始化全局事件
|
||||
await import_modules_async(modules=settings.EVENT_LIST, desc="全局事件", app=app, status=True)
|
||||
|
||||
# 初始化系统配置
|
||||
await ConfigService().init_config_service(redis=app.state.redis, db=session)
|
||||
logger.info("初始化系统配置完成...")
|
||||
|
||||
# 初始化数据字典
|
||||
await DictDataService().init_dict_service(redis=app.state.redis, db=session)
|
||||
logger.info('初始化数据字典完成...')
|
||||
|
||||
# 初始化定时任务
|
||||
await SchedulerUtil.init_system_scheduler(db=session)
|
||||
logger.info('初始化定时任务完成...')
|
||||
|
||||
logger.info(f'{settings.TITLE} 服务成功启动...')
|
||||
except Exception as e:
|
||||
|
||||
@@ -4,7 +4,7 @@ import uuid
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Dict, List
|
||||
from sqlalchemy import inspect, select, func, text
|
||||
from sqlalchemy import inspect, select, func
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.core.base_model import MappedBase
|
||||
@@ -59,134 +59,46 @@ class InitializeData:
|
||||
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:
|
||||
"""初始化基础数据"""
|
||||
try:
|
||||
inserted_data = False # 标记是否有数据插入
|
||||
for model in self.prepare_init_models:
|
||||
table_name = model.__tablename__
|
||||
|
||||
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()
|
||||
|
||||
logger.info(f"检查表 {table_name} 数据: 已存在 {existing_count} 条记录")
|
||||
|
||||
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:
|
||||
# 表为空,直接插入全部数据
|
||||
logger.info(f"准备向 {table_name} 表插入 {len(data)} 条记录")
|
||||
objs = [model(**item) for item in data]
|
||||
db.add_all(objs)
|
||||
inserted_data = True
|
||||
|
||||
# 对于 PostgreSQL,更新序列值
|
||||
if settings.DATABASE_TYPE == "postgresql" and model in self.models_with_id and len(objs) > 0:
|
||||
await self.__update_postgresql_sequence_for_model(db, model, table_name)
|
||||
|
||||
logger.info(f"已向 {table_name} 表写入 {len(objs)} 条记录")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"初始化 {table_name} 表数据失败: {str(e)}")
|
||||
raise
|
||||
# 检查表中是否已经有数据
|
||||
count_result = await db.execute(select(func.count()).select_from(model))
|
||||
existing_count = count_result.scalar()
|
||||
|
||||
# 只有在有数据插入时才提交事务
|
||||
if inserted_data:
|
||||
await db.commit()
|
||||
logger.info("数据初始化事务已提交")
|
||||
else:
|
||||
logger.info("没有新数据需要插入,跳过事务提交")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"初始化数据过程中出现错误: {str(e)}")
|
||||
# 如果出现错误,回滚事务
|
||||
await db.rollback()
|
||||
raise
|
||||
if existing_count > 0:
|
||||
logger.warning(f"跳过 {table_name} 表数据初始化(表已存在 {existing_count} 条记录)")
|
||||
continue
|
||||
|
||||
async def __update_postgresql_sequence_for_model(self, db: AsyncSession, model, table_name: str) -> None:
|
||||
"""为特定模型更新 PostgreSQL 序列值"""
|
||||
try:
|
||||
# 检查模型是否有id属性
|
||||
if not hasattr(model, 'id'):
|
||||
return
|
||||
|
||||
# 获取表中最大的 ID 值
|
||||
max_id_result = await db.execute(select(func.max(model.id)).select_from(model))
|
||||
max_id = max_id_result.scalar()
|
||||
data = await self.__get_data(table_name)
|
||||
if not data:
|
||||
logger.warning(f"跳过 {table_name} 表,无初始化数据")
|
||||
continue
|
||||
|
||||
if max_id is not None and max_id > 0:
|
||||
# 更新序列值
|
||||
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}")
|
||||
except Exception as e:
|
||||
logger.error(f"更新 {table_name} 表的 PostgreSQL 序列值失败: {str(e)}")
|
||||
# 不抛出异常,因为序列更新失败不应该导致整个初始化失败
|
||||
try:
|
||||
# 表为空,直接插入全部数据
|
||||
objs = [model(**item) for item in data]
|
||||
db.add_all(objs)
|
||||
await db.flush()
|
||||
logger.info(f"已向 {table_name} 表写入 {len(objs)} 条记录")
|
||||
|
||||
async def __get_data(self, table_name: str) -> List[Dict]:
|
||||
except Exception as e:
|
||||
logger.error(f"初始化 {table_name} 表数据失败: {str(e)}")
|
||||
raise
|
||||
|
||||
async def __get_data(self, filename: str) -> List[Dict]:
|
||||
"""读取初始化数据文件"""
|
||||
json_path = Path.joinpath(settings.SCRIPT_DIR, f'{table_name}.json')
|
||||
logger.info(f"尝试读取初始化数据文件: {json_path}")
|
||||
json_path = Path.joinpath(settings.SCRIPT_DIR, f'{filename}.json')
|
||||
if not json_path.exists():
|
||||
logger.warning(f"初始化数据文件不存在: {json_path}")
|
||||
return []
|
||||
|
||||
try:
|
||||
with open(json_path, 'r', encoding='utf-8') as f:
|
||||
data = json.loads(f.read())
|
||||
logger.info(f"成功读取 {table_name} 数据文件,包含 {len(data)} 条记录")
|
||||
return data
|
||||
return json.loads(f.read())
|
||||
except json.JSONDecodeError as e:
|
||||
logger.error(f"解析 {json_path} 失败: {str(e)}")
|
||||
raise
|
||||
@@ -198,8 +110,5 @@ class InitializeData:
|
||||
"""
|
||||
执行完整初始化流程
|
||||
"""
|
||||
logger.info("开始执行数据库初始化流程")
|
||||
await self.__init_model(db)
|
||||
# 刷新session以确保数据可见性
|
||||
await db.flush()
|
||||
logger.info("数据库初始化流程完成")
|
||||
await self.__init_data(db)
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ VITE_APP_TITLE=fastapiadmin
|
||||
|
||||
|
||||
# 网络请求公用地址
|
||||
VITE_API_BASE_URL=http://localhost:8001
|
||||
VITE_API_BASE_URL=http://127.0.0.1:8001
|
||||
# VITE_API_BASE_URL=https://service.fastapiadmin.com
|
||||
|
||||
# 代理前缀
|
||||
|
||||
@@ -5,7 +5,7 @@ VITE_APP_ENV=production
|
||||
VITE_APP_TITLE=fastapiadmin
|
||||
|
||||
# 网络请求公用地址
|
||||
VITE_API_BASE_URL=http://localhost:8001
|
||||
VITE_API_BASE_URL=https://service.fastapiadmin.com
|
||||
|
||||
# 代理前缀
|
||||
VITE_APP_BASE_API=/api/v1
|
||||
|
||||
Reference in New Issue
Block a user