mirror of
https://github.com/fastapiadmin/FastapiAdmin.git
synced 2026-09-26 06:19:04 +00:00
移除SQLITE_DB_NAME配置,统一使用DATABASE_NAME作为数据库名称 重构CRUD基类,增加out_schema参数用于序列化输出 优化数据库表查询逻辑,支持多种数据库类型 修复序列化工具类的方法签名和实现
417 lines
16 KiB
Python
417 lines
16 KiB
Python
# -*- coding:utf-8 -*-
|
|
|
|
from datetime import datetime, time
|
|
import json
|
|
from sqlalchemy.engine.row import Row
|
|
from sqlalchemy import delete, func, select, text, update
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
from sqlalchemy.orm import selectinload
|
|
from typing import List, Optional, Sequence, Any, Dict
|
|
|
|
from .model import GenTableModel, GenTableColumnModel
|
|
from app.config.setting import settings
|
|
from app.common.request import PaginationService
|
|
from .schema import GenTableCreateSchema, GenTableUpdateSchema, GenTableOutSchema, GenTableDeleteSchema, GenTableColumnCreateSchema, GenTableColumnUpdateSchema, GenTableColumnOutSchema, GenTableColumnDeleteSchema
|
|
from .param import GenTableQueryParam
|
|
from app.core.base_crud import CRUDBase
|
|
from app.api.v1.module_system.auth.schema import AuthSchema
|
|
|
|
|
|
class GenTableCRUD(CRUDBase[GenTableModel, GenTableCreateSchema, GenTableUpdateSchema]):
|
|
"""代码生成业务表模块数据库操作层"""
|
|
|
|
def __init__(self, auth: AuthSchema) -> None:
|
|
"""初始化CRUD"""
|
|
super().__init__(model=GenTableModel, auth=auth)
|
|
|
|
async def get_gen_table_by_id(self, table_id: int) -> Optional[GenTableModel]:
|
|
"""
|
|
根据业务表id获取需要生成的业务表信息
|
|
|
|
:param db: orm对象
|
|
:param table_id: 业务表id
|
|
:return: 需要生成的业务表信息对象
|
|
"""
|
|
gen_table_info = (
|
|
(
|
|
await self.db.execute(
|
|
select(GenTableModel).options(selectinload(GenTableModel.columns)).where(GenTableModel.id == table_id)
|
|
)
|
|
)
|
|
.scalars()
|
|
.first()
|
|
)
|
|
|
|
return gen_table_info
|
|
|
|
async def get_gen_table_by_name(self, table_name: str) -> Optional[GenTableModel]:
|
|
"""
|
|
根据业务表名称获取需要生成的业务表信息
|
|
|
|
:param db: orm对象
|
|
:param table_name: 业务表名称
|
|
:return: 需要生成的业务表信息对象
|
|
"""
|
|
gen_table_info = (
|
|
(
|
|
await self.db.execute(
|
|
select(GenTableModel).options(selectinload(GenTableModel.columns)).where(GenTableModel.table_name == table_name)
|
|
)
|
|
)
|
|
.scalars()
|
|
.first()
|
|
)
|
|
|
|
return gen_table_info
|
|
|
|
async def get_gen_table_all(self) -> Sequence[GenTableModel]:
|
|
"""
|
|
获取所有业务表信息
|
|
|
|
:param db: orm对象
|
|
:return: 所有业务表信息
|
|
"""
|
|
gen_table_all = (await self.db.execute(select(GenTableModel).options(selectinload(GenTableModel.columns)))).scalars().all()
|
|
|
|
return gen_table_all
|
|
|
|
async def create_table_by_sql(self, sql_statements: List) -> None:
|
|
"""
|
|
根据sql语句创建表结构
|
|
|
|
:param db: orm对象
|
|
:param sql_statements: sql语句的ast列表
|
|
:return:
|
|
"""
|
|
for sql_statement in sql_statements:
|
|
sql = sql_statement.sql(dialect=settings.DATABASE_TYPE)
|
|
await self.db.execute(text(sql))
|
|
|
|
async def get_gen_table_list(self, query_object: GenTableQueryParam, is_page: bool = False):
|
|
"""
|
|
根据查询参数获取代码生成业务表列表信息
|
|
|
|
:param db: orm对象
|
|
:param query_object: 查询参数对象
|
|
:param is_page: 是否开启分页
|
|
:return: 代码生成业务表列表信息对象
|
|
"""
|
|
# 构建查询条件
|
|
conditions = []
|
|
|
|
# 访问table_name属性
|
|
if getattr(query_object, 'table_name', None) and query_object.table_name[1]:
|
|
conditions.append(func.lower(GenTableModel.table_name).like(f'%{str(query_object.table_name[1]).lower()}%'))
|
|
|
|
# 访问table_comment属性
|
|
if getattr(query_object, 'table_comment', None):
|
|
conditions.append(func.lower(GenTableModel.table_comment).like(f'%{str(query_object.table_comment).lower()}%'))
|
|
|
|
# 访问created_at属性而不是start_time和end_time
|
|
if hasattr(query_object, 'created_at') and query_object.created_at:
|
|
if isinstance(query_object.created_at, tuple) and query_object.created_at[0] == "between":
|
|
conditions.append(GenTableModel.created_at.between(*query_object.created_at[1]))
|
|
|
|
query = (
|
|
select(GenTableModel)
|
|
.options(selectinload(GenTableModel.columns))
|
|
.where(*conditions)
|
|
.order_by(GenTableModel.created_at.desc())
|
|
.distinct()
|
|
)
|
|
|
|
# 获取所有数据
|
|
result = await self.db.execute(query)
|
|
all_data = list(result.scalars().all())
|
|
|
|
# 使用PaginationService.paginate进行分页
|
|
# 注意:这里假设query_object有page_no和page_size属性,如果没有需要从其他地方获取
|
|
page_no = getattr(query_object, 'page_no', None)
|
|
page_size = getattr(query_object, 'page_size', None)
|
|
if is_page and page_no is not None and page_size is not None:
|
|
paginated_result = await PaginationService.paginate(
|
|
data_list=all_data,
|
|
page_no=page_no,
|
|
page_size=page_size
|
|
)
|
|
return paginated_result
|
|
else:
|
|
return {
|
|
"items": all_data,
|
|
"total": len(all_data),
|
|
"page_no": None,
|
|
"page_size": None,
|
|
"has_next": False
|
|
}
|
|
|
|
async def get_gen_db_table_list(self, search: GenTableQueryParam, order_by: Optional[List[Dict[str, str]]] = None) -> list[Any]:
|
|
"""
|
|
根据查询参数获取数据库列表信息
|
|
|
|
:param db: orm对象
|
|
:param search: 查询参数对象
|
|
:param order_by: 排序字段
|
|
:return: 数据库列表信息对象
|
|
"""
|
|
# pg数据库
|
|
# {
|
|
# "table_catalog": "fastapiadmin",
|
|
# "table_schema": "pg_catalog",
|
|
# "table_name": "pg_foreign_table",
|
|
# "table_type": "BASE TABLE",
|
|
# "self_referencing_column_name": null,
|
|
# "reference_generation": null,
|
|
# "user_defined_type_catalog": null,
|
|
# "user_defined_type_schema": null,
|
|
# "user_defined_type_name": null,
|
|
# "is_insertable_into": "YES",
|
|
# "is_typed": "NO",
|
|
# "commit_action": null
|
|
# }
|
|
|
|
# mysql
|
|
# {
|
|
# "TABLE_CATALOG": "def",
|
|
# "TABLE_SCHEMA": "fastapiadmin",
|
|
# "TABLE_NAME": "ai_mcp",
|
|
# "TABLE_TYPE": "BASE TABLE",
|
|
# "ENGINE": "InnoDB",
|
|
# "VERSION": 10,
|
|
# "ROW_FORMAT": "Dynamic",
|
|
# "TABLE_ROWS": 0,
|
|
# "AVG_ROW_LENGTH": 0,
|
|
# "DATA_LENGTH": 16384,
|
|
# "MAX_DATA_LENGTH": 0,
|
|
# "INDEX_LENGTH": 16384,
|
|
# "DATA_FREE": 0,
|
|
# "AUTO_INCREMENT": null,
|
|
# "CREATE_TIME": "2025-10-02T03:43:02",
|
|
# "UPDATE_TIME": null,
|
|
# "CHECK_TIME": null,
|
|
# "TABLE_COLLATION": "utf8mb4_0900_ai_ci",
|
|
# "CHECKSUM": null,
|
|
# "CREATE_OPTIONS": "",
|
|
# "TABLE_COMMENT": "MCP 服务器表"
|
|
# },
|
|
|
|
# sqlite
|
|
# {
|
|
# "type": "table",
|
|
# "name": "system_users",
|
|
# "tbl_name": "system_users",
|
|
# "rootpage": 47,
|
|
# "sql": "CREATE TABLE system_users (\n\tusername VARCHAR(32) NOT NULL, \n\tpassword VARCHAR(255) NOT NULL, \n\tname VARCHAR(32) NOT NULL, \n\tstatus BOOLEAN NOT NULL, \n\tmobile VARCHAR(20), \n\temail VARCHAR(64), \n\tgender VARCHAR(1), \n\tavatar VARCHAR(500), \n\tis_superuser BOOLEAN NOT NULL, \n\tlast_login DATETIME, \n\tdept_id INTEGER, \n\tcreator_id INTEGER, \n\tid INTEGER NOT NULL, \n\tdescription TEXT, \n\tcreated_at DATETIME, \n\tupdated_at DATETIME, \n\tPRIMARY KEY (id), \n\tUNIQUE (username), \n\tUNIQUE (mobile), \n\tUNIQUE (email), \n\tFOREIGN KEY(dept_id) REFERENCES system_dept (id) ON DELETE SET NULL ON UPDATE CASCADE\n)"
|
|
# },
|
|
|
|
# 使用更健壮的方式检测数据库方言
|
|
if settings.DATABASE_TYPE == 'postgresql':
|
|
query_sql = """
|
|
SELECT
|
|
table_catalog as database_name,
|
|
table_name as table_name,
|
|
table_type as table_type,
|
|
table_schema as table_comment
|
|
from
|
|
information_schema.tables
|
|
where
|
|
table_catalog = (select current_database())
|
|
and is_insertable_into = 'YES'
|
|
and table_schema = 'public'
|
|
"""
|
|
elif settings.DATABASE_TYPE == 'mysql':
|
|
query_sql = """
|
|
SELECT
|
|
table_schema as database_name,
|
|
table_name as table_name,
|
|
table_type as table_type,
|
|
table_comment as table_comment
|
|
from
|
|
information_schema.tables
|
|
where
|
|
table_schema = (select database())
|
|
"""
|
|
else:
|
|
query_sql = f"""
|
|
SELECT
|
|
'{settings.DATABASE_NAME}' as database_name,
|
|
name as table_name,
|
|
type as table_type,
|
|
tbl_name as table_comment
|
|
from
|
|
sqlite_master
|
|
where
|
|
type = 'table'
|
|
"""
|
|
|
|
# 直接执行文本SQL查询,避免SQLAlchemy自动添加额外的SELECT )
|
|
query = text(query_sql).bindparams()
|
|
|
|
# 执行查询
|
|
result = await self.db.execute(query)
|
|
all_data = result.fetchall()
|
|
|
|
# 将Row对象转换为字典列表,解决JSON序列化问题
|
|
dict_data = []
|
|
for row in all_data:
|
|
# 检查row是否为Row对象
|
|
if isinstance(row, Row):
|
|
# 使用._mapping获取字典
|
|
dict_row = dict(row._mapping)
|
|
dict_data.append(dict_row)
|
|
else:
|
|
dict_data.append(row)
|
|
return dict_data
|
|
|
|
async def get_gen_db_table_list_by_names(self, table_names: List[str]):
|
|
"""
|
|
根据业务表名称组获取数据库列表信息
|
|
|
|
:param db: orm对象
|
|
:param table_names: 业务表名称组
|
|
:return: 数据库列表信息对象
|
|
"""
|
|
# 使用更健壮的方式检测数据库方言
|
|
if settings.DATABASE_TYPE == 'postgresql':
|
|
query_sql = """
|
|
SELECT
|
|
table_catalog as database_name,
|
|
table_name as table_name,
|
|
table_type as table_type,
|
|
table_schema as table_comment
|
|
from
|
|
information_schema.tables
|
|
where
|
|
table_catalog = (select current_database())
|
|
and is_insertable_into = 'YES'
|
|
and table_schema = 'public'
|
|
and table_name in :table_names
|
|
"""
|
|
elif settings.DATABASE_TYPE == 'mysql':
|
|
query_sql = """
|
|
SELECT
|
|
table_schema as database_name,
|
|
table_name as table_name,
|
|
table_type as table_type,
|
|
table_comment as table_comment
|
|
from
|
|
information_schema.tables
|
|
where
|
|
table_schema = (select database())
|
|
and table_name in :table_names
|
|
"""
|
|
else:
|
|
query_sql = f"""
|
|
SELECT
|
|
'{settings.DATABASE_NAME}' as database_name,
|
|
name as table_name,
|
|
type as table_type,
|
|
tbl_name as table_comment
|
|
from
|
|
sqlite_master
|
|
where
|
|
type = 'table'
|
|
and table_name in :table_names
|
|
"""
|
|
|
|
query = text(query_sql).bindparams(table_names=tuple(table_names))
|
|
gen_db_table_list = (await self.db.execute(query)).fetchall()
|
|
|
|
return gen_db_table_list
|
|
|
|
|
|
class GenTableColumnCRUD(CRUDBase[GenTableColumnModel, GenTableColumnCreateSchema, GenTableColumnUpdateSchema]):
|
|
"""代码生成业务表字段模块数据库操作层"""
|
|
|
|
def __init__(self, auth: AuthSchema) -> None:
|
|
"""初始化CRUD"""
|
|
super().__init__(model=GenTableColumnModel, auth=auth)
|
|
|
|
async def get_gen_table_column_list_by_table_id_crud(self, table_id: int) -> Sequence[GenTableColumnModel]:
|
|
"""根据业务表id获取需要生成的业务表字段列表信息"""
|
|
return await self.list(search={"table_id": table_id})
|
|
|
|
async def get_gen_table_column_list_by_table_id(self, db: AsyncSession, table_id: int) -> Sequence[GenTableColumnModel]:
|
|
"""
|
|
根据业务表id获取需要生成的业务表字段列表信息
|
|
|
|
:param db: orm对象
|
|
:param table_id: 业务表id
|
|
:return: 需要生成的业务表字段列表信息对象
|
|
"""
|
|
gen_table_column_list = (
|
|
(
|
|
await db.execute(
|
|
select(GenTableColumnModel).where(GenTableColumnModel.table_id == table_id).order_by(GenTableColumnModel.sort)
|
|
)
|
|
)
|
|
.scalars()
|
|
.all()
|
|
)
|
|
|
|
return gen_table_column_list
|
|
|
|
async def get_gen_db_table_columns_by_name(self, db: AsyncSession, table_name: str) -> Sequence[Row[Any]]:
|
|
"""
|
|
根据业务表名称获取业务表字段列表信息
|
|
|
|
:param db: orm对象
|
|
:param table_name: 业务表名称
|
|
:return: 业务表字段列表信息对象
|
|
"""
|
|
# 兼容SQLite和MySQL/PostgreSQL
|
|
if str(db.bind.dialect) == 'sqlite':
|
|
query_sql = """
|
|
pragma table_info(:table_name)
|
|
"""
|
|
query = text(query_sql).bindparams(table_name=table_name)
|
|
gen_db_table_columns_raw = (await db.execute(query)).fetchall()
|
|
|
|
# 转换SQLite的pragma结果为与information_schema.columns兼容的格式
|
|
gen_db_table_columns = []
|
|
for col in gen_db_table_columns_raw:
|
|
# col格式: (cid, name, type, notnull, dflt_value, pk)
|
|
is_required = '1' if col[3] == 1 and col[5] == 0 else '0'
|
|
is_pk = '1' if col[5] == 1 else '0'
|
|
is_increment = '0' # SQLite没有auto_increment标记,需要额外判断
|
|
|
|
# 构建兼容的结果行
|
|
gen_db_table_columns.append({
|
|
'column_name': col[1],
|
|
'is_required': is_required,
|
|
'is_pk': is_pk,
|
|
'sort': col[0], # 使用cid作为排序
|
|
'column_comment': '', # SQLite不存储列注释
|
|
'is_increment': is_increment,
|
|
'column_type': col[2]
|
|
})
|
|
else:
|
|
query_sql = """
|
|
select
|
|
column_name as column_name,
|
|
case
|
|
when is_nullable = 'no' and column_key != 'PRI' then '1'
|
|
else '0'
|
|
end as is_required,
|
|
case
|
|
when column_key = 'PRI' then '1'
|
|
else '0'
|
|
end as is_pk,
|
|
ordinal_position as sort,
|
|
column_comment as column_comment,
|
|
case
|
|
when extra = 'auto_increment' then '1'
|
|
else '0'
|
|
end as is_increment,
|
|
column_type as column_type
|
|
from
|
|
information_schema.columns
|
|
where
|
|
table_schema = (select database())
|
|
and table_name = :table_name
|
|
order by
|
|
ordinal_position
|
|
"""
|
|
query = text(query_sql).bindparams(table_name=table_name)
|
|
gen_db_table_columns = (await db.execute(query)).fetchall()
|
|
|
|
return gen_db_table_columns |