feat(gencode): 增强代码生成模块功能

- 添加列长度和默认值字段支持
- 改进表创建时的唯一性检查
- 优化字段属性初始化逻辑
- 增强前端表格展示和编辑功能
- 修复任务调度状态获取问题
- 清理和优化文档内容

refactor: 重构数据库查询逻辑

- 统一不同数据库类型的字段查询方式
- 添加唯一性约束检查
- 优化字段类型转换处理

fix: 修复任务重启逻辑

- 确保任务重启时使用最新配置
- 改进任务状态获取方法

docs: 更新部署文档

- 添加详细部署说明
- 优化文档结构
- 移除冗余内容

chore: 清理.gitignore和.gitattributes

- 移除冗余配置
- 更新忽略规则
This commit is contained in:
zhangtao
2025-11-07 01:46:50 +08:00
parent 4d171b19ac
commit fd4598ea39
17 changed files with 538 additions and 409 deletions
@@ -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)