mirror of
https://github.com/fastapiadmin/FastapiAdmin.git
synced 2026-09-20 20:39:55 +00:00
refactor(gencode): 重构代码生成模块,优化字段初始化和类型处理
fix(setting): 确保日志目录存在 perf(logger): 改进日志文件轮转处理,优化异常处理 feat(gencode): 添加子表关联字段支持 style(constant): 更新字段常量命名规范 chore: 移除前端低代码生成器相关文件 fix(gencode): 修复CRUD操作中的字段过滤逻辑 refactor(gen_util): 重构字段初始化逻辑,增强类型安全 docs: 更新代码注释和文档 test: 移除无效测试文件 build: 更新依赖版本 ci: 优化CI配置
This commit is contained in:
@@ -81,12 +81,7 @@ class GenTableCRUD(CRUDBase[GenTableModel, GenTableSchema, GenTableSchema]):
|
||||
- Sequence[GenTableModel]: 业务表列表信息。
|
||||
"""
|
||||
# 使用基础CRUD的list与like检索
|
||||
search_dict: Dict = {}
|
||||
if search and search.table_name:
|
||||
search_dict["table_name"] = ("like", search.table_name)
|
||||
if search and search.table_comment:
|
||||
search_dict["table_comment"] = ("like", search.table_comment)
|
||||
return await self.list(search=search_dict, order_by=[{"created_at": "desc"}], preload=preload)
|
||||
return await self.list(search=search.__dict__, order_by=[{"created_at": "desc"}], preload=preload)
|
||||
|
||||
async def add_gen_table(self, add_model: GenTableSchema) -> GenTableModel:
|
||||
"""
|
||||
@@ -111,7 +106,9 @@ class GenTableCRUD(CRUDBase[GenTableModel, GenTableSchema, GenTableSchema]):
|
||||
返回:
|
||||
- GenTableSchema: 修改后的业务表信息模型。
|
||||
"""
|
||||
obj = await self.update(id=table_id, data=edit_model)
|
||||
# 排除嵌套对象字段,避免SQLAlchemy尝试直接将字典设置到模型实例上
|
||||
data_dict = edit_model.model_dump(exclude_unset=True, exclude={"columns", "pk_column", "sub_table", "sub"})
|
||||
obj = await self.update(id=table_id, data=data_dict)
|
||||
return GenTableSchema.model_validate(obj)
|
||||
|
||||
async def delete_gen_table(self, ids: List[int]) -> None:
|
||||
@@ -520,7 +517,9 @@ class GenTableColumnCRUD(CRUDBase[GenTableColumnModel, GenTableColumnSchema, Gen
|
||||
返回:
|
||||
- Optional[GenTableColumnModel]: 业务表字段列表信息对象。
|
||||
"""
|
||||
return await self.update(id=id, data=data)
|
||||
# 将对象转换为字典,避免SQLAlchemy直接操作对象时出现的状态问题
|
||||
data_dict = data.model_dump(exclude_unset=True)
|
||||
return await self.update(id=id, data=data_dict)
|
||||
|
||||
async def delete_gen_table_column_by_table_id_dao(self, table_ids: List[int]) -> None:
|
||||
"""根据业务表ID批量删除业务表字段。
|
||||
|
||||
@@ -4,7 +4,9 @@ from typing import Optional, List
|
||||
from sqlalchemy import String, Integer, ForeignKey
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
|
||||
from app.config.setting import settings
|
||||
from app.core.base_model import CreatorMixin
|
||||
from app.utils.common_util import SqlalchemyUtil
|
||||
|
||||
|
||||
class GenTableModel(CreatorMixin):
|
||||
@@ -17,6 +19,8 @@ class GenTableModel(CreatorMixin):
|
||||
|
||||
table_name: Mapped[Optional[str]] = mapped_column(String(200), nullable=True, default='', comment='表名称')
|
||||
table_comment: Mapped[Optional[str]] = mapped_column(String(500), nullable=True, default='', comment='表描述')
|
||||
sub_table_name : Mapped[Optional[str]] = mapped_column(String(64), nullable=True, server_default=SqlalchemyUtil.get_server_default_null(settings.DATABASE_TYPE), comment='关联子表的表名',)
|
||||
sub_table_fk_name: Mapped[Optional[str]] = mapped_column(String(64), nullable=True, server_default=SqlalchemyUtil.get_server_default_null(settings.DATABASE_TYPE), comment='子表关联的外键名',)
|
||||
class_name: Mapped[Optional[str]] = mapped_column(String(100), nullable=True, default='', comment='实体类名称')
|
||||
package_name: Mapped[Optional[str]] = mapped_column(String(100), nullable=True, comment='生成包路径')
|
||||
module_name: Mapped[Optional[str]] = mapped_column(String(30), nullable=True, comment='生成模块名')
|
||||
|
||||
@@ -37,20 +37,19 @@ class GenTableBaseSchema(BaseModel):
|
||||
"""
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
table_id: Optional[int] = Field(default=None, description='编号')
|
||||
table_name: str= Field(..., description='表名称')
|
||||
table_comment: Optional[str] = Field(default=None, description='表描述')
|
||||
sub_table_name: Optional[str] = Field(default=None, description='关联子表的表名')
|
||||
sub_table_fk_name: Optional[str] = Field(default=None, description='子表关联的外键名')
|
||||
class_name: Optional[str] = Field(default=None, description='实体类名称')
|
||||
package_name: Optional[str] = Field(default=None, description='生成包路径')
|
||||
module_name: Optional[str] = Field(default=None, description='生成模块名')
|
||||
business_name: Optional[str] = Field(default=None, description='生成业务名')
|
||||
function_name: Optional[str] = Field(default=None, description='生成功能名')
|
||||
gen_type: Optional[Literal['0', '1']] = Field(default=None, description='生成代码方式(0zip压缩包 1生成项目路径)')
|
||||
options: Optional[str] = Field(default=None, description='其它生成选项')
|
||||
options: Optional[str] = Field(default=None, description='其它生成选项(JSON字符串)')
|
||||
description: Optional[str] = Field(default=None, description='功能描述')
|
||||
|
||||
params: Optional[GenTableOptionSchema] = Field(default=None, description='前端传递过来的表附加信息,转换成json字符串后放到options')
|
||||
|
||||
|
||||
class GenTableSchema(GenTableBaseSchema):
|
||||
"""代码生成业务表更新模型(扩展聚合字段)。
|
||||
@@ -62,6 +61,7 @@ class GenTableSchema(GenTableBaseSchema):
|
||||
columns: Optional[List['GenTableColumnOutSchema']] = Field(default=None, description='表列信息')
|
||||
parent_menu_id: Optional[int] = Field(default=None, description='上级菜单ID字段')
|
||||
parent_menu_name: Optional[str] = Field(default=None, description='上级菜单名称字段')
|
||||
sub: Optional[bool] = Field(default=None, description='是否为子表')
|
||||
|
||||
|
||||
class GenTableOutSchema(GenTableSchema, BaseSchema):
|
||||
@@ -70,9 +70,6 @@ class GenTableOutSchema(GenTableSchema, BaseSchema):
|
||||
- 兼容:既支持传入ORM对象,也支持字典输入。
|
||||
"""
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
# 修复:确保columns字段默认为空列表而不是None
|
||||
columns: Optional[List['GenTableColumnOutSchema']] = Field(default_factory=list, description='表列信息')
|
||||
|
||||
|
||||
class GenTableColumnSchema(BaseModel):
|
||||
|
||||
@@ -11,11 +11,10 @@ from sqlglot import parse as sqlglot_parse
|
||||
from app.config.setting import settings
|
||||
from app.core.logger import logger
|
||||
from app.core.exceptions import CustomException
|
||||
from app.common.constant import GenConstant
|
||||
from app.api.v1.module_system.auth.schema import AuthSchema
|
||||
from app.utils.gen_util import GenUtils
|
||||
from app.utils.jinja2_template_util import Jinja2TemplateUtil
|
||||
from .schema import GenTableOptionSchema, GenTableSchema, GenTableOutSchema, GenTableColumnSchema, GenTableColumnOutSchema
|
||||
from .schema import GenTableSchema, GenTableOutSchema, GenTableColumnSchema, GenTableColumnOutSchema
|
||||
from .param import GenTableQueryParam
|
||||
from .crud import GenTableColumnCRUD, GenTableCRUD
|
||||
|
||||
@@ -43,13 +42,6 @@ class GenTableService:
|
||||
gen_table = await cls.get_gen_table_by_id_service(auth, table_id)
|
||||
gen_tables = await cls.get_gen_table_all_service(auth)
|
||||
gen_columns = await GenTableColumnService.get_gen_table_column_list_by_table_id_service(auth, table_id)
|
||||
# 修复:确保options不为None再解析
|
||||
if gen_table.options:
|
||||
try:
|
||||
table_options = GenTableOptionSchema(**json.loads(gen_table.options))
|
||||
gen_table.parent_menu_id = table_options.parent_menu_id
|
||||
except Exception as e:
|
||||
logger.warning(f"解析表选项时出错: {str(e)}")
|
||||
gen_table.columns = gen_columns
|
||||
return dict(info=gen_table, rows=gen_columns, tables=gen_tables)
|
||||
|
||||
@@ -71,7 +63,7 @@ class GenTableService:
|
||||
|
||||
@classmethod
|
||||
@handle_service_exception
|
||||
async def get_gen_db_table_list_service(cls, auth: AuthSchema, search: GenTableQueryParam, order_by: Optional[List[Dict[str, str]]] = None) -> list[Any]:
|
||||
async def get_gen_db_table_list_service(cls, auth: AuthSchema, search: GenTableQueryParam) -> list[Any]:
|
||||
"""获取数据库表列表(跨方言)。
|
||||
- 备注:返回已转换为字典的结构,适用于前端直接展示;排序参数保留扩展位但当前未使用。
|
||||
"""
|
||||
@@ -89,12 +81,6 @@ class GenTableService:
|
||||
raise CustomException(msg="表名列表不能为空")
|
||||
|
||||
gen_db_table_list_result = await GenTableCRUD(auth).get_db_table_list_by_names(table_names)
|
||||
|
||||
# 检查是否有未找到的表
|
||||
found_table_names = [table.table_name for table in gen_db_table_list_result]
|
||||
missing_tables = [name for name in table_names if name not in found_table_names]
|
||||
if missing_tables:
|
||||
raise CustomException(msg=f"以下数据表不存在: {', '.join(missing_tables)}")
|
||||
|
||||
# 修复:将GenDBTableSchema对象转换为字典后再传递给GenTableOutSchema
|
||||
result = []
|
||||
@@ -119,9 +105,6 @@ class GenTableService:
|
||||
existing_tables = []
|
||||
for table in gen_table_list:
|
||||
table_name = table.table_name
|
||||
# 确保table_name不为None
|
||||
if table_name is None:
|
||||
raise CustomException(msg="表名不能为空")
|
||||
# 检查表是否已存在
|
||||
existing_table = await GenTableCRUD(auth).get_gen_table_by_name(table_name)
|
||||
if existing_table:
|
||||
@@ -137,21 +120,22 @@ class GenTableService:
|
||||
GenUtils.init_table(table)
|
||||
add_gen_table = await GenTableCRUD(auth).add_gen_table(table)
|
||||
if add_gen_table:
|
||||
table.table_id = add_gen_table.id
|
||||
table.id = add_gen_table.id
|
||||
# 获取数据库表的字段信息
|
||||
gen_table_columns = await GenTableColumnCRUD(auth).get_gen_db_table_columns_by_name(table_name)
|
||||
|
||||
# 为每个字段初始化并保存到数据库
|
||||
for column in gen_table_columns:
|
||||
# 将GenTableColumnOutSchema转换为GenTableColumnSchema
|
||||
# 将GenTableColumnOutSchema转换为GenTableColumnSchema,确保is_*字段为字符串格式
|
||||
column_schema = GenTableColumnSchema(
|
||||
table_id=table.table_id,
|
||||
table_id=table.id,
|
||||
column_name=column.column_name,
|
||||
column_comment=column.column_comment,
|
||||
column_type=column.column_type,
|
||||
is_pk=column.is_pk,
|
||||
is_increment=column.is_increment,
|
||||
is_required=column.is_required,
|
||||
# 确保这些字段为字符串格式,'1'表示true,'0'表示false
|
||||
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',
|
||||
sort=column.sort
|
||||
)
|
||||
# 初始化字段属性
|
||||
@@ -235,25 +219,21 @@ class GenTableService:
|
||||
async def update_gen_table_service(cls, auth: AuthSchema, data: GenTableSchema, table_id: int) -> Dict[str, Any]:
|
||||
"""编辑业务表信息(含选项与字段)。
|
||||
- 备注:将`params`序列化写入`options`以持久化;仅更新存在`id`的列,避免误创建。
|
||||
"""
|
||||
|
||||
"""
|
||||
# 处理params为None的情况
|
||||
gen_table_info = await cls.get_gen_table_by_id_service(auth, table_id)
|
||||
if gen_table_info.id:
|
||||
try:
|
||||
# 处理params为None的情况
|
||||
edit_gen_table = data.model_dump(exclude_unset=True, by_alias=True)
|
||||
params = edit_gen_table.get('params')
|
||||
if params:
|
||||
edit_gen_table['options'] = json.dumps(params)
|
||||
# 将字典转换为GenTableSchema对象
|
||||
gen_table_schema = GenTableSchema(**edit_gen_table)
|
||||
result = await GenTableCRUD(auth).edit_gen_table(table_id, gen_table_schema)
|
||||
# 直接调用edit_gen_table方法,它会在内部处理排除嵌套字段的逻辑
|
||||
result = await GenTableCRUD(auth).edit_gen_table(table_id, data)
|
||||
|
||||
# 处理data.columns为None的情况
|
||||
if data.columns:
|
||||
for gen_table_column in data.columns:
|
||||
# 确保column有id字段
|
||||
if hasattr(gen_table_column, 'id') and gen_table_column.id:
|
||||
await GenTableColumnCRUD(auth).update_gen_table_column_crud(gen_table_column.id, gen_table_column)
|
||||
column_schema = GenTableColumnSchema(**gen_table_column.model_dump())
|
||||
await GenTableColumnCRUD(auth).update_gen_table_column_crud(gen_table_column.id, column_schema)
|
||||
return result.model_dump()
|
||||
except Exception as e:
|
||||
raise CustomException(msg=str(e))
|
||||
@@ -287,12 +267,8 @@ class GenTableService:
|
||||
raise CustomException(msg='业务表不存在')
|
||||
|
||||
result = GenTableOutSchema.model_validate(gen_table)
|
||||
# 确保columns字段为列表,即使为None
|
||||
if result.columns is None:
|
||||
result.columns = []
|
||||
|
||||
return result
|
||||
|
||||
|
||||
@classmethod
|
||||
@handle_service_exception
|
||||
@@ -412,10 +388,7 @@ class GenTableService:
|
||||
gen_table = await GenTableCRUD(auth).get_gen_table_by_name(table_name)
|
||||
if not gen_table:
|
||||
raise CustomException(msg='业务表不存在')
|
||||
table = GenTableSchema.model_validate(gen_table)
|
||||
# 关键修复:确保 table.table_id 正确设置为持久化的表ID,否则列无法关联到该表
|
||||
if getattr(table, 'table_id', None) is None:
|
||||
table.table_id = getattr(gen_table, 'id', None)
|
||||
table = GenTableOutSchema.model_validate(gen_table)
|
||||
table_columns = table.columns or []
|
||||
table_column_map = {column.column_name: column for column in table_columns}
|
||||
db_table_columns = await GenTableColumnCRUD(auth).get_gen_db_table_columns_by_name(table_name)
|
||||
@@ -457,7 +430,7 @@ class GenTableService:
|
||||
await GenTableColumnCRUD(auth).create_gen_table_column_crud(column)
|
||||
else:
|
||||
# 设置table_id以确保新字段能正确关联到表
|
||||
column.table_id = table.table_id
|
||||
column.table_id = table.id
|
||||
await GenTableColumnCRUD(auth).create_gen_table_column_crud(column)
|
||||
del_columns = [column for column in table_columns if column.column_name not in db_table_column_names]
|
||||
if del_columns:
|
||||
@@ -542,8 +515,38 @@ class GenTableColumnService:
|
||||
result = []
|
||||
for gen_table_column in gen_table_column_list_result:
|
||||
try:
|
||||
# 转换为输出模型前确保必要字段正确设置
|
||||
# 确保is_*字段为字符串格式
|
||||
if hasattr(gen_table_column, 'is_pk') and gen_table_column.is_pk is not None and not isinstance(gen_table_column.is_pk, str):
|
||||
gen_table_column.is_pk = str(gen_table_column.is_pk)
|
||||
if hasattr(gen_table_column, 'is_increment') and gen_table_column.is_increment is not None and not isinstance(gen_table_column.is_increment, str):
|
||||
gen_table_column.is_increment = str(gen_table_column.is_increment)
|
||||
if hasattr(gen_table_column, 'is_required') and gen_table_column.is_required is not None and not isinstance(gen_table_column.is_required, str):
|
||||
gen_table_column.is_required = str(gen_table_column.is_required)
|
||||
if hasattr(gen_table_column, 'is_unique') and gen_table_column.is_unique is not None and not isinstance(gen_table_column.is_unique, str):
|
||||
gen_table_column.is_unique = str(gen_table_column.is_unique)
|
||||
if hasattr(gen_table_column, 'is_insert') and gen_table_column.is_insert is not None and not isinstance(gen_table_column.is_insert, str):
|
||||
gen_table_column.is_insert = str(gen_table_column.is_insert)
|
||||
if hasattr(gen_table_column, 'is_edit') and gen_table_column.is_edit is not None and not isinstance(gen_table_column.is_edit, str):
|
||||
gen_table_column.is_edit = str(gen_table_column.is_edit)
|
||||
if hasattr(gen_table_column, 'is_list') and gen_table_column.is_list is not None and not isinstance(gen_table_column.is_list, str):
|
||||
gen_table_column.is_list = str(gen_table_column.is_list)
|
||||
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)
|
||||
|
||||
# 确保输出模型中的布尔字段正确设置
|
||||
column_out.pk = column_out.is_pk == '1'
|
||||
column_out.increment = column_out.is_increment == '1'
|
||||
column_out.required = column_out.is_required == '1'
|
||||
column_out.unique = column_out.is_unique == '1'
|
||||
column_out.insert = column_out.is_insert == '1'
|
||||
column_out.edit = column_out.is_edit == '1'
|
||||
column_out.list = column_out.is_list == '1'
|
||||
column_out.query = column_out.is_query == '1'
|
||||
|
||||
result.append(column_out)
|
||||
except Exception as e:
|
||||
logger.warning(f"转换字段模型时出错: {str(e)}")
|
||||
|
||||
Reference in New Issue
Block a user