mirror of
https://github.com/fastapiadmin/FastapiAdmin.git
synced 2026-09-20 20:39:55 +00:00
feat(gencode): 增强代码生成模块功能
- 添加列长度和默认值字段支持 - 改进表创建时的唯一性检查 - 优化字段属性初始化逻辑 - 增强前端表格展示和编辑功能 - 修复任务调度状态获取问题 - 清理和优化文档内容 refactor: 重构数据库查询逻辑 - 统一不同数据库类型的字段查询方式 - 添加唯一性约束检查 - 优化字段类型转换处理 fix: 修复任务重启逻辑 - 确保任务重启时使用最新配置 - 改进任务状态获取方法 docs: 更新部署文档 - 添加详细部署说明 - 优化文档结构 - 移除冗余内容 chore: 清理.gitignore和.gitattributes - 移除冗余配置 - 更新忽略规则
This commit is contained in:
@@ -209,7 +209,7 @@ async def get_job_log_controller():
|
||||
"coalesce": i.coalesce,
|
||||
"max_instances": i.max_instances,
|
||||
"next_run_time": i.next_run_time,
|
||||
"state": SchedulerUtil.get_job_status()
|
||||
"state": SchedulerUtil.get_single_job_status(job_id=i.id)
|
||||
}
|
||||
for i in SchedulerUtil.get_all_jobs()
|
||||
]
|
||||
|
||||
@@ -149,9 +149,16 @@ class JobService:
|
||||
elif option == 2:
|
||||
SchedulerUtil().resume_job(job_id=id)
|
||||
await JobCRUD(auth).set_obj_field_crud(ids=[id], status=True)
|
||||
# elif option == 3:
|
||||
# SchedulerUtil().reschedule_job(job_id=id)
|
||||
# await JobCRUD(auth).set_obj_field_crud(ids=[id], status=False)
|
||||
elif option == 3:
|
||||
# 重启任务:先移除再添加,确保使用最新的任务配置
|
||||
SchedulerUtil.remove_job(job_id=id)
|
||||
# 获取最新的任务配置
|
||||
updated_job = await JobCRUD(auth).get_obj_by_id_crud(id=id)
|
||||
if updated_job:
|
||||
# 重新添加任务
|
||||
SchedulerUtil.add_job(job_info=updated_job)
|
||||
# 设置状态为运行中
|
||||
await JobCRUD(auth).set_obj_field_crud(ids=[id], status=True)
|
||||
|
||||
@classmethod
|
||||
async def export_job_service(cls, data_list: List[Dict[str, Any]]) -> bytes:
|
||||
|
||||
@@ -321,6 +321,35 @@ class GenTableCRUD(CRUDBase[GenTableModel, GenTableSchema, GenTableSchema]):
|
||||
dict_data.append(dict_row)
|
||||
return dict_data
|
||||
|
||||
async def check_table_exists(self, table_name: str) -> bool:
|
||||
"""
|
||||
检查数据库中是否已存在指定表名的表。
|
||||
|
||||
参数:
|
||||
- table_name (str): 要检查的表名。
|
||||
|
||||
返回:
|
||||
- bool: 如果表存在返回True,否则返回False。
|
||||
"""
|
||||
try:
|
||||
# 根据不同数据库类型使用不同的查询方式
|
||||
if settings.DATABASE_TYPE.lower() == 'mysql':
|
||||
query = text("SELECT 1 FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = :table_name")
|
||||
elif settings.DATABASE_TYPE.lower() == 'postgresql':
|
||||
query = text("SELECT 1 FROM pg_tables WHERE tablename = :table_name")
|
||||
elif settings.DATABASE_TYPE.lower() == 'sqlite':
|
||||
query = text("SELECT name FROM sqlite_master WHERE type='table' AND name = :table_name")
|
||||
else:
|
||||
# 默认查询方式
|
||||
query = text("SELECT 1 FROM information_schema.tables WHERE table_name = :table_name")
|
||||
|
||||
result = await self.db.execute(query, {"table_name": table_name})
|
||||
return result.scalar() is not None
|
||||
except Exception as e:
|
||||
logger.error(f"检查表格存在性时发生错误: {e}")
|
||||
# 出错时返回False,避免误报表已存在
|
||||
return False
|
||||
|
||||
async def create_table_by_sql(self, sql: str) -> bool:
|
||||
"""
|
||||
根据SQL语句创建表结构。
|
||||
@@ -332,14 +361,11 @@ class GenTableCRUD(CRUDBase[GenTableModel, GenTableSchema, GenTableSchema]):
|
||||
- bool: 是否创建成功。
|
||||
"""
|
||||
try:
|
||||
# 执行SQL但不手动提交事务,由框架管理事务生命周期
|
||||
await self.db.execute(text(sql))
|
||||
# 提交事务
|
||||
await self.db.commit()
|
||||
await self.db.flush()
|
||||
return True
|
||||
except Exception as e:
|
||||
# 如果发生异常,回滚事务
|
||||
await self.db.rollback()
|
||||
logger.error(f"创建表时发生错误: {e}")
|
||||
return False
|
||||
|
||||
@@ -415,10 +441,13 @@ class GenTableColumnCRUD(CRUDBase[GenTableColumnModel, GenTableColumnSchema, Gen
|
||||
c.column_name,
|
||||
(CASE WHEN (c.is_nullable = 'NO' AND (tc.constraint_type IS DISTINCT FROM 'PRIMARY KEY')) THEN '1' ELSE '0' END) AS is_required,
|
||||
(CASE WHEN (tc.constraint_type = 'PRIMARY KEY') THEN '1' ELSE '0' END) AS is_pk,
|
||||
(CASE WHEN EXISTS (SELECT 1 FROM information_schema.table_constraints uc JOIN information_schema.key_column_usage kcu ON uc.constraint_name = kcu.constraint_name WHERE uc.table_name = c.table_name AND uc.table_schema = c.table_schema AND uc.constraint_type = 'UNIQUE' AND kcu.column_name = c.column_name) THEN '1' ELSE '0' END) AS is_unique,
|
||||
c.ordinal_position AS sort,
|
||||
COALESCE(pgd.description, '') AS column_comment,
|
||||
(CASE WHEN c.column_default LIKE 'nextval%' THEN '1' ELSE '0' END) AS is_increment,
|
||||
c.udt_name AS column_type
|
||||
c.udt_name AS column_type,
|
||||
c.character_maximum_length AS column_length,
|
||||
c.column_default AS column_default
|
||||
FROM information_schema.columns c
|
||||
LEFT JOIN information_schema.key_column_usage kcu
|
||||
ON c.table_name = kcu.table_name AND c.column_name = kcu.column_name AND kcu.table_schema = c.table_schema
|
||||
@@ -436,19 +465,22 @@ class GenTableColumnCRUD(CRUDBase[GenTableColumnModel, GenTableColumnSchema, Gen
|
||||
elif settings.DATABASE_TYPE == "mysql":
|
||||
query_sql = """
|
||||
SELECT
|
||||
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,
|
||||
(CASE WHEN extra = 'auto_increment' THEN '1' ELSE '0' end) AS is_increment,
|
||||
column_type
|
||||
c.column_name,
|
||||
(CASE WHEN (c.is_nullable = 'NO' AND c.column_key != 'PRI') THEN '1' ELSE '0' END) AS is_required,
|
||||
(CASE WHEN c.column_key = 'PRI' THEN '1' ELSE '0' END) AS is_pk,
|
||||
(CASE WHEN EXISTS (SELECT 1 FROM information_schema.statistics s WHERE s.table_schema = c.table_schema AND s.table_name = c.table_name AND s.column_name = c.column_name AND s.non_unique = 0 AND s.index_name != 'PRIMARY') THEN '1' ELSE '0' END) AS is_unique,
|
||||
c.ordinal_position AS sort,
|
||||
c.column_comment,
|
||||
(CASE WHEN c.extra = 'auto_increment' THEN '1' ELSE '0' end) AS is_increment,
|
||||
c.column_type,
|
||||
c.character_maximum_length AS column_length,
|
||||
c.column_default AS column_default
|
||||
FROM
|
||||
information_schema.columns
|
||||
information_schema.columns c
|
||||
WHERE
|
||||
table_schema = (SELECT DATABASE())
|
||||
AND table_name = :table_name
|
||||
ORDER BY ordinal_position
|
||||
c.table_schema = (SELECT DATABASE())
|
||||
AND c.table_name = :table_name
|
||||
ORDER BY c.ordinal_position
|
||||
"""
|
||||
else:
|
||||
# 修复SQLite查询语句,使用PRAGMA获取表结构信息
|
||||
@@ -457,10 +489,13 @@ class GenTableColumnCRUD(CRUDBase[GenTableColumnModel, GenTableColumnSchema, Gen
|
||||
name as column_name,
|
||||
(CASE WHEN (type != '' AND pk != 1) THEN '1' ELSE '0' END) AS is_required,
|
||||
(CASE WHEN pk = 1 THEN '1' ELSE '0' END) AS is_pk,
|
||||
(CASE WHEN (SELECT COUNT(*) FROM pragma_index_list(:table_name) pil JOIN pragma_index_info(pil.name) pii ON 1=1 WHERE pil.unique = 1 AND pii.name = pragma_table_info.name AND pil.name NOT LIKE 'sqlite_%') > 0 THEN '1' ELSE '0' END) AS is_unique,
|
||||
cid AS sort,
|
||||
'' as column_comment,
|
||||
(CASE WHEN type LIKE '%AUTOINCREMENT%' THEN '1' ELSE '0' END) AS is_increment,
|
||||
type as column_type
|
||||
type as column_type,
|
||||
(CASE WHEN type LIKE 'varchar(%' OR type LIKE 'char(%' THEN substr(type, instr(type, '(') + 1, instr(type, ')') - instr(type, '(') - 1) ELSE NULL END) AS column_length,
|
||||
dflt_value AS column_default
|
||||
FROM
|
||||
pragma_table_info(:table_name)
|
||||
ORDER BY cid
|
||||
@@ -473,10 +508,13 @@ class GenTableColumnCRUD(CRUDBase[GenTableColumnModel, GenTableColumnSchema, Gen
|
||||
column_name=row[0],
|
||||
is_required=row[1],
|
||||
is_pk=row[2],
|
||||
sort=row[3],
|
||||
column_comment=row[4],
|
||||
is_increment=row[5],
|
||||
column_type=row[6]
|
||||
is_unique=row[3],
|
||||
sort=row[4],
|
||||
column_comment=row[5],
|
||||
is_increment=row[6],
|
||||
column_type=row[7],
|
||||
column_length=str(row[8]) if row[8] is not None else None,
|
||||
column_default=str(row[9]) if row[9] is not None else None
|
||||
)
|
||||
for row in rows
|
||||
]
|
||||
|
||||
@@ -49,6 +49,8 @@ class GenTableColumnModel(CreatorMixin):
|
||||
column_name: Mapped[Optional[str]] = mapped_column(String(200), nullable=True, comment='列名称')
|
||||
column_comment: Mapped[Optional[str]] = mapped_column(String(500), nullable=True, comment='列描述')
|
||||
column_type: Mapped[Optional[str]] = mapped_column(String(100), nullable=True, comment='列类型')
|
||||
column_length: Mapped[Optional[str]] = mapped_column(String(50), nullable=True, comment='列长度')
|
||||
column_default: Mapped[Optional[str]] = mapped_column(String(200), nullable=True, comment='列默认值')
|
||||
python_type: Mapped[Optional[str]] = mapped_column(String(500), nullable=True, comment='PYTHON类型')
|
||||
python_field: Mapped[Optional[str]] = mapped_column(String(200), nullable=True, comment='PYTHON字段名')
|
||||
is_pk: Mapped[Optional[str]] = mapped_column(String(1), nullable=True, comment='是否主键(1是)')
|
||||
|
||||
@@ -84,6 +84,8 @@ class GenTableColumnSchema(BaseModel):
|
||||
column_name: Optional[str] = Field(default=None, description='列名称')
|
||||
column_comment: Optional[str] = Field(default=None, description='列描述')
|
||||
column_type: Optional[str] = Field(default=None, description='列类型')
|
||||
column_length: Optional[str] = Field(default=None, description='列长度')
|
||||
column_default: Optional[str] = Field(default=None, description='列默认值')
|
||||
python_type: Optional[str] = Field(default=None, description='python类型')
|
||||
python_field: Optional[str] = Field(default=None, description='python字段名')
|
||||
is_pk: Optional[str] = Field(default=None, description='是否主键(1是)')
|
||||
|
||||
@@ -10,6 +10,7 @@ from sqlglot import parse as sqlglot_parse
|
||||
|
||||
from app.config.setting import settings
|
||||
from app.core.logger import logger
|
||||
from app.common.response import ErrorResponse
|
||||
from app.core.exceptions import CustomException
|
||||
from app.api.v1.module_system.auth.schema import AuthSchema
|
||||
from app.utils.gen_util import GenUtils
|
||||
@@ -126,16 +127,20 @@ class GenTableService:
|
||||
|
||||
# 为每个字段初始化并保存到数据库
|
||||
for column in gen_table_columns:
|
||||
# 将GenTableColumnOutSchema转换为GenTableColumnSchema,确保is_*字段为字符串格式
|
||||
# 将GenTableColumnOutSchema转换为GenTableColumnSchema,确保所有字段正确设置
|
||||
column_schema = GenTableColumnSchema(
|
||||
table_id=table.id,
|
||||
column_name=column.column_name,
|
||||
column_comment=column.column_comment,
|
||||
column_type=column.column_type,
|
||||
# 确保这些字段为字符串格式,'1'表示true,'0'表示false
|
||||
column_length=column.column_length if column.column_length is not None else '',
|
||||
column_default=column.column_default if column.column_default is not None else '',
|
||||
python_type=column.python_type,
|
||||
python_field=column.python_field,
|
||||
is_pk=str(column.is_pk) if column.is_pk is not None else '0',
|
||||
is_increment=str(column.is_increment) if column.is_increment is not None else '0',
|
||||
is_required=str(column.is_required) if column.is_required is not None else '0',
|
||||
is_unique=str(column.is_unique) if column.is_unique is not None else '0',
|
||||
sort=column.sort
|
||||
)
|
||||
# 初始化字段属性
|
||||
@@ -151,26 +156,51 @@ class GenTableService:
|
||||
async def create_table_service(cls, auth: AuthSchema, sql: str) -> Literal[True] | None:
|
||||
"""创建表结构并导入至代码生成模块。
|
||||
- 校验:使用`sqlglot`确保仅包含`CREATE TABLE`语句;失败抛出明确异常。
|
||||
- 唯一性检查:在创建前检查该表是否已存在于数据库中。
|
||||
"""
|
||||
# 验证SQL非空
|
||||
if not sql or not sql.strip():
|
||||
raise CustomException(msg='SQL语句不能为空')
|
||||
|
||||
try:
|
||||
# 解析SQL语句
|
||||
sql_statements = sqlglot_parse(sql, dialect=settings.DATABASE_TYPE)
|
||||
|
||||
# 校验sql语句是否为合法的建表语句
|
||||
if not cls.__is_valid_create_table(sql_statements):
|
||||
raise CustomException(msg='sql语句不是合法的建表语句')
|
||||
|
||||
# 获取要创建的表名
|
||||
table_names = cls.__get_table_names(sql_statements)
|
||||
# 执行SQL语句创建表
|
||||
result = await GenTableCRUD(auth=auth).create_table_by_sql(sql)
|
||||
if not result:
|
||||
raise CustomException(msg='创建表失败,请检查SQL语句,请确保语法是否符合标准,并检查后端日志')
|
||||
if not table_names:
|
||||
raise CustomException(msg='无法从SQL语句中提取表名')
|
||||
|
||||
# 创建CRUD实例
|
||||
gen_table_crud = GenTableCRUD(auth=auth)
|
||||
|
||||
# 检查每个表是否已存在
|
||||
for table_name in table_names:
|
||||
# 检查数据库中是否已存在该表
|
||||
if await gen_table_crud.check_table_exists(table_name):
|
||||
raise CustomException(msg=f'表 {table_name} 已存在,请检查并修改表名后重试')
|
||||
|
||||
# 检查代码生成模块中是否已导入该表
|
||||
existing_table = await gen_table_crud.get_gen_table_by_name(table_name)
|
||||
if existing_table:
|
||||
raise CustomException(msg=f'表 {table_name} 已在代码生成模块中存在,请检查并修改表名后重试')
|
||||
|
||||
# 表不存在,执行SQL语句创建表
|
||||
await gen_table_crud.create_table_by_sql(sql)
|
||||
|
||||
# 导入表结构到代码生成模块
|
||||
gen_table_list = await cls.get_gen_db_table_list_by_name_service(auth, table_names)
|
||||
import_result = await cls.import_gen_table_service(auth, gen_table_list)
|
||||
return import_result
|
||||
except CustomException:
|
||||
# 直接传递已格式化的CustomException
|
||||
raise
|
||||
except Exception as e:
|
||||
raise CustomException(msg=f'创建表结构失败: {str(e)}')
|
||||
raise CustomException(msg=f'创建表结构失败: {str(e)}。SQL预览: {sql}')
|
||||
|
||||
@classmethod
|
||||
def __is_valid_create_table(cls, sql_statements: List[Expression | None]) -> bool:
|
||||
@@ -397,6 +427,11 @@ class GenTableService:
|
||||
for column in db_table_columns:
|
||||
# 仅在缺省时初始化默认属性(包含 table_id 关联)
|
||||
GenUtils.init_column_field(column, table)
|
||||
# 确保column_length和column_default字段有默认值
|
||||
if column.column_length is None:
|
||||
column.column_length = ''
|
||||
if column.column_default is None:
|
||||
column.column_default = ''
|
||||
if column.column_name in table_column_map:
|
||||
prev_column = table_column_map[column.column_name]
|
||||
# 复用旧记录ID,确保执行更新
|
||||
@@ -418,6 +453,12 @@ class GenTableService:
|
||||
is_pk_bool = bool(getattr(prev_column, 'pk', False)) or (prev_column.is_pk == '1')
|
||||
if not is_pk_bool:
|
||||
column.is_required = keep_str(prev_column.is_required, column.is_required)
|
||||
column.column_length = keep_str(prev_column.column_length, column.column_length)
|
||||
column.column_default = keep_str(prev_column.column_default, column.column_default)
|
||||
column.python_type = keep_str(prev_column.python_type, column.python_type)
|
||||
column.python_field = keep_str(prev_column.python_field, column.python_field)
|
||||
column.is_pk = keep_str(prev_column.is_pk, column.is_pk)
|
||||
column.is_increment = keep_str(prev_column.is_increment, column.is_increment)
|
||||
column.is_unique = keep_str(prev_column.is_unique, column.is_unique)
|
||||
column.is_insert = keep_str(prev_column.is_insert, column.is_insert)
|
||||
column.is_edit = keep_str(prev_column.is_edit, column.is_edit)
|
||||
@@ -534,6 +575,7 @@ class GenTableColumnService:
|
||||
if hasattr(gen_table_column, 'is_query') and gen_table_column.is_query is not None and not isinstance(gen_table_column.is_query, str):
|
||||
gen_table_column.is_query = str(gen_table_column.is_query)
|
||||
|
||||
|
||||
# 转换为输出模型
|
||||
column_out = GenTableColumnOutSchema.model_validate(gen_table_column)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user