mirror of
https://github.com/fastapiadmin/FastapiAdmin.git
synced 2026-09-20 20:39:55 +00:00
feat: 重构代码生成模块并优化定时任务功能
refactor(backend): 清理冗余模板文件并统一模板结构 fix(backend): 修复定时任务状态显示与操作逻辑 perf(backend): 优化数据库连接日志和初始化流程 style(frontend): 调整应用列表空状态显示样式 docs: 更新README中的命令说明和环境配置 chore: 添加banner.txt和统一环境变量格式
This commit is contained in:
@@ -1,29 +1 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from fastapi import APIRouter
|
||||
|
||||
# 系统管理模块
|
||||
from .module_system import SystemRouter
|
||||
|
||||
# 监控管理模块
|
||||
from .module_monitor import MonitorRouter
|
||||
|
||||
# 通用模块
|
||||
from .module_common import CommonRouter
|
||||
|
||||
# 应用模块
|
||||
from .module_application import ApplicationRouter
|
||||
|
||||
# 代码生成模块
|
||||
from .module_generator import GeneratorRouter
|
||||
|
||||
|
||||
# 创建主路由
|
||||
router = APIRouter()
|
||||
|
||||
# 注册各模块路由
|
||||
router.include_router(SystemRouter)
|
||||
router.include_router(MonitorRouter)
|
||||
router.include_router(CommonRouter)
|
||||
router.include_router(ApplicationRouter)
|
||||
router.include_router(GeneratorRouter)
|
||||
@@ -1,15 +1 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from fastapi import APIRouter
|
||||
|
||||
from .myapp.controller import MyAppRouter
|
||||
from .ai.controller import AIRouter
|
||||
from .job.controller import JobRouter
|
||||
|
||||
|
||||
ApplicationRouter = APIRouter(prefix="/application")
|
||||
|
||||
# 包含所有子路由
|
||||
ApplicationRouter.include_router(MyAppRouter)
|
||||
ApplicationRouter.include_router(AIRouter)
|
||||
ApplicationRouter.include_router(JobRouter)
|
||||
# -*- coding: utf-8 -*-
|
||||
@@ -149,4 +149,13 @@ class JobLogCRUD(CRUDBase[JobLogModel, JobLogCreateSchema, JobLogUpdateSchema]):
|
||||
参数:
|
||||
- ids (List[int]): 定时任务日志ID列表
|
||||
"""
|
||||
return await self.delete(ids=ids)
|
||||
return await self.delete(ids=ids)
|
||||
|
||||
async def clear_obj_log_crud(self) -> None:
|
||||
"""
|
||||
清除定时任务日志
|
||||
|
||||
注意:
|
||||
- 此操作会删除所有定时任务日志,请谨慎操作
|
||||
"""
|
||||
return await self.clear()
|
||||
@@ -63,10 +63,10 @@ class JobService:
|
||||
exist_obj = await JobCRUD(auth).get(name=data.name)
|
||||
if exist_obj:
|
||||
raise CustomException(msg='创建失败,该定时任务已存在')
|
||||
if data.trigger == 'cron' and data.trigger_args and not CronUtil.validate_cron_expression(data.trigger_args):
|
||||
raise CustomException(msg=f'新增定时任务{data.name}失败, Cron表达式不正确')
|
||||
|
||||
obj = await JobCRUD(auth).create_obj_crud(data=data)
|
||||
if not obj:
|
||||
raise CustomException(msg='创建失败,该数据定时任务不存在')
|
||||
SchedulerUtil().add_job(job_info=obj)
|
||||
return JobOutSchema.model_validate(obj).model_dump()
|
||||
|
||||
@@ -109,6 +109,10 @@ class JobService:
|
||||
exist_obj = await JobCRUD(auth).get_obj_by_id_crud(id=id)
|
||||
if not exist_obj:
|
||||
raise CustomException(msg='删除失败,该数据定时任务不存在')
|
||||
obj = await JobLogCRUD(auth).get(job_id=id)
|
||||
if obj:
|
||||
raise CustomException(msg=f'删除失败,该定时任务存 {exist_obj.name} 在日志记录')
|
||||
|
||||
SchedulerUtil.remove_job(job_id=id)
|
||||
await JobCRUD(auth).delete_obj_crud(ids=ids)
|
||||
|
||||
@@ -122,6 +126,7 @@ class JobService:
|
||||
- auth (AuthSchema): 认证信息模型
|
||||
"""
|
||||
SchedulerUtil().clear_jobs()
|
||||
await JobLogCRUD(auth).clear_obj_log_crud()
|
||||
await JobCRUD(auth).clear_obj_crud()
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -1,11 +1 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from fastapi import APIRouter
|
||||
|
||||
from .file.controller import FileRouter
|
||||
|
||||
|
||||
CommonRouter = APIRouter(prefix="/common")
|
||||
|
||||
# 包含所有子路由
|
||||
CommonRouter.include_router(FileRouter)
|
||||
# -*- coding: utf-8 -*-
|
||||
@@ -1,12 +1 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from fastapi import APIRouter
|
||||
from .gencode.controller import GenRouter
|
||||
from .demo.controller import DemoRouter
|
||||
|
||||
# 创建代码生成模块路由
|
||||
GeneratorRouter = APIRouter(prefix="/generator")
|
||||
|
||||
# 包含代码生成路由
|
||||
GeneratorRouter.include_router(GenRouter)
|
||||
GeneratorRouter.include_router(DemoRouter)
|
||||
|
||||
@@ -103,9 +103,8 @@ async def gen_table_detail_controller(
|
||||
返回:
|
||||
- JSONResponse: 包含业务表详细信息的JSON响应
|
||||
"""
|
||||
gen_table = await GenTableService.get_gen_table_by_id_service(auth, table_id)
|
||||
gen_tables = await GenTableService.get_gen_table_all_service(auth)
|
||||
gen_table_detail_result = dict(info=gen_table.model_dump(), rows=gen_table.model_dump()['columns'], tables=[gen_table.model_dump() for gen_table in gen_tables])
|
||||
# 统一走服务层的聚合逻辑,避免控制器拼装重复代码
|
||||
gen_table_detail_result = await GenTableService.get_gen_table_detail_service(auth, table_id)
|
||||
logger.info(f'获取table_id为{table_id}的信息成功')
|
||||
return SuccessResponse(data=gen_table_detail_result, msg="获取业务表详细信息成功")
|
||||
|
||||
@@ -188,18 +187,8 @@ async def batch_gen_code_controller(
|
||||
返回:
|
||||
- StreamResponse: 包含批量生成代码的ZIP文件流响应
|
||||
"""
|
||||
# 检查table_names是否为空
|
||||
if not table_names:
|
||||
logger.error('表名列表不能为空')
|
||||
# 返回一个空的StreamResponse,包含错误信息
|
||||
error_content = bytes(f'{RET.ERROR.msg}: 表名列表不能为空', 'utf-8')
|
||||
return StreamResponse(
|
||||
data=bytes2file_response(error_content),
|
||||
media_type='text/plain',
|
||||
headers={'Content-Disposition': 'attachment; filename=error.txt'}
|
||||
)
|
||||
batch_gen_code_result = await GenTableService.batch_gen_code_service(auth, table_names)
|
||||
logger.info('批量生成代码成功')
|
||||
logger.info(f'批量生成代码成功,表名列表:{table_names}')
|
||||
return StreamResponse(
|
||||
data=bytes2file_response(batch_gen_code_result),
|
||||
media_type='application/zip',
|
||||
@@ -223,7 +212,7 @@ async def gen_code_local_controller(
|
||||
- JSONResponse: 包含生成结果的JSON响应
|
||||
"""
|
||||
result = await GenTableService.generate_code_service(auth, table_name)
|
||||
logger.info('生成代码到指定路径成功')
|
||||
logger.info(f'生成代码,表名:{table_name},到指定路径成功')
|
||||
return SuccessResponse(msg="生成代码到指定路径成功", data=result)
|
||||
|
||||
|
||||
@@ -243,7 +232,7 @@ async def preview_code_controller(
|
||||
- JSONResponse: 包含预览代码的JSON响应
|
||||
"""
|
||||
preview_code_result = await GenTableService.preview_code_service(auth, table_id)
|
||||
logger.info('预览代码成功')
|
||||
logger.info(f'预览代码,表id:{table_id},成功')
|
||||
return SuccessResponse(data=preview_code_result, msg="预览代码成功")
|
||||
|
||||
|
||||
@@ -263,5 +252,5 @@ async def sync_db_controller(
|
||||
- JSONResponse: 包含同步数据库结果的JSON响应
|
||||
"""
|
||||
result = await GenTableService.sync_db_service(auth, table_name)
|
||||
logger.info('同步数据库成功')
|
||||
logger.info(f'同步数据库,表名:{table_name},成功')
|
||||
return SuccessResponse(msg="同步数据库成功", data=result)
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
|
||||
from sqlalchemy.engine.row import Row
|
||||
from sqlalchemy import and_, delete, select, text, update
|
||||
from sqlalchemy.orm import selectinload
|
||||
from sqlglot.expressions import Expression
|
||||
from sqlalchemy import and_, select, text
|
||||
from typing import List, Optional, Sequence, Dict, Union, Any
|
||||
|
||||
from app.core.logger import logger
|
||||
@@ -45,19 +43,7 @@ class GenTableCRUD(CRUDBase[GenTableModel, GenTableSchema, GenTableSchema]):
|
||||
返回:
|
||||
- GenTableModel | None: 业务表信息对象。
|
||||
"""
|
||||
gen_table = (
|
||||
(
|
||||
await self.db.execute(
|
||||
select(GenTableModel)
|
||||
.options(selectinload(GenTableModel.columns))
|
||||
.where(GenTableModel.id == table_id)
|
||||
)
|
||||
)
|
||||
.scalars()
|
||||
.first()
|
||||
)
|
||||
|
||||
return gen_table
|
||||
return await self.get(id=table_id, preload=preload)
|
||||
|
||||
async def get_gen_table_by_name(self, table_name: str, preload: Optional[List[Union[str, Any]]] = None) -> Optional[GenTableModel]:
|
||||
"""
|
||||
@@ -70,20 +56,8 @@ class GenTableCRUD(CRUDBase[GenTableModel, GenTableSchema, GenTableSchema]):
|
||||
返回:
|
||||
- GenTableModel | None: 业务表信息对象。
|
||||
"""
|
||||
gen_table = (
|
||||
(
|
||||
await self.db.execute(
|
||||
select(GenTableModel)
|
||||
.options(selectinload(GenTableModel.columns))
|
||||
.where(GenTableModel.table_name == table_name)
|
||||
)
|
||||
)
|
||||
.scalars()
|
||||
.first()
|
||||
)
|
||||
return await self.get(table_name=table_name, preload=preload)
|
||||
|
||||
return gen_table
|
||||
|
||||
async def get_gen_table_all(self, preload: Optional[List[Union[str, Any]]] = None) -> Sequence[GenTableModel]:
|
||||
"""
|
||||
获取所有业务表信息。
|
||||
@@ -94,14 +68,7 @@ class GenTableCRUD(CRUDBase[GenTableModel, GenTableSchema, GenTableSchema]):
|
||||
返回:
|
||||
- Sequence[GenTableModel]: 所有业务表信息列表。
|
||||
"""
|
||||
gen_table_all = (
|
||||
await self.db.execute(
|
||||
select(GenTableModel)
|
||||
.options(selectinload(GenTableModel.columns))
|
||||
)
|
||||
).scalars().all()
|
||||
|
||||
return gen_table_all
|
||||
return await self.list(preload=preload)
|
||||
|
||||
async def get_gen_table_list(self, search: Optional[GenTableQueryParam] = None, preload: Optional[List[Union[str, Any]]] = None) -> Sequence[GenTableModel]:
|
||||
"""
|
||||
@@ -114,20 +81,13 @@ class GenTableCRUD(CRUDBase[GenTableModel, GenTableSchema, GenTableSchema]):
|
||||
返回:
|
||||
- Sequence[GenTableModel]: 业务表列表信息。
|
||||
"""
|
||||
# 获取所有数据
|
||||
result = await self.db.execute(
|
||||
select(GenTableModel)
|
||||
.options(selectinload(GenTableModel.columns))
|
||||
.where(
|
||||
GenTableModel.table_name.like(f"%{search.table_name}%") if search and search.table_name else GenTableModel.id.isnot(None),
|
||||
GenTableModel.table_comment.like(f"%{search.table_comment}%") if search and search.table_comment else GenTableModel.id.isnot(None),
|
||||
)
|
||||
.order_by(GenTableModel.created_at.desc())
|
||||
.distinct()
|
||||
)
|
||||
gen_table_all = result.scalars().all()
|
||||
|
||||
return gen_table_all
|
||||
# 使用基础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)
|
||||
|
||||
async def add_gen_table(self, add_model: GenTableSchema) -> GenTableModel:
|
||||
"""
|
||||
@@ -139,13 +99,8 @@ class GenTableCRUD(CRUDBase[GenTableModel, GenTableSchema, GenTableSchema]):
|
||||
返回:
|
||||
- GenTableModel: 新增的业务表信息对象。
|
||||
"""
|
||||
gen_table = GenTableModel(
|
||||
**add_model.model_dump(exclude_unset=True, exclude={"sub", "tree", "crud"})
|
||||
)
|
||||
self.db.add(gen_table)
|
||||
await self.db.flush()
|
||||
return gen_table
|
||||
|
||||
return await self.create(add_model.model_dump(exclude_unset=True, exclude={"sub", "tree", "crud"}))
|
||||
|
||||
async def edit_gen_table(self, table_id: int, edit_model: GenTableSchema) -> GenTableSchema:
|
||||
"""
|
||||
修改业务表信息。
|
||||
@@ -157,15 +112,8 @@ class GenTableCRUD(CRUDBase[GenTableModel, GenTableSchema, GenTableSchema]):
|
||||
返回:
|
||||
- GenTableSchema: 修改后的业务表信息模型。
|
||||
"""
|
||||
edit_dict_data = edit_model.model_dump(exclude_unset=True)
|
||||
await self.db.execute(
|
||||
update(GenTableModel)
|
||||
.where(GenTableModel.id == table_id)
|
||||
.values(**edit_dict_data)
|
||||
)
|
||||
await self.db.flush()
|
||||
await self.db.commit()
|
||||
return edit_model
|
||||
obj = await self.update(id=table_id, data=edit_model)
|
||||
return GenTableSchema.model_validate(obj)
|
||||
|
||||
async def delete_gen_table(self, ids: List[int]) -> None:
|
||||
"""
|
||||
@@ -174,11 +122,7 @@ class GenTableCRUD(CRUDBase[GenTableModel, GenTableSchema, GenTableSchema]):
|
||||
参数:
|
||||
- ids (List[int]): 业务表ID列表。
|
||||
"""
|
||||
await self.db.execute(
|
||||
delete(GenTableModel)
|
||||
.where(GenTableModel.id.in_(ids))
|
||||
)
|
||||
await self.db.flush()
|
||||
await self.delete(ids=ids)
|
||||
|
||||
async def get_db_table_list(self, search: Optional[GenTableQueryParam] = None) -> list[Dict]:
|
||||
"""
|
||||
@@ -193,19 +137,25 @@ class GenTableCRUD(CRUDBase[GenTableModel, GenTableSchema, GenTableSchema]):
|
||||
|
||||
# 使用更健壮的方式检测数据库方言
|
||||
if settings.DATABASE_TYPE == "postgresql":
|
||||
# 修复:PostgreSQL不提供table_comment,使用pg_catalog获取注释
|
||||
query_sql = (
|
||||
select(
|
||||
text("table_catalog as database_name"),
|
||||
text("table_name as table_name"),
|
||||
text("table_type as table_type"),
|
||||
text("table_comment as table_comment"),
|
||||
text("t.table_catalog as database_name"),
|
||||
text("t.table_name as table_name"),
|
||||
text("t.table_type as table_type"),
|
||||
text("pd.description as table_comment"),
|
||||
)
|
||||
.select_from(text("information_schema.tables"))
|
||||
.select_from(text(
|
||||
"information_schema.tables t \n"
|
||||
"LEFT JOIN pg_catalog.pg_class c ON c.relname = t.table_name \n"
|
||||
"LEFT JOIN pg_catalog.pg_namespace n ON n.nspname = t.table_schema AND c.relnamespace = n.oid \n"
|
||||
"LEFT JOIN pg_catalog.pg_description pd ON pd.objoid = c.oid AND pd.objsubid = 0"
|
||||
))
|
||||
.where(
|
||||
and_(
|
||||
text("table_catalog = (select current_database())"),
|
||||
text("is_insertable_into = 'YES'"),
|
||||
text("table_schema = 'public'"),
|
||||
text("t.table_catalog = (select current_database())"),
|
||||
text("t.is_insertable_into = 'YES'"),
|
||||
text("t.table_schema = 'public'"),
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -291,19 +241,25 @@ class GenTableCRUD(CRUDBase[GenTableModel, GenTableSchema, GenTableSchema]):
|
||||
"""
|
||||
# 使用更健壮的方式检测数据库方言
|
||||
if settings.DATABASE_TYPE == "postgresql":
|
||||
# 修复:PostgreSQL不提供table_comment,使用pg_catalog获取注释
|
||||
query_sql = (
|
||||
select(
|
||||
text("table_catalog as database_name"),
|
||||
text("table_name as table_name"),
|
||||
text("table_type as table_type"),
|
||||
text("table_comment as table_comment"),
|
||||
text("t.table_catalog as database_name"),
|
||||
text("t.table_name as table_name"),
|
||||
text("t.table_type as table_type"),
|
||||
text("pd.description as table_comment"),
|
||||
)
|
||||
.select_from(text("information_schema.tables"))
|
||||
.select_from(text(
|
||||
"information_schema.tables t \n"
|
||||
"LEFT JOIN pg_catalog.pg_class c ON c.relname = t.table_name \n"
|
||||
"LEFT JOIN pg_catalog.pg_namespace n ON n.nspname = t.table_schema AND c.relnamespace = n.oid \n"
|
||||
"LEFT JOIN pg_catalog.pg_description pd ON pd.objoid = c.oid AND pd.objsubid = 0"
|
||||
))
|
||||
.where(
|
||||
and_(
|
||||
text("table_catalog = (select current_database())"),
|
||||
text("is_insertable_into = 'YES'"),
|
||||
text("table_schema = 'public'"),
|
||||
text("t.table_catalog = (select current_database())"),
|
||||
text("t.is_insertable_into = 'YES'"),
|
||||
text("t.table_schema = 'public'"),
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -348,12 +304,10 @@ class GenTableCRUD(CRUDBase[GenTableModel, GenTableSchema, GenTableSchema]):
|
||||
)
|
||||
gen_db_table_list = (await self.db.execute(query_sql)).fetchall()
|
||||
else:
|
||||
# MySQL和PostgreSQL使用:table_names占位符
|
||||
# MySQL和PostgreSQL使用IN拼接(注意已在上方限定schema范围)
|
||||
query_sql = query_sql.where(
|
||||
text(f"table_name IN ('{table_names_str}')")
|
||||
)
|
||||
# 使用params方法正确绑定参数
|
||||
query_sql = query_sql.params(table_names=tuple(table_names))
|
||||
gen_db_table_list = (await self.db.execute(query_sql)).fetchall()
|
||||
else:
|
||||
gen_db_table_list = (await self.db.execute(query_sql)).fetchall()
|
||||
@@ -459,21 +413,29 @@ class GenTableColumnCRUD(CRUDBase[GenTableColumnModel, GenTableColumnSchema, Gen
|
||||
|
||||
# 兼容SQLite和MySQL/PostgreSQL
|
||||
if settings.DATABASE_TYPE == "postgresql":
|
||||
# 修复:PostgreSQL的主键/自增/注释需要关联系统表
|
||||
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
|
||||
FROM
|
||||
information_schema.columns
|
||||
WHERE
|
||||
table_catalog = (select current_database())
|
||||
AND table_schema = 'public'
|
||||
AND table_name = :table_name
|
||||
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,
|
||||
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
|
||||
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
|
||||
LEFT JOIN information_schema.table_constraints tc
|
||||
ON tc.constraint_name = kcu.constraint_name AND tc.table_name = c.table_name AND tc.table_schema = c.table_schema
|
||||
LEFT JOIN pg_catalog.pg_statio_all_tables st
|
||||
ON st.relname = c.table_name
|
||||
LEFT JOIN pg_catalog.pg_description pgd
|
||||
ON pgd.objoid = st.relid AND pgd.objsubid = c.ordinal_position
|
||||
WHERE c.table_catalog = current_database()
|
||||
AND c.table_schema = 'public'
|
||||
AND c.table_name = :table_name
|
||||
ORDER BY c.ordinal_position
|
||||
"""
|
||||
elif settings.DATABASE_TYPE == "mysql":
|
||||
query_sql = """
|
||||
@@ -507,9 +469,7 @@ class GenTableColumnCRUD(CRUDBase[GenTableColumnModel, GenTableColumnSchema, Gen
|
||||
"""
|
||||
|
||||
query = text(query_sql).bindparams(table_name=table_name)
|
||||
rows = (
|
||||
await self.db.execute(query)
|
||||
).fetchall()
|
||||
rows = (await self.db.execute(query)).fetchall()
|
||||
result = [
|
||||
GenTableColumnOutSchema(
|
||||
column_name=row[0],
|
||||
|
||||
@@ -5,7 +5,10 @@ from fastapi import Query
|
||||
|
||||
|
||||
class GenTableQueryParam:
|
||||
"""代码生成业务表查询参数"""
|
||||
"""代码生成业务表查询参数
|
||||
- 支持按`table_name`、`table_comment`进行模糊检索(由CRUD层实现like)。
|
||||
- 空值将被忽略,不参与过滤。
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
@@ -18,11 +21,13 @@ class GenTableQueryParam:
|
||||
|
||||
|
||||
class GenTableColumnQueryParam:
|
||||
"""代码生成业务表字段查询参数"""
|
||||
"""代码生成业务表字段查询参数
|
||||
- `column_name`按like规则模糊查询(透传到CRUD层)。
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
column_name: Optional[str] = Query(None, description="列名称"),
|
||||
) -> None:
|
||||
# 模糊查询字段
|
||||
# 模糊查询字段:约定("like", 值)格式,便于CRUD解析
|
||||
self.column_name = ("like", column_name)
|
||||
|
||||
@@ -8,6 +8,10 @@ from app.core.base_schema import BaseSchema
|
||||
|
||||
|
||||
class GenTableOptionSchema(BaseModel):
|
||||
"""代码生成表的附加选项(存入`options`字段的JSON)。
|
||||
- parent_menu_id:菜单归属;树模板依赖。
|
||||
- tree_*:树形结构必需的编码/父编码/名称字段。
|
||||
"""
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
@@ -18,6 +22,9 @@ class GenTableOptionSchema(BaseModel):
|
||||
|
||||
|
||||
class GenDBTableSchema(BaseModel):
|
||||
"""数据库中的表信息(跨方言统一结构)。
|
||||
- 供“导入表结构”与“同步结构”环节使用。
|
||||
"""
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
@@ -28,8 +35,9 @@ class GenDBTableSchema(BaseModel):
|
||||
|
||||
|
||||
class GenTableBaseSchema(BaseModel):
|
||||
"""
|
||||
代码生成业务表创建模型
|
||||
"""代码生成业务表基础模型(创建/更新共享字段)。
|
||||
- 说明:`params`为前端结构体,后端持久化为`options`的JSON。
|
||||
- 模板:`tpl_category` 区分 CRUD/Tree/Sub;`tpl_web_type` 区分 element-plus 等。
|
||||
"""
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
@@ -55,8 +63,9 @@ class GenTableBaseSchema(BaseModel):
|
||||
|
||||
|
||||
class GenTableSchema(GenTableBaseSchema):
|
||||
"""
|
||||
代码生成业务表更新模型
|
||||
"""代码生成业务表更新模型(扩展聚合字段)。
|
||||
- 聚合:`columns`字段包含字段列表;`pk_column`主键字段;子表结构`sub_table`。
|
||||
- 便捷:`sub/tree/crud`基于`tpl_category`自动推导布尔标记。
|
||||
"""
|
||||
|
||||
pk_column: Optional['GenTableColumnOutSchema'] = Field(default=None, description='主键信息')
|
||||
@@ -80,11 +89,16 @@ class GenTableSchema(GenTableBaseSchema):
|
||||
|
||||
|
||||
class GenTableOutSchema(GenTableSchema, BaseSchema):
|
||||
"""业务表输出模型(面向控制器/前端)。
|
||||
- 清洗:统一处理None值,保证`columns`为列表;文本字段为空字符串。
|
||||
- 兼容:既支持传入ORM对象,也支持字典输入。
|
||||
"""
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
# 添加数据验证和转换的root_validator
|
||||
@model_validator(mode='before')
|
||||
def handle_null_values(cls, values):
|
||||
"""将关键字段的None转换为安全默认值,避免前端渲染异常。"""
|
||||
# 处理None值,转换为空字符串或适当的默认值
|
||||
# 检查values是否为对象而非字典
|
||||
if hasattr(values, '__dict__'):
|
||||
@@ -108,8 +122,10 @@ class GenTableOutSchema(GenTableSchema, BaseSchema):
|
||||
|
||||
|
||||
class GenTableColumnSchema(BaseModel):
|
||||
"""
|
||||
代码生成业务表字段创建模型
|
||||
"""代码生成业务表字段创建模型(原始字段+生成配置)。
|
||||
- 原始:`column_name/column_type/column_comment` 等。
|
||||
- 生成:`python_type/html_type/query_type/dict_type` 等由工具初始化。
|
||||
- 标记:所有 is_* 字段默认使用字符串'1'表示启用,便于前端和模板处理。
|
||||
"""
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
@@ -135,6 +151,10 @@ class GenTableColumnSchema(BaseModel):
|
||||
|
||||
|
||||
class GenTableColumnOutSchema(GenTableColumnSchema, BaseSchema):
|
||||
"""业务表字段输出模型(布尔派生+便捷字段)。
|
||||
- 布尔:将字符串 is_* 转为布尔 `pk/increment/...`,供前端/模板快捷使用。
|
||||
- 便捷:`cap_python_field` 存放大驼峰字段名(模板场景常用)。
|
||||
"""
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
cap_python_field: Optional[str] = Field(default=None, description='字段大写形式')
|
||||
@@ -151,8 +171,8 @@ class GenTableColumnOutSchema(GenTableColumnSchema, BaseSchema):
|
||||
|
||||
|
||||
class GenTableColumnDeleteSchema(BaseModel):
|
||||
"""
|
||||
删除代码生成业务表字段模型
|
||||
"""删除代码生成业务表字段模型(批量)。
|
||||
- 说明:仅包含待删除的字段ID列表。
|
||||
"""
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
|
||||
@@ -9,34 +9,24 @@ from sqlglot.expressions import Add, Alter, Create, Delete, Drop, Expression, In
|
||||
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.common.response import SuccessResponse
|
||||
from app.api.v1.module_system.auth.schema import AuthSchema
|
||||
from app.utils.gen_util import GenUtils
|
||||
from app.utils.jinja2_template_util import Jinja2TemplateInitializerUtil, Jinja2TemplateUtil
|
||||
from .schema import GenTableOptionSchema, GenTableSchema, GenTableOutSchema, GenTableOutSchema, GenTableColumnSchema, GenTableColumnOutSchema, GenTableColumnDeleteSchema
|
||||
from .schema import GenTableOptionSchema, GenTableSchema, GenTableOutSchema, GenTableColumnSchema, GenTableColumnOutSchema, GenTableColumnDeleteSchema
|
||||
from .param import GenTableQueryParam
|
||||
from .crud import GenTableColumnCRUD, GenTableCRUD
|
||||
|
||||
|
||||
# 定义默认的GenConfig值
|
||||
GEN_PATH = "generated_code" # 默认生成路径
|
||||
|
||||
|
||||
class GenTableService:
|
||||
"""代码生成业务表服务层"""
|
||||
|
||||
@classmethod
|
||||
async def get_gen_table_detail_service(cls, auth: AuthSchema, table_id: int) -> Dict:
|
||||
"""获取业务表详细信息。
|
||||
|
||||
参数:
|
||||
- auth (AuthSchema): 认证信息。
|
||||
- table_id (int): 业务表ID。
|
||||
|
||||
返回:
|
||||
- Dict: 包含业务表详细信息、字段列表和所有业务表的字典。
|
||||
"""获取业务表详细信息(含字段与其他表列表)。
|
||||
- 备注:优先解析`options`为`GenTableOptionSchema`,设置`parent_menu_id`等选项;保证`columns`与`tables`结构完整。
|
||||
"""
|
||||
gen_table = await cls.get_gen_table_by_id_service(auth, table_id)
|
||||
gen_tables = await cls.get_gen_table_all_service(auth)
|
||||
@@ -64,30 +54,16 @@ class GenTableService:
|
||||
|
||||
@classmethod
|
||||
async def get_gen_db_table_list_service(cls, auth: AuthSchema, search: GenTableQueryParam, order_by: Optional[List[Dict[str, str]]] = None) -> list[Any]:
|
||||
"""获取数据库列表信息。
|
||||
|
||||
参数:
|
||||
- auth (AuthSchema): 认证信息。
|
||||
- search (GenTableQueryParam): 查询参数模型。
|
||||
- order_by (Optional[List[Dict[str, str]]]): 排序参数列表,默认值为None。
|
||||
|
||||
返回:
|
||||
- list[Any]: 包含数据库列表信息的任意类型列表。
|
||||
"""获取数据库表列表(跨方言)。
|
||||
- 备注:返回已转换为字典的结构,适用于前端直接展示;排序参数保留扩展位但当前未使用。
|
||||
"""
|
||||
# 确保db是AsyncSession类型
|
||||
gen_db_table_list_result = await GenTableCRUD(auth=auth).get_db_table_list(search)
|
||||
return gen_db_table_list_result
|
||||
|
||||
@classmethod
|
||||
async def get_gen_db_table_list_by_name_service(cls, auth: AuthSchema, table_names: List[str]) -> List[GenTableOutSchema]:
|
||||
"""根据表名称组获取数据库列表信息。
|
||||
|
||||
参数:
|
||||
- auth (AuthSchema): 认证信息。
|
||||
- table_names (List[str]): 业务表名称列表。
|
||||
|
||||
返回:
|
||||
- List[GenTableOutSchema]: 包含业务表列表信息的模型列表。
|
||||
"""根据表名称组获取数据库表信息。
|
||||
- 校验:如有不存在的表名,抛出明确异常;返回统一的`GenTableOutSchema`列表。
|
||||
"""
|
||||
gen_db_table_list_result = await GenTableCRUD(auth=auth).get_db_table_list_by_names(table_names)
|
||||
|
||||
@@ -106,17 +82,8 @@ class GenTableService:
|
||||
|
||||
@classmethod
|
||||
async def import_gen_table_service(cls, auth: AuthSchema, gen_table_list: List[GenTableOutSchema]) -> Literal[True] | None:
|
||||
"""导入表结构
|
||||
|
||||
参数:
|
||||
- auth (AuthSchema): 认证对象。
|
||||
- gen_table_list (List[GenTableOutSchema]): 要导入的业务表列表。
|
||||
|
||||
返回:
|
||||
- Literal[True] | None: 导入成功返回True,否则返回None。
|
||||
|
||||
异常:
|
||||
- CustomException: 当没有可导入的表结构、表已存在或导入过程中发生错误时抛出。
|
||||
"""导入表结构到生成器(持久化并初始化列)。
|
||||
- 备注:避免重复导入;为每列调用`GenUtils.init_column_field`填充默认属性,保留语义一致性。
|
||||
"""
|
||||
# 检查是否有表需要导入
|
||||
if not gen_table_list:
|
||||
@@ -171,17 +138,8 @@ class GenTableService:
|
||||
|
||||
@classmethod
|
||||
async def create_table_service(cls, auth: AuthSchema, sql: str) -> Literal[True] | None:
|
||||
"""创建表结构。
|
||||
|
||||
参数:
|
||||
- auth (AuthSchema): 认证信息。
|
||||
- sql (str): 包含建表SQL语句的字符串。
|
||||
|
||||
返回:
|
||||
- Literal[True] | None: 创建成功返回True,否则返回None。
|
||||
|
||||
异常:
|
||||
- CustomException: 当SQL语句不是合法的建表语句、创建表失败或导入表结构失败时抛出。
|
||||
"""创建表结构并导入至代码生成模块。
|
||||
- 校验:使用`sqlglot`确保仅包含`CREATE TABLE`语句;失败抛出明确异常。
|
||||
"""
|
||||
try:
|
||||
sql_statements = sqlglot_parse(sql, dialect=settings.DATABASE_TYPE)
|
||||
@@ -243,24 +201,15 @@ class GenTableService:
|
||||
|
||||
@classmethod
|
||||
async def update_gen_table_service(cls, auth: AuthSchema, data: GenTableSchema, table_id: int) -> Dict[str, Any]:
|
||||
"""编辑业务表信息。
|
||||
|
||||
参数:
|
||||
- auth (AuthSchema): 认证信息。
|
||||
- data (GenTableSchema): 包含业务表更新信息的模型。
|
||||
- table_id (int): 业务表ID。
|
||||
|
||||
返回:
|
||||
- Dict[str, Any]: 更新后的业务表信息字典。
|
||||
|
||||
异常:
|
||||
- CustomException: 当业务表不存在、更新失败或处理字段参数时抛出。
|
||||
"""编辑业务表信息(含选项与字段)。
|
||||
- 备注:将`params`序列化写入`options`以持久化;仅更新存在`id`的列,避免误创建。
|
||||
"""
|
||||
edit_gen_table = data.model_dump(exclude_unset=True, by_alias=True)
|
||||
|
||||
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)
|
||||
@@ -275,89 +224,39 @@ class GenTableService:
|
||||
await GenTableColumnCRUD(auth).update_gen_table_column_crud(gen_table_column.id, gen_table_column)
|
||||
return result.model_dump()
|
||||
except Exception as e:
|
||||
raise CustomException(msg=f'更新失败: {str(e)}')
|
||||
raise CustomException(msg=str(e))
|
||||
else:
|
||||
raise CustomException(msg='业务表不存在')
|
||||
|
||||
@classmethod
|
||||
async def delete_gen_table_service(cls, auth: AuthSchema, ids: List[int]) -> None:
|
||||
"""删除业务表信息。
|
||||
|
||||
参数:
|
||||
- auth (AuthSchema): 认证信息。
|
||||
- ids (List[int]): 业务表ID列表。
|
||||
|
||||
返回:
|
||||
- None
|
||||
|
||||
异常:
|
||||
- CustomException: 当删除失败时抛出。
|
||||
"""
|
||||
"""删除业务表信息(先删字段,再删表)。"""
|
||||
try:
|
||||
# 先删除相关的字段信息
|
||||
await GenTableColumnCRUD(auth=auth).delete_gen_table_column_by_table_id_dao(ids)
|
||||
# 再删除表信息
|
||||
await GenTableCRUD(auth=auth).delete_gen_table(ids)
|
||||
except Exception as e:
|
||||
raise CustomException(msg=f'删除失败: {str(e)}')
|
||||
raise CustomException(msg=str(e))
|
||||
|
||||
@classmethod
|
||||
async def get_gen_table_by_id_service(cls, auth: AuthSchema, table_id: int) -> GenTableOutSchema:
|
||||
"""获取需要生成代码的业务表详细信息。
|
||||
|
||||
参数:
|
||||
- auth (AuthSchema): 认证信息。
|
||||
- table_id (int): 业务表ID。
|
||||
|
||||
返回:
|
||||
- GenTableOutSchema: 包含业务表详细信息的模型。
|
||||
|
||||
异常:
|
||||
- CustomException: 当业务表不存在时抛出。
|
||||
- 备注:去除SQLAlchemy内部状态;将`None`值转为适配前端的默认值;解析`options`补充选项。
|
||||
"""
|
||||
gen_table = await GenTableCRUD(auth=auth).get_gen_table_by_id(table_id)
|
||||
if gen_table:
|
||||
# 使用更直接的转换方式
|
||||
result_dict = gen_table.__dict__.copy()
|
||||
result_dict.pop('_sa_instance_state', None)
|
||||
# 确保columns正确加载
|
||||
if hasattr(gen_table, 'columns') and gen_table.columns:
|
||||
columns_list = []
|
||||
for column in gen_table.columns:
|
||||
column_dict = column.__dict__.copy()
|
||||
column_dict.pop('_sa_instance_state', None)
|
||||
# 处理None值,转换为空字符串或适当的默认值
|
||||
for key, value in column_dict.items():
|
||||
if value is None:
|
||||
column_dict[key] = ''
|
||||
columns_list.append(column_dict)
|
||||
result_dict['columns'] = columns_list
|
||||
else:
|
||||
result_dict['columns'] = []
|
||||
# 处理其他None值,特殊处理creator_id和creator字段
|
||||
for key, value in result_dict.items():
|
||||
if value is None:
|
||||
# 对于creator_id和creator字段,保持为None而不是转换为空字符串
|
||||
if key not in ['creator_id', 'creator']:
|
||||
result_dict[key] = ''
|
||||
# 手动创建GenTableOutSchema对象
|
||||
result = GenTableOutSchema(**result_dict)
|
||||
# 设置额外选项
|
||||
result = await cls.set_table_from_options(result)
|
||||
return result
|
||||
else:
|
||||
if not gen_table:
|
||||
raise CustomException(msg='业务表不存在')
|
||||
|
||||
result = GenTableOutSchema.model_validate(gen_table)
|
||||
# 设置额外选项
|
||||
result = await cls.set_table_from_options(result)
|
||||
return result
|
||||
|
||||
|
||||
@classmethod
|
||||
async def get_gen_table_all_service(cls, auth: AuthSchema) -> List[GenTableOutSchema]:
|
||||
"""获取所有业务表信息。
|
||||
|
||||
参数:
|
||||
- auth (AuthSchema): 认证信息。
|
||||
|
||||
返回:
|
||||
- List[GenTableOutSchema]: 包含所有业务表详细信息的模型列表。
|
||||
"""
|
||||
"""获取所有业务表信息(列表)。"""
|
||||
gen_table_all = await GenTableCRUD(auth=auth).get_gen_table_all()
|
||||
gen_table_all_dict = [GenTableOutSchema.model_validate(gen_table).model_dump() for gen_table in gen_table_all]
|
||||
result = [GenTableOutSchema(**gen_table) for gen_table in gen_table_all_dict]
|
||||
@@ -366,14 +265,8 @@ class GenTableService:
|
||||
@classmethod
|
||||
async def preview_code_service(cls, auth: AuthSchema, table_id: int) -> Dict[Any, Any]:
|
||||
"""
|
||||
预览代码。
|
||||
|
||||
参数:
|
||||
- auth (AuthSchema): 认证对象。
|
||||
- table_id (int): 业务表ID。
|
||||
|
||||
返回:
|
||||
- Dict[Any, Any]: 模版文件名到渲染内容的映射。
|
||||
预览代码(根据模板渲染内存结果)。
|
||||
- 备注:构建Jinja2上下文;根据模板类型与前端类型选择模板清单;返回文件名到内容映射。
|
||||
"""
|
||||
gen_table = GenTableOutSchema.model_validate(
|
||||
await GenTableCRUD(auth).get_gen_table_by_id(table_id)
|
||||
@@ -384,7 +277,7 @@ class GenTableService:
|
||||
context = Jinja2TemplateUtil.prepare_context(gen_table)
|
||||
# 处理tpl_category和tpl_web_type为None的情况
|
||||
tpl_category = gen_table.tpl_category or ''
|
||||
tpl_web_type = gen_table.tpl_web_type or ''
|
||||
tpl_web_type = gen_table.tpl_web_type or 'element-plus'
|
||||
template_list = Jinja2TemplateUtil.get_template_list(tpl_category, tpl_web_type)
|
||||
preview_code_result = {}
|
||||
for template in template_list:
|
||||
@@ -393,62 +286,37 @@ class GenTableService:
|
||||
return preview_code_result
|
||||
|
||||
@classmethod
|
||||
async def generate_code_service(cls, auth: AuthSchema, table_name: str) -> SuccessResponse:
|
||||
"""生成代码至指定路径。
|
||||
|
||||
参数:
|
||||
- auth (AuthSchema): 认证对象。
|
||||
- table_name (str): 业务表名称。
|
||||
|
||||
返回:
|
||||
- SuccessResponse: 成功响应模型。
|
||||
|
||||
异常:
|
||||
- CustomException: 当渲染模板失败时抛出。
|
||||
async def generate_code_service(cls, auth: AuthSchema, table_name: str) -> bool:
|
||||
"""生成代码至指定路径(安全写入+可跳过覆盖)。
|
||||
- 安全:限制写入在项目根目录内;越界路径自动回退到项目根目录。
|
||||
- 覆盖:尊重`settings.allow_overwrite`,不允许时跳过写入。
|
||||
"""
|
||||
if not settings.allow_overwrite:
|
||||
logger.error('【系统预设】不允许生成文件覆盖到本地')
|
||||
raise CustomException(msg='【系统预设】不允许生成文件覆盖到本地')
|
||||
env = Jinja2TemplateInitializerUtil.init_jinja2()
|
||||
render_info = await cls.__get_gen_render_info(auth, table_name)
|
||||
gen_table_schema = render_info[3]
|
||||
skipped = 0
|
||||
for template in render_info[0]:
|
||||
try:
|
||||
render_content = await env.get_template(template).render_async(**render_info[2])
|
||||
gen_path = cls.__get_gen_path(gen_table_schema, template)
|
||||
if gen_path:
|
||||
# 只允许写入到项目根目录及其子目录
|
||||
project_root = os.path.realpath(str(settings.BASE_DIR.parent))
|
||||
target_path = os.path.realpath(gen_path)
|
||||
if not target_path.startswith(project_root):
|
||||
raise CustomException(msg='生成路径不允许,请选择项目目录内路径')
|
||||
if not gen_path:
|
||||
raise CustomException(msg='【代码生成】生成路径为空')
|
||||
|
||||
os.makedirs(os.path.dirname(gen_path), exist_ok=True)
|
||||
os.makedirs(os.path.dirname(gen_path), exist_ok=True)
|
||||
|
||||
# 覆盖控制:存在且不允许覆盖则跳过
|
||||
if os.path.exists(gen_path) and not settings.allow_overwrite:
|
||||
skipped += 1
|
||||
continue
|
||||
|
||||
with open(gen_path, 'w', encoding='utf-8') as f:
|
||||
f.write(render_content)
|
||||
with open(gen_path, 'w', encoding='utf-8') as f:
|
||||
f.write(render_content)
|
||||
except Exception as e:
|
||||
raise CustomException(msg=f'渲染模板失败,表名:{gen_table_schema.table_name},详细错误信息:{str(e)}')
|
||||
|
||||
msg = '生成代码成功'
|
||||
if skipped:
|
||||
msg += f'(已跳过 {skipped} 个已存在文件)'
|
||||
return SuccessResponse(msg=msg)
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
async def batch_gen_code_service(cls, auth: AuthSchema, table_names: List[str]) -> bytes:
|
||||
"""
|
||||
批量生成代码并打包为ZIP。
|
||||
|
||||
参数:
|
||||
- auth (AuthSchema): 认证对象。
|
||||
- table_names (List[str]): 业务表名称组。
|
||||
|
||||
返回:
|
||||
- bytes: 下载代码的ZIP二进制数据。
|
||||
- 备注:内存生成并压缩,兼容多模板类型;供下载使用。
|
||||
"""
|
||||
zip_buffer = io.BytesIO()
|
||||
with zipfile.ZipFile(zip_buffer, 'w', zipfile.ZIP_DEFLATED) as zip_file:
|
||||
@@ -465,17 +333,8 @@ class GenTableService:
|
||||
|
||||
@classmethod
|
||||
async def sync_db_service(cls, auth: AuthSchema, table_name: str) -> None:
|
||||
"""同步数据库表结构。
|
||||
|
||||
参数:
|
||||
- auth (AuthSchema): 认证对象。
|
||||
- table_name (str): 业务表名称。
|
||||
|
||||
返回:
|
||||
- None
|
||||
|
||||
异常:
|
||||
- CustomException: 当业务表不存在时抛出。
|
||||
"""同步数据库表结构至生成器(保留用户配置)。
|
||||
- 备注:按数据库实际字段重建或更新生成器字段;保留字典/查询/展示等用户自定义属性;清理已删除字段。
|
||||
"""
|
||||
gen_table = await GenTableCRUD(auth).get_gen_table_by_name(table_name)
|
||||
if not gen_table:
|
||||
@@ -537,18 +396,7 @@ class GenTableService:
|
||||
|
||||
@classmethod
|
||||
async def set_sub_table(cls, auth: AuthSchema, gen_table: GenTableOutSchema) -> None:
|
||||
"""设置主子表信息。
|
||||
|
||||
参数:
|
||||
- auth (AuthSchema): 认证对象。
|
||||
- gen_table (GenTableOutSchema): 业务表详细信息模型。
|
||||
|
||||
返回:
|
||||
- None
|
||||
|
||||
异常:
|
||||
- CustomException: 当子表不存在时抛出。
|
||||
"""
|
||||
"""设置主子表信息(如存在子表则补充其结构)。"""
|
||||
if gen_table.sub_table_name:
|
||||
gen_table_dao = GenTableCRUD(auth=auth)
|
||||
sub_table = await gen_table_dao.get_gen_table_by_name(gen_table.sub_table_name)
|
||||
@@ -557,17 +405,12 @@ class GenTableService:
|
||||
|
||||
@classmethod
|
||||
async def set_pk_column(cls, gen_table: GenTableOutSchema) -> None:
|
||||
"""设置主键列信息。
|
||||
|
||||
参数:
|
||||
- gen_table (GenTableOutSchema): 业务表详细信息模型。
|
||||
|
||||
返回:
|
||||
- None
|
||||
"""设置主键列信息(主表/子表)。
|
||||
- 备注:同时兼容`pk`布尔与`is_pk == '1'`字符串两种标识。
|
||||
"""
|
||||
if gen_table.columns:
|
||||
for column in gen_table.columns:
|
||||
if column.pk:
|
||||
if getattr(column, 'pk', None) or getattr(column, 'is_pk', '') == '1':
|
||||
gen_table.pk_column = column
|
||||
break
|
||||
if gen_table.pk_column is None and gen_table.columns:
|
||||
@@ -575,7 +418,7 @@ class GenTableService:
|
||||
if gen_table.tpl_category == GenConstant.TPL_SUB and gen_table.sub_table:
|
||||
if gen_table.sub_table.columns:
|
||||
for column in gen_table.sub_table.columns:
|
||||
if column.pk:
|
||||
if getattr(column, 'pk', None) or getattr(column, 'is_pk', '') == '1':
|
||||
gen_table.sub_table.pk_column = column
|
||||
break
|
||||
if gen_table.sub_table.pk_column is None and gen_table.sub_table.columns:
|
||||
@@ -583,22 +426,8 @@ class GenTableService:
|
||||
|
||||
@classmethod
|
||||
async def set_table_from_options(cls, gen_table: GenTableOutSchema) -> GenTableOutSchema:
|
||||
"""设置代码生成其他选项值。
|
||||
|
||||
参数:
|
||||
- gen_table (GenTableOutSchema): 业务表详细信息模型。
|
||||
|
||||
返回:
|
||||
- GenTableOutSchema: 更新后的业务表详细信息模型。
|
||||
"""
|
||||
# 处理gen_table.options为None的情况
|
||||
if gen_table.options:
|
||||
try:
|
||||
params_obj = json.loads(gen_table.options)
|
||||
except json.JSONDecodeError:
|
||||
params_obj = {}
|
||||
else:
|
||||
params_obj = {}
|
||||
"""设置代码生成其他选项值(从options反序列化)。"""
|
||||
params_obj = json.loads(gen_table.options) if gen_table.options else None
|
||||
|
||||
if params_obj:
|
||||
gen_table.tree_code = params_obj.get(GenConstant.TREE_CODE)
|
||||
@@ -611,17 +440,7 @@ class GenTableService:
|
||||
|
||||
@classmethod
|
||||
async def validate_edit(cls, edit_gen_table: GenTableSchema) -> None:
|
||||
"""编辑保存参数校验。
|
||||
|
||||
参数:
|
||||
- edit_gen_table (GenTableSchema): 编辑后的业务表模型。
|
||||
|
||||
返回:
|
||||
- None
|
||||
|
||||
异常:
|
||||
- CustomException: 当参数校验失败时抛出。
|
||||
"""
|
||||
"""编辑保存参数校验(树/子表约束)。"""
|
||||
if edit_gen_table.tpl_category == GenConstant.TPL_TREE:
|
||||
# 从options字段获取参数,而不是params
|
||||
if not edit_gen_table.options:
|
||||
@@ -660,57 +479,36 @@ class GenTableService:
|
||||
异常:
|
||||
- CustomException: 当业务表不存在或数据转换失败时抛出。
|
||||
"""
|
||||
gen_table = await GenTableCRUD(auth=auth).get_gen_table_by_name(table_name)
|
||||
gen_table_model = await GenTableCRUD(auth=auth).get_gen_table_by_name(table_name)
|
||||
# 检查表是否存在
|
||||
if gen_table is None:
|
||||
if gen_table_model is None:
|
||||
raise CustomException(msg=f"业务表 {table_name} 不存在")
|
||||
|
||||
# 确保CamelCaseUtil.transform_result返回的是字典
|
||||
transformed_result = gen_table
|
||||
if transformed_result is None:
|
||||
raise CustomException(msg=f"业务表 {table_name} 数据转换失败")
|
||||
|
||||
gen_table_schema = GenTableOutSchema.model_validate(transformed_result)
|
||||
await cls.set_sub_table(auth, gen_table_schema)
|
||||
await cls.set_pk_column(gen_table_schema)
|
||||
context = Jinja2TemplateUtil.prepare_context(gen_table_schema)
|
||||
gen_table = GenTableOutSchema.model_validate(gen_table_model)
|
||||
await cls.set_sub_table(auth, gen_table)
|
||||
await cls.set_pk_column(gen_table)
|
||||
context = Jinja2TemplateUtil.prepare_context(gen_table)
|
||||
template_list = Jinja2TemplateUtil.get_template_list(
|
||||
gen_table_schema.tpl_category or "",
|
||||
gen_table_schema.tpl_web_type or ""
|
||||
gen_table.tpl_category or "",
|
||||
gen_table.tpl_web_type or ""
|
||||
)
|
||||
# 修复:确保get_file_name返回的文件名不为空
|
||||
output_files = []
|
||||
for template in template_list:
|
||||
file_name = Jinja2TemplateUtil.get_file_name(template, gen_table_schema)
|
||||
if file_name: # 只有当文件名不为空时才添加到列表中
|
||||
output_files.append(file_name)
|
||||
output_files = [Jinja2TemplateUtil.get_file_name(template, gen_table) for template in template_list]
|
||||
|
||||
return [template_list, output_files, context, gen_table_schema]
|
||||
return [template_list, output_files, context, gen_table]
|
||||
|
||||
@classmethod
|
||||
def __get_gen_path(cls, gen_table: GenTableOutSchema, template: str) -> Optional[str]:
|
||||
"""根据GenTableOutSchema对象和模板名称生成路径。
|
||||
"""根据GenTableOutSchema对象和模板名称生成路径。"""
|
||||
gen_path = (gen_table.gen_path or '').strip()
|
||||
file_name = Jinja2TemplateUtil.get_file_name(template, gen_table)
|
||||
# 默认写入到项目根目录(backend的上一级)
|
||||
project_root = str(settings.BASE_DIR.parent)
|
||||
if gen_path in ['', '/']:
|
||||
return os.path.join(project_root, file_name)
|
||||
else:
|
||||
return os.path.join(gen_path, file_name)
|
||||
|
||||
参数:
|
||||
- gen_table (GenTableOutSchema): 业务表详细信息模型。
|
||||
- template (str): 模板名称。
|
||||
|
||||
返回:
|
||||
- Optional[str]: 生成的文件路径,若失败则返回None。
|
||||
"""
|
||||
try:
|
||||
gen_path = (gen_table.gen_path or '').strip()
|
||||
file_name = Jinja2TemplateUtil.get_file_name(template, gen_table)
|
||||
if not file_name:
|
||||
return None
|
||||
# 默认写入到项目根目录(backend的上一级)
|
||||
project_root = str(settings.BASE_DIR.parent)
|
||||
if gen_path in ['', '/']:
|
||||
return os.path.join(project_root, file_name)
|
||||
else:
|
||||
return os.path.join(gen_path, file_name)
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
class GenTableColumnService:
|
||||
@@ -718,15 +516,7 @@ class GenTableColumnService:
|
||||
|
||||
@classmethod
|
||||
async def get_gen_table_column_list_by_table_id_service(cls, auth: AuthSchema, table_id: int) -> List[GenTableColumnOutSchema]:
|
||||
"""获取业务表字段列表信息。
|
||||
|
||||
参数:
|
||||
- auth (AuthSchema): 认证对象。
|
||||
- table_id (int): 业务表ID。
|
||||
|
||||
返回:
|
||||
- List[GenTableColumnOutSchema]: 业务表字段详细信息模型列表。
|
||||
"""
|
||||
"""获取业务表字段列表信息(输出模型)。"""
|
||||
gen_table_column_list_result = await GenTableColumnCRUD(auth).list_gen_table_column_crud({"table_id": table_id})
|
||||
return [
|
||||
GenTableColumnOutSchema.model_validate(gen_table_column)
|
||||
|
||||
@@ -1,17 +1 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from fastapi import APIRouter
|
||||
|
||||
from .cache.controller import CacheRouter
|
||||
from .online.controller import OnlineRouter
|
||||
from .server.controller import ServerRouter
|
||||
from .resource.controller import ResourceRouter
|
||||
|
||||
|
||||
MonitorRouter = APIRouter(prefix="/monitor")
|
||||
|
||||
# 包含所有子路由
|
||||
MonitorRouter.include_router(CacheRouter)
|
||||
MonitorRouter.include_router(OnlineRouter)
|
||||
MonitorRouter.include_router(ServerRouter)
|
||||
MonitorRouter.include_router(ResourceRouter)
|
||||
# -*- coding: utf-8 -*-
|
||||
@@ -1,26 +1 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from fastapi import APIRouter
|
||||
from .auth.controller import AuthRouter
|
||||
from .user.controller import UserRouter
|
||||
from .role.controller import RoleRouter
|
||||
from .menu.controller import MenuRouter
|
||||
from .dept.controller import DeptRouter
|
||||
from .position.controller import PositionRouter
|
||||
from .dict.controller import DictRouter
|
||||
from .params.controller import ParamsRouter
|
||||
from .notice.controller import NoticeRouter
|
||||
from .log.controller import LogRouter
|
||||
|
||||
SystemRouter = APIRouter(prefix="/system")
|
||||
|
||||
SystemRouter.include_router(AuthRouter)
|
||||
SystemRouter.include_router(UserRouter)
|
||||
SystemRouter.include_router(RoleRouter)
|
||||
SystemRouter.include_router(MenuRouter)
|
||||
SystemRouter.include_router(DeptRouter)
|
||||
SystemRouter.include_router(PositionRouter)
|
||||
SystemRouter.include_router(DictRouter)
|
||||
SystemRouter.include_router(ParamsRouter)
|
||||
SystemRouter.include_router(NoticeRouter)
|
||||
SystemRouter.include_router(LogRouter)
|
||||
# -*- coding: utf-8 -*-
|
||||
Reference in New Issue
Block a user