refactor(module): 重构模块结构和代码生成模板

- 删除废弃的ticket和version模块相关代码
- 将resource模块从system迁移到monitor
- 移除前端资源管理相关配置
- 重构代码生成模板路径和配置
- 修复CRUD基类初始化参数问题
- 优化模板工具类路径处理
- 更新基础模型字段和配置
This commit is contained in:
zhangtao
2025-09-24 01:39:44 +08:00
parent 4a7533325a
commit d30d2faf6e
59 changed files with 663 additions and 2823 deletions
-4
View File
@@ -11,9 +11,6 @@ from .module_monitor import MonitorRouter
# 通用模块
from .module_common import CommonRouter
# 资源管理模块
from .module_resource import ResourceRouter
# 示例模块
from .module_example import ExampleRouter
@@ -34,7 +31,6 @@ router = APIRouter()
router.include_router(SystemRouter)
router.include_router(MonitorRouter)
router.include_router(CommonRouter)
router.include_router(ResourceRouter)
router.include_router(ExampleRouter)
router.include_router(ApplicationRouter)
router.include_router(AIRouter)
+1 -1
View File
@@ -14,7 +14,7 @@ class McpCRUD(CRUDBase[McpModel, McpCreateSchema, McpUpdateSchema]):
def __init__(self, auth: AuthSchema) -> None:
"""初始化CRUD"""
self.auth = auth
super().__init__(model=McpModel(), auth=auth)
super().__init__(model=McpModel, auth=auth)
async def get_by_id_crud(self, id: int) -> Optional[McpModel]:
"""详情"""
@@ -14,7 +14,7 @@ class DemoCRUD(CRUDBase[DemoModel, DemoCreateSchema, DemoUpdateSchema]):
def __init__(self, auth: AuthSchema) -> None:
"""初始化CRUD"""
self.auth = auth
super().__init__(model=DemoModel, auth=auth)
super().__init__(model=DemoModel(), auth=auth)
async def get_by_id_crud(self, id: int) -> Optional[DemoModel]:
"""详情"""
@@ -1,10 +1,10 @@
# -*- coding: utf-8 -*-
from fastapi import APIRouter
from .gencode.controller import genController
from .gencode.controller import GenRouter
# 创建代码生成模块路由
GeneratorRouter = APIRouter(prefix="/generator")
# 包含代码生成路由
GeneratorRouter.include_router(genController)
GeneratorRouter.include_router(GenRouter)
@@ -1,107 +1,107 @@
# -*- coding:utf-8 -*-
from datetime import datetime
from fastapi import APIRouter, Depends, Query, Request, Body
from fastapi.responses import StreamingResponse
from typing import List
from fastapi import APIRouter, Depends, Query, Body
from fastapi.responses import StreamingResponse, JSONResponse
from pydantic_validation_decorator import ValidateFields
from sqlalchemy.ext.asyncio import AsyncSession
from app.common.enums import BusinessType
from app.common.response import SuccessResponse, ErrorResponse, StreamResponse
from app.core.dependencies import AuthPermission
from app.core.router_class import OperationLogRoute
from app.core.base_params import PaginationQueryParam
from app.common.request import PaginationService
from app.api.v1.module_system.auth.schema import AuthSchema
from app.api.v1.module_system.user.schema import UserOutSchema
from .param import GenTableQueryParam
from .schema import DeleteGenTableSchema, EditGenTableSchema, GenTableSchema
from .schema import GenTableDeleteSchema, GenTableUpdateSchema, GenTableOutSchema
from .service import GenTableColumnService, GenTableService
from app.utils.common_util import bytes2file_response
from app.core.logger import logger
genController = APIRouter(route_class=OperationLogRoute, prefix='/tool/gen', tags=["代码生成模块"])
GenRouter = APIRouter(route_class=OperationLogRoute, prefix='/tool/gen', tags=["代码生成模块"])
@genController.get('/list', summary="查询代码生成业务表列表", description="查询代码生成业务表列表")
async def get_gen_table_list(
@GenRouter.get('/list', summary="查询代码生成业务表列表", description="查询代码生成业务表列表")
async def get_gen_table_list_controller(
page: PaginationQueryParam = Depends(),
search: GenTableQueryParam = Depends(),
auth: AuthSchema = Depends(AuthPermission(permissions=["tool:gen:list"]))
):
# 获取分页数据
gen_page_query_result = await GenTableService.get_gen_table_list_services(auth, search, is_page=True)
) -> JSONResponse:
result_dict_list = await GenTableService.get_gen_table_list_services(auth=auth, query_object=search, is_page=False)
result_dict = await PaginationService.paginate(data_list=result_dict_list["items"], page_no=page.page_no, page_size=page.page_size)
logger.info('获取代码生成业务表列表成功')
return SuccessResponse(data=gen_page_query_result, msg="获取代码生成业务表列表成功")
return SuccessResponse(data=result_dict, msg="获取代码生成业务表列表成功")
@genController.get('/db/list', summary="查询数据库表列表", description="查询数据库表列表")
async def get_gen_db_table_list(
@GenRouter.get('/db/list', summary="查询数据库表列表", description="查询数据库表列表")
async def get_gen_db_table_list_controller(
page: PaginationQueryParam = Depends(),
search: GenTableQueryParam = Depends(),
auth: AuthSchema = Depends(AuthPermission(permissions=["tool:gen:list"]))
):
# 获取分页数据
gen_page_query_result = await GenTableService.get_gen_db_table_list_services(auth, search, is_page=True)
) -> JSONResponse:
result_dict_list = await GenTableService.get_gen_db_table_list_services(auth=auth, query_object=search, is_page=False)
result_dict = await PaginationService.paginate(data_list=result_dict_list["items"], page_no=page.page_no, page_size=page.page_size)
logger.info('获取数据库表列表成功')
return SuccessResponse(data=gen_page_query_result, msg="获取数据库表列表成功")
return SuccessResponse(data=result_dict, msg="获取数据库表列表成功")
@genController.post('/importTable', summary="导入表结构", description="导入表结构")
@GenRouter.post('/importTable', summary="导入表结构", description="导入表结构")
@ValidateFields(validate_model='edit_gen_table')
async def import_gen_table(
tables: str = Query(..., description="表名列表"),
async def import_gen_table_controller(
tables: List[str] = Body(..., description="表名列表", embed=True),
auth: AuthSchema = Depends(AuthPermission(permissions=["tool:gen:import"])),
current_user: UserOutSchema = Depends(lambda auth: auth.user)
):
table_names = tables.split(',') if tables else []
) -> JSONResponse:
table_names = tables if tables else []
add_gen_table_list = await GenTableService.get_gen_db_table_list_by_name_services(auth, table_names)
result = await GenTableService.import_gen_table_services(auth, add_gen_table_list, current_user)
logger.info('导入表结构成功')
return result
@genController.put('', summary="编辑业务表信息", description="编辑业务表信息")
@GenRouter.put('', summary="编辑业务表信息", description="编辑业务表信息")
@ValidateFields(validate_model='edit_gen_table')
async def edit_gen_table(
data: EditGenTableSchema,
async def edit_gen_table_controller(
data: GenTableUpdateSchema,
auth: AuthSchema = Depends(AuthPermission(permissions=["tool:gen:edit"])),
current_user: UserOutSchema = Depends(lambda auth: auth.user)
):
) -> JSONResponse:
data.update_by = current_user.username
data.update_time = datetime.now()
data.updated_at = datetime.now()
await GenTableService.validate_edit(data)
edit_gen_result = await GenTableService.edit_gen_table_services(auth, data)
logger.info('编辑业务表信息成功')
return SuccessResponse(data=edit_gen_result, msg="编辑业务表信息成功")
@genController.delete('/{table_ids}', summary="删除业务表信息", description="删除业务表信息")
async def delete_gen_table(
@GenRouter.delete('/{table_ids}', summary="删除业务表信息", description="删除业务表信息")
async def delete_gen_table_controller(
table_ids: str,
auth: AuthSchema = Depends(AuthPermission(permissions=["tool:gen:remove"]))
):
delete_gen_table = DeleteGenTableSchema(table_ids=table_ids)
) -> JSONResponse:
delete_gen_table = GenTableDeleteSchema(table_ids=table_ids)
result = await GenTableService.delete_gen_table_services(auth, delete_gen_table)
logger.info('删除业务表信息成功')
return result
@genController.post('/createTable', summary="创建表结构", description="创建表结构")
async def create_table(
@GenRouter.post('/createTable', summary="创建表结构", description="创建表结构")
async def create_table_controller(
sql: str = Query(..., description="SQL语句"),
auth: AuthSchema = Depends(AuthPermission(permissions=["tool:gen:create"])),
current_user: UserOutSchema = Depends(lambda auth: auth.user)
):
) -> JSONResponse:
result = await GenTableService.create_table_services(auth, sql, current_user)
logger.info('创建表结构成功')
return result
return SuccessResponse(msg="创建表结构成功", data=result)
@genController.get('/batchGenCode', summary="批量生成代码", description="批量生成代码")
async def batch_gen_code(
@GenRouter.get('/batchGenCode', summary="批量生成代码", description="批量生成代码")
async def batch_gen_code_controller(
tables: str = Query(..., description="表名列表"),
auth: AuthSchema = Depends(AuthPermission(permissions=["tool:gen:code"]))
):
) -> StreamResponse:
table_names = tables.split(',') if tables else []
batch_gen_code_result = await GenTableService.batch_gen_code_services(auth, table_names)
logger.info('批量生成代码成功')
@@ -112,11 +112,11 @@ async def batch_gen_code(
)
@genController.get('/genCode/{table_name}', summary="生成代码到指定路径", description="生成代码到指定路径")
async def gen_code_local(
@GenRouter.get('/genCode/{table_name}', summary="生成代码到指定路径", description="生成代码到指定路径")
async def gen_code_local_controller(
table_name: str,
auth: AuthSchema = Depends(AuthPermission(permissions=["tool:gen:code"]))
):
) -> JSONResponse:
from app.config.setting import settings
if not settings.allow_overwrite:
logger.error('【系统预设】不允许生成文件覆盖到本地')
@@ -126,11 +126,11 @@ async def gen_code_local(
return result
@genController.get('/{table_id}', summary="获取业务表详细信息", description="获取业务表详细信息")
async def query_detail_gen_table(
@GenRouter.get('/{table_id}', summary="获取业务表详细信息", description="获取业务表详细信息")
async def query_detail_gen_table_controller(
table_id: int,
auth: AuthSchema = Depends(AuthPermission(permissions=["tool:gen:query"]))
):
) -> JSONResponse:
gen_table = await GenTableService.get_gen_table_by_id_services(auth, table_id)
gen_tables = await GenTableService.get_gen_table_all_services(auth)
gen_columns = await GenTableColumnService.get_gen_table_column_list_by_table_id_services(auth, table_id)
@@ -139,21 +139,21 @@ async def query_detail_gen_table(
return SuccessResponse(data=gen_table_detail_result, msg="获取业务表详细信息成功")
@genController.get('/preview/{table_id}', summary="预览代码", description="预览代码")
async def preview_code(
@GenRouter.get('/preview/{table_id}', summary="预览代码", description="预览代码")
async def preview_code_controller(
table_id: int,
auth: AuthSchema = Depends(AuthPermission(permissions=["tool:gen:preview"]))
):
) -> JSONResponse:
preview_code_result = await GenTableService.preview_code_services(auth, table_id)
logger.info('预览代码成功')
return SuccessResponse(data=preview_code_result, msg="预览代码成功")
@genController.get('/synchDb/{table_name}', summary="同步数据库", description="同步数据库")
async def sync_db(
@GenRouter.get('/synchDb/{table_name}', summary="同步数据库", description="同步数据库")
async def sync_db_controller(
table_name: str,
auth: AuthSchema = Depends(AuthPermission(permissions=["tool:gen:edit"]))
):
) -> JSONResponse:
result = await GenTableService.sync_db_services(auth, table_name)
logger.info('同步数据库成功')
return result
@@ -1,6 +1,7 @@
# -*- coding:utf-8 -*-
from datetime import datetime, time
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
@@ -9,25 +10,20 @@ 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 (
GenTableBaseSchema,
GenTableColumnBaseSchema,
GenTableColumnSchema,
GenTableSchema,
)
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 GenTableDao(CRUDBase[GenTableModel, GenTableBaseSchema, GenTableBaseSchema]):
class GenTableCRUD(CRUDBase[GenTableModel, GenTableCreateSchema, GenTableUpdateSchema]):
"""
代码生成业务表模块数据库操作层
"""
def __init__(self, auth: AuthSchema) -> None:
"""初始化CRUD"""
super().__init__(model=GenTableModel(), auth=auth)
super().__init__(model=GenTableModel, auth=auth)
async def get_gen_table_by_id(self, db: AsyncSession, table_id: int) -> Optional[GenTableModel]:
"""
@@ -104,23 +100,24 @@ class GenTableDao(CRUDBase[GenTableModel, GenTableBaseSchema, GenTableBaseSchema
# 构建查询条件
conditions = []
# 访问name属性而不是table_name
if query_object.name:
conditions.append(func.lower(GenTableModel.table_name).like(f'%{str(query_object.name).lower()}%'))
# 访问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 query_object.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.create_time.between(*query_object.created_at[1]))
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()
)
@@ -154,47 +151,43 @@ class GenTableDao(CRUDBase[GenTableModel, GenTableBaseSchema, GenTableBaseSchema
:param is_page: 是否开启分页
:return: 数据库列表信息对象
"""
if settings.DATABASE_TYPE == 'postgresql':
query_sql = """
SELECT table_name as table_name,
table_comment as table_comment,
create_time as create_time,
update_time as update_time
from
list_table
where
table_name not like 'apscheduler_%'
and table_name not like 'gen_%'
and table_name not in (select table_name from gen_table)
"""
else:
query_sql = """
SELECT table_name as table_name,
table_comment as table_comment,
create_time as create_time,
update_time as update_time
from
information_schema.tables
where
table_schema = (select database())
and table_name not like 'apscheduler\_%'
and table_name not like 'gen\_%'
and table_name not in (select table_name from gen_table)
"""
if query_object.name:
query_sql = """
SELECT table_name as table_name,
table_comment as table_comment,
create_time as create_time,
update_time as update_time
from
information_schema.tables
where
table_schema = (select database())
and table_name not like 'apscheduler\_%'
and table_name not like 'gen\_%'
and table_name not in (select table_name from gen_table)
"""
# 根据param.py中的定义,table_name是元组形式("like", value)
if getattr(query_object, 'table_name', None) and query_object.table_name[1]:
query_sql += """and lower(table_name) like lower(concat('%', :table_name, '%'))"""
if query_object.table_comment:
# 处理table_comment字段(如果有)
if getattr(query_object, 'table_comment', None):
query_sql += """and lower(table_comment) like lower(concat('%', :table_comment, '%'))"""
# 修复查询参数处理
query_params = {
k: v for k, v in query_object.model_dump(exclude_none=True, exclude={'page_no', 'page_size'}).items()
}
# 构建查询参数
query_params = {}
# 添加table_name查询参数
if getattr(query_object, 'table_name', None) and query_object.table_name[1]:
query_params['table_name'] = query_object.table_name[1]
# 添加table_comment查询参数
if getattr(query_object, 'table_comment', None):
query_params['table_comment'] = query_object.table_comment
query_sql += """order by create_time desc"""
query = text(query_sql).bindparams(**query_params)
# 执行查询
result = await db.execute(select(query))
result = await db.execute(query)
all_data = list(result.fetchall())
# 使用PaginationService.paginate进行分页
@@ -222,49 +215,32 @@ class GenTableDao(CRUDBase[GenTableModel, GenTableBaseSchema, GenTableBaseSchema
:param table_names: 业务表名称组
:return: 数据库列表信息对象
"""
if settings.DATABASE_TYPE == 'postgresql':
query_sql = """
select
table_name as table_name,
table_comment as table_comment,
create_time as create_time,
update_time as update_time
from
list_table
where
table_name not like 'qrtz_%'
and table_name not like 'gen_%'
and table_name = any(:table_names)
"""
else:
query_sql = """
select
table_name as table_name,
table_comment as table_comment,
create_time as create_time,
update_time as update_time
from
information_schema.tables
where
table_name not like 'qrtz\_%'
and table_name not like 'gen\_%'
and table_schema = (select database())
and table_name in :table_names
"""
query_sql = """
select
table_name as table_name,
table_comment as table_comment,
create_time as create_time,
update_time as update_time
from
information_schema.tables
where
table_schema = (select database())
and table_name in :table_names
"""
query = text(query_sql).bindparams(table_names=tuple(table_names))
gen_db_table_list = (await db.execute(query)).fetchall()
return gen_db_table_list
class GenTableColumnDao(CRUDBase[GenTableColumnModel, GenTableColumnBaseSchema, GenTableColumnBaseSchema]):
class GenTableColumnCRUD(CRUDBase[GenTableColumnModel, GenTableColumnCreateSchema, GenTableColumnUpdateSchema]):
"""
代码生成业务表字段模块数据库操作层
"""
def __init__(self, auth: AuthSchema) -> None:
"""初始化CRUD"""
super().__init__(model=GenTableColumnModel(), auth=auth)
super().__init__(model=GenTableColumnModel, auth=auth)
async def get_gen_table_column_list_by_table_id(self, db: AsyncSession, table_id: int) -> Sequence[GenTableColumnModel]:
"""
@@ -286,7 +262,7 @@ class GenTableColumnDao(CRUDBase[GenTableColumnModel, GenTableColumnBaseSchema,
return gen_table_column_list
async def get_gen_db_table_columns_by_name(self, db: AsyncSession, table_name: str):
async def get_gen_db_table_columns_by_name(self, db: AsyncSession, table_name: str) -> Sequence[Row[Any]]:
"""
根据业务表名称获取业务表字段列表信息
@@ -294,42 +270,32 @@ class GenTableColumnDao(CRUDBase[GenTableColumnModel, GenTableColumnBaseSchema,
:param table_name: 业务表名称
:return: 业务表字段列表信息对象
"""
if settings.DATABASE_TYPE == 'postgresql':
query_sql = """
select
column_name, is_required, is_pk, sort, column_comment, is_increment, column_type
from
list_column
where
table_name = :table_name
"""
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_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()
@@ -1,6 +1,5 @@
# -*- coding: utf-8 -*-
from datetime import datetime
from typing import Optional, List
from sqlalchemy import String, Integer, ForeignKey
from sqlalchemy.orm import Mapped, mapped_column, relationship
@@ -5,25 +5,21 @@ from typing import Optional
from fastapi import Query
from app.core.validator import DateTimeStr
from app.core.base_params import PaginationQueryParam
from .schema import GenTableBaseSchema, GenTableColumnBaseSchema
class GenTableQueryParam(PaginationQueryParam, GenTableBaseSchema):
class GenTableQueryParam:
"""数据库表查询参数"""
def __init__(
self,
name: Optional[str] = Query(None, description="名称"),
table_name: Optional[str] = Query(None, description="名称"),
status: Optional[bool] = Query(None, description="是否启用"),
creator: Optional[int] = Query(None, description="创建人"),
start_time: Optional[DateTimeStr] = Query(None, description="开始时间", example="2023-01-01 00:00:00"),
end_time: Optional[DateTimeStr] = Query(None, description="结束时间", example="2023-12-31 23:59:59"),
) -> None:
super().__init__()
# 模糊查询字段
self.name = ("like", name)
) -> None:
# 存储查询条件,不直接赋值给父类属性
self.table_name = ("like", table_name)
# 精确查询字段
self.creator_id = creator
@@ -36,21 +32,19 @@ class GenTableQueryParam(PaginationQueryParam, GenTableBaseSchema):
self.created_at = ("between", (start_datetime, end_datetime))
class GenTableColumnQueryParam(PaginationQueryParam, GenTableColumnBaseSchema):
class GenTableColumnQueryParam:
"""数据库表字段查询参数"""
def __init__(
self,
name: Optional[str] = Query(None, description="名称"),
column_name: Optional[str] = Query(None, description="名称"),
status: Optional[bool] = Query(None, description="是否启用"),
creator: Optional[int] = Query(None, description="创建人"),
start_time: Optional[DateTimeStr] = Query(None, description="开始时间", example="2023-01-01 00:00:00"),
end_time: Optional[DateTimeStr] = Query(None, description="结束时间", example="2023-12-31 23:59:59"),
) -> None:
super().__init__()
# 模糊查询字段
self.name = ("like", name)
) -> None:
# 存储查询条件,不直接赋值给父类属性
self.column_name = ("like", column_name)
# 精确查询字段
self.creator_id = creator
@@ -1,23 +1,22 @@
# -*- coding:utf-8 -*-
from datetime import datetime
from typing import List, Literal, Optional, Union
from typing import List, Literal, Optional
from pydantic import BaseModel, ConfigDict, Field, model_validator
from pydantic.alias_generators import to_camel
from pydantic_validation_decorator import NotBlank
from app.utils.string_util import StringUtil
from app.common.constant import GenConstant
from app.core.base_schema import BaseSchema
class GenTableBaseSchema(BaseModel):
class GenTableCreateSchema(BaseModel):
"""
代码生成业务表对应pydantic模型
"""
model_config = ConfigDict(alias_generator=to_camel, from_attributes=True)
model_config = ConfigDict(from_attributes=True)
table_id: Optional[int] = Field(default=None, description='编号')
table_name: str = Field(default=..., description='表名称')
table_comment: str = Field(default=..., description='表描述')
sub_table_name: Optional[str] = Field(default=None, description='关联子表的表名')
@@ -33,12 +32,6 @@ class GenTableBaseSchema(BaseModel):
gen_type: Optional[Literal['0', '1']] = Field(default=None, description='生成代码方式(0zip压缩包 1自定义路径)')
gen_path: Optional[str] = Field(default=None, description='生成路径(不填默认项目路径)')
options: Optional[str] = Field(default=None, description='其它生成选项')
create_by: Optional[str] = Field(default=None, description='创建者')
create_time: Optional[datetime] = Field(default=None, description='创建时间')
update_by: Optional[str] = Field(default=None, description='更新者')
update_time: Optional[datetime] = Field(default=None, description='更新时间')
remark: Optional[str] = Field(default=None, description='备注')
@NotBlank(field_name='table_name', message='表名称不能为空')
def get_table_name(self):
@@ -83,14 +76,14 @@ class GenTableBaseSchema(BaseModel):
self.get_function_author()
class GenTableSchema(GenTableBaseSchema):
class GenTableUpdateSchema(GenTableCreateSchema):
"""
代码生成业务表模型
"""
pk_column: Optional['GenTableColumnSchema'] = Field(default=None, description='主键信息')
sub_table: Optional['GenTableSchema'] = Field(default=None, description='子表信息')
columns: List['GenTableColumnSchema'] = Field(default=..., description='表列信息')
pk_column: Optional['GenTableColumnUpdateSchema'] = Field(default=None, description='主键信息')
sub_table: Optional['GenTableUpdateSchema'] = Field(default=None, description='子表信息')
columns: List['GenTableColumnUpdateSchema'] = Field(default=..., description='表列信息')
tree_code: Optional[str] = Field(default=None, description='树编码字段')
tree_parent_code: Optional[str] = Field(default=None, description='树父编码字段')
tree_name: Optional[str] = Field(default=None, description='树名称字段')
@@ -101,35 +94,19 @@ class GenTableSchema(GenTableBaseSchema):
crud: Optional[bool] = Field(default=None, description='是否为单表')
@model_validator(mode='after')
def check_some_is(self) -> 'GenTableSchema':
def check_some_is(self) -> 'GenTableUpdateSchema':
self.sub = True if self.tpl_category and self.tpl_category == GenConstant.TPL_SUB else False
self.tree = True if self.tpl_category and self.tpl_category == GenConstant.TPL_TREE else False
self.crud = True if self.tpl_category and self.tpl_category == GenConstant.TPL_CRUD else False
return self
class EditGenTableSchema(GenTableSchema):
"""
修改代码生成业务表模型
"""
params: Optional['GenTableParamsSchema'] = Field(default=None, description='业务表参数')
class GenTableOutSchema(GenTableUpdateSchema, BaseSchema):
"""响应模型"""
model_config = ConfigDict(from_attributes=True)
class GenTableParamsSchema(BaseModel):
"""
代码生成业务表参数模型
"""
model_config = ConfigDict(alias_generator=to_camel)
tree_code: Optional[str] = Field(default=None, description='树编码字段')
tree_parent_code: Optional[str] = Field(default=None, description='树父编码字段')
tree_name: Optional[str] = Field(default=None, description='树名称字段')
parent_menu_id: Optional[int] = Field(default=None, description='上级菜单ID字段')
class DeleteGenTableSchema(BaseModel):
class GenTableDeleteSchema(BaseModel):
"""
删除代码生成业务表模型
"""
@@ -139,15 +116,15 @@ class DeleteGenTableSchema(BaseModel):
table_ids: str = Field(description='需要删除的代码生成业务表ID')
class GenTableColumnBaseSchema(BaseModel):
class GenTableColumnCreateSchema(BaseModel):
"""
代码生成业务表字段对应pydantic模型
"""
model_config = ConfigDict(alias_generator=to_camel, from_attributes=True)
column_id: Optional[int] = Field(default=None, description='编号')
table_id: Optional[int] = Field(default=None, description='归属表编号')
column_name: str = Field(default=..., description='列名称')
column_comment: Optional[str] = Field(default=None, description='列描述')
column_type: str = Field(default=..., description='列类型')
@@ -166,11 +143,6 @@ class GenTableColumnBaseSchema(BaseModel):
dict_type: str = Field(default=..., description='字典类型')
sort: Optional[int] = Field(default=None, description='排序')
create_by: Optional[str] = Field(default=None, description='创建者')
create_time: Optional[datetime] = Field(default=None, description='创建时间')
update_by: Optional[str] = Field(default=None, description='更新者')
update_time: Optional[datetime] = Field(default=None, description='更新时间')
@NotBlank(field_name='python_field', message='Python属性不能为空')
def get_python_field(self):
return self.python_field
@@ -179,7 +151,7 @@ class GenTableColumnBaseSchema(BaseModel):
self.get_python_field()
class GenTableColumnSchema(GenTableColumnBaseSchema):
class GenTableColumnUpdateSchema(GenTableColumnCreateSchema):
"""
代码生成业务表字段模型
"""
@@ -197,7 +169,7 @@ class GenTableColumnSchema(GenTableColumnBaseSchema):
usable_column: Optional[bool] = Field(default=None, description='是否为基类字段白名单')
@model_validator(mode='after')
def check_some_is(self) -> 'GenTableColumnSchema':
def check_some_is(self) -> 'GenTableColumnUpdateSchema':
self.cap_python_field = self.python_field[0].upper() + self.python_field[1:] if self.python_field else None
self.pk = True if self.is_pk and self.is_pk == '1' else False
self.increment = True if self.is_increment and self.is_increment == '1' else False
@@ -218,11 +190,16 @@ class GenTableColumnSchema(GenTableColumnBaseSchema):
return self
class DeleteGenTableColumnSchema(BaseModel):
class GenTableColumnOutSchema(GenTableColumnUpdateSchema, BaseSchema):
"""响应模型"""
model_config = ConfigDict(from_attributes=True)
class GenTableColumnDeleteSchema(BaseModel):
"""
删除代码生成业务表字段模型
"""
model_config = ConfigDict(alias_generator=to_camel)
model_config = ConfigDict(from_attributes=True)
column_ids: str = Field(description='需要删除的代码生成业务表字段ID')
@@ -17,9 +17,9 @@ from app.common.constant import GenConstant
from app.common.response import SuccessResponse
from app.api.v1.module_system.user.schema import UserOutSchema
from app.api.v1.module_system.auth.schema import AuthSchema
from .schema import DeleteGenTableSchema, EditGenTableSchema, GenTableColumnSchema, GenTableSchema
from .schema import GenTableCreateSchema, GenTableUpdateSchema, GenTableOutSchema, GenTableDeleteSchema, GenTableColumnCreateSchema, GenTableColumnUpdateSchema, GenTableColumnOutSchema, GenTableColumnDeleteSchema
from .param import GenTableQueryParam
from .crud import GenTableColumnDao, GenTableDao
from .crud import GenTableColumnCRUD, GenTableCRUD
from .model import GenTableModel, GenTableColumnModel
@@ -33,7 +33,7 @@ class GenTableService:
"""
@classmethod
async def get_gen_table_list_services(
async def get_gen_table_list_service(
cls, auth: AuthSchema, query_object: GenTableQueryParam, is_page: bool = False
):
"""
@@ -44,17 +44,13 @@ class GenTableService:
:param is_page: 是否开启分页
:return: 代码生成业务列表信息对象
"""
# 确保db是AsyncSession类型
if not isinstance(auth.db, AsyncSession):
raise CustomException(msg='数据库会话类型不正确')
gen_table_dao = GenTableDao(auth=auth)
gen_table_dao = GenTableCRUD(auth=auth)
gen_table_list_result = await gen_table_dao.get_gen_table_list(auth.db, query_object, is_page)
return gen_table_list_result
@classmethod
async def get_gen_db_table_list_services(
async def get_gen_db_table_list_service(
cls, auth: AuthSchema, query_object: GenTableQueryParam, is_page: bool = False
):
"""
@@ -65,17 +61,13 @@ class GenTableService:
:param is_page: 是否开启分页
:return: 数据库列表信息对象
"""
# 确保db是AsyncSession类型
if not isinstance(auth.db, AsyncSession):
raise CustomException(msg='数据库会话类型不正确')
gen_table_dao = GenTableDao(auth=auth)
gen_table_dao = GenTableCRUD(auth=auth)
gen_db_table_list_result = await gen_table_dao.get_gen_db_table_list(auth.db, query_object, is_page)
return gen_db_table_list_result
@classmethod
async def get_gen_db_table_list_by_name_services(cls, auth: AuthSchema, table_names: List[str]) -> list[GenTableSchema]:
async def get_gen_db_table_list_by_name_service(cls, auth: AuthSchema, table_names: List[str]) -> list[GenTableOutSchema]:
"""
根据表名称组获取数据库列表信息service
@@ -83,18 +75,14 @@ class GenTableService:
:param table_names: 表名称组
:return: 数据库列表信息对象
"""
# 确保db是AsyncSession类型
if not isinstance(auth.db, AsyncSession):
raise CustomException(msg='数据库会话类型不正确')
gen_table_dao = GenTableDao(auth=auth)
gen_table_dao = GenTableCRUD(auth=auth)
gen_db_table_list_result = await gen_table_dao.get_gen_db_table_list_by_names(auth.db, table_names)
return [GenTableSchema(**gen_table) for gen_table in CamelCaseUtil.transform_result(gen_db_table_list_result)]
return [GenTableOutSchema(**gen_table) for gen_table in CamelCaseUtil.transform_result(gen_db_table_list_result)]
@classmethod
async def import_gen_table_services(
cls, auth: AuthSchema, gen_table_list: List[GenTableSchema], current_user: UserOutSchema
async def import_gen_table_service(
cls, auth: AuthSchema, gen_table_list: List[GenTableOutSchema], current_user: UserOutSchema
):
"""
导入表结构service
@@ -105,12 +93,8 @@ class GenTableService:
:return: 导入结果
"""
try:
# 确保db是AsyncSession类型
if not isinstance(auth.db, AsyncSession):
raise CustomException(msg='数据库会话类型不正确')
gen_table_dao = GenTableDao(auth=auth)
gen_table_column_dao = GenTableColumnDao(auth=auth)
gen_table_dao = GenTableCRUD(auth=auth)
gen_table_column_dao = GenTableColumnCRUD(auth=auth)
for table in gen_table_list:
table_name = table.table_name
@@ -120,24 +104,22 @@ class GenTableService:
table.table_id = add_gen_table.id
gen_table_columns = await gen_table_column_dao.get_gen_db_table_columns_by_name(auth.db, table_name or "")
for column in [
GenTableColumnSchema(**gen_table_column)
GenTableColumnOutSchema(**gen_table_column)
for gen_table_column in CamelCaseUtil.transform_result(gen_table_columns)
]:
GenUtils.init_column_field(column, table)
await gen_table_column_dao.create(data=column.model_dump())
if isinstance(auth.db, AsyncSession):
await auth.db.commit()
await auth.db.commit()
return SuccessResponse(msg='导入成功')
except Exception as e:
if isinstance(auth.db, AsyncSession):
try:
await auth.db.rollback()
except:
pass # 忽略回滚错误
try:
await auth.db.rollback()
except:
pass # 忽略回滚错误
raise CustomException(msg=f'导入失败, {str(e)}')
@classmethod
async def edit_gen_table_services(cls, auth: AuthSchema, page_object: EditGenTableSchema) -> Dict[str, Any]:
async def edit_gen_table_service(cls, auth: AuthSchema, page_object: GenTableUpdateSchema) -> Dict[str, Any]:
"""
编辑业务表信息service
@@ -145,30 +127,26 @@ class GenTableService:
:param page_object: 编辑业务表对象
:return: 编辑业务表校验结果
"""
# 确保db是AsyncSession类型
if not isinstance(auth.db, AsyncSession):
raise CustomException(msg='数据库会话类型不正确')
gen_table_dao = GenTableDao(auth=auth)
gen_table_column_dao = GenTableColumnDao(auth=auth)
gen_table_dao = GenTableCRUD(auth=auth)
gen_table_column_dao = GenTableColumnCRUD(auth=auth)
# 检查必要字段是否存在
if page_object.table_id is None:
if getattr(page_object, 'table_id', None) is None:
raise CustomException(msg='业务表ID不能为空')
edit_gen_table = page_object.model_dump(exclude_unset=True, by_alias=True)
gen_table_info = await cls.get_gen_table_by_id_services(auth, page_object.table_id)
gen_table_info = await cls.get_gen_table_by_id_service(auth, page_object.table_id)
if gen_table_info.table_id:
try:
# 处理params字段,确保不为None
params = edit_gen_table.get('params')
if params is not None:
edit_gen_table['options'] = json.dumps(params)
else:
# 确保options字段存在且为有效JSON
if 'options' not in edit_gen_table or edit_gen_table['options'] is None:
edit_gen_table['options'] = '{}' # 默认空对象
# 移除params字段,因为options字段已经包含了序列化的params
edit_gen_table.pop('params', None)
else:
# 验证options是否为有效的JSON
try:
json.loads(edit_gen_table['options'])
except json.JSONDecodeError:
edit_gen_table['options'] = '{}'
await gen_table_dao.update(id=page_object.table_id, data=edit_gen_table)
if page_object.columns:
@@ -180,21 +158,19 @@ class GenTableService:
id=gen_table_column.column_id,
data=gen_table_column.model_dump(by_alias=True)
)
if isinstance(auth.db, AsyncSession):
await auth.db.commit()
await auth.db.commit()
return {"is_success": True, "message": "更新成功"}
except Exception as e:
if isinstance(auth.db, AsyncSession):
try:
await auth.db.rollback()
except:
pass # 忽略回滚错误
try:
await auth.db.rollback()
except:
pass # 忽略回滚错误
raise CustomException(msg=f'更新失败: {str(e)}')
else:
raise CustomException(msg='业务表不存在')
@classmethod
async def delete_gen_table_services(cls, auth: AuthSchema, page_object: DeleteGenTableSchema) -> SuccessResponse:
async def delete_gen_table_service(cls, auth: AuthSchema, page_object: GenTableDeleteSchema) -> SuccessResponse:
"""
删除业务表信息service
@@ -202,12 +178,8 @@ class GenTableService:
:param page_object: 删除业务表对象
:return: 删除业务表校验结果
"""
# 确保db是AsyncSession类型
if not isinstance(auth.db, AsyncSession):
raise CustomException(msg='数据库会话类型不正确')
gen_table_dao = GenTableDao(auth=auth)
gen_table_column_dao = GenTableColumnDao(auth=auth)
gen_table_dao = GenTableCRUD(auth=auth)
gen_table_column_dao = GenTableColumnCRUD(auth=auth)
if page_object.table_ids:
table_id_list = page_object.table_ids.split(',')
@@ -220,21 +192,19 @@ class GenTableService:
if columns:
column_ids = [column.id for column in columns]
await gen_table_column_dao.delete(ids=column_ids)
if isinstance(auth.db, AsyncSession):
await auth.db.commit()
await auth.db.commit()
return SuccessResponse(msg='删除成功')
except Exception as e:
if isinstance(auth.db, AsyncSession):
try:
await auth.db.rollback()
except:
pass # 忽略回滚错误
try:
await auth.db.rollback()
except:
pass # 忽略回滚错误
raise CustomException(msg=f'删除失败: {str(e)}')
else:
raise CustomException(msg='传入业务表id为空')
@classmethod
async def get_gen_table_by_id_services(cls, auth: AuthSchema, table_id: int) -> GenTableSchema:
async def get_gen_table_by_id_service(cls, auth: AuthSchema, table_id: int) -> GenTableOutSchema:
"""
获取需要生成的业务表详细信息service
@@ -242,38 +212,32 @@ class GenTableService:
:param table_id: 需要生成的业务表id
:return: 需要生成的业务表id对应的信息
"""
# 确保db是AsyncSession类型
if not isinstance(auth.db, AsyncSession):
raise CustomException(msg='数据库会话类型不正确')
gen_table_dao = GenTableDao(auth=auth)
gen_table_dao = GenTableCRUD(auth=auth)
gen_table = await gen_table_dao.get_gen_table_by_id(auth.db, table_id)
if gen_table:
result = await cls.set_table_from_options(GenTableSchema(**CamelCaseUtil.transform_result(gen_table)))
result = await cls.set_table_from_options(GenTableOutSchema(**CamelCaseUtil.transform_result(gen_table)))
return result
else:
raise CustomException(msg='业务表不存在')
@classmethod
async def get_gen_table_all_services(cls, auth: AuthSchema) -> list[GenTableSchema]:
async def get_gen_table_all_service(cls, auth: AuthSchema) -> list[GenTableOutSchema]:
"""
获取所有业务表信息service
:param auth: 认证信息
:return: 所有业务表信息
:return: 所有业务表信息列表
"""
# 确保db是AsyncSession类型
if not isinstance(auth.db, AsyncSession):
raise CustomException(msg='数据库会话类型不正确')
gen_table_dao = GenTableDao(auth=auth)
gen_table_all = await gen_table_dao.get_gen_table_all(auth.db)
result = [GenTableSchema(**gen_table) for gen_table in CamelCaseUtil.transform_result(gen_table_all)]
gen_table_dao = GenTableCRUD(auth=auth)
gen_tables = await gen_table_dao.get_gen_table_all(auth.db)
result = []
for table in gen_tables:
table_info = await cls.set_table_from_options(GenTableOutSchema(**CamelCaseUtil.transform_result(table)))
result.append(table_info)
return result
@classmethod
async def create_table_services(cls, auth: AuthSchema, sql: str, current_user: UserOutSchema) -> SuccessResponse:
async def create_table_service(cls, auth: AuthSchema, sql: str, current_user: UserOutSchema) -> SuccessResponse:
"""
创建表结构service
@@ -282,28 +246,22 @@ class GenTableService:
:param current_user: 当前用户信息对象
:return: 创建表结构结果
"""
# 确保db是AsyncSession类型
if not isinstance(auth.db, AsyncSession):
raise CustomException(msg='数据库会话类型不正确')
gen_table_dao = GenTableDao(auth=auth)
gen_table_dao = GenTableCRUD(auth=auth)
try:
# 执行SQL语句创建表
await gen_table_dao.create_table_by_sql_dao(auth.db, [sql])
if isinstance(auth.db, AsyncSession):
await auth.db.commit()
await auth.db.commit()
return SuccessResponse(msg='创建表结构成功')
except Exception as e:
if isinstance(auth.db, AsyncSession):
try:
await auth.db.rollback()
except:
pass # 忽略回滚错误
try:
await auth.db.rollback()
except:
pass # 忽略回滚错误
raise CustomException(msg=f'创建表结构失败: {str(e)}')
@classmethod
async def preview_code_services(cls, auth: AuthSchema, table_id: int) -> dict[Any, Any]:
async def preview_code_service(cls, auth: AuthSchema, table_id: int) -> dict[Any, Any]:
"""
预览代码service
@@ -311,7 +269,7 @@ class GenTableService:
:param table_id: 业务表id
:return: 预览数据列表
"""
gen_table = await cls.get_gen_table_by_id_services(auth, table_id)
gen_table = await cls.get_gen_table_by_id_service(auth, table_id)
await cls.set_sub_table(auth, gen_table)
await cls._set_pk_column(gen_table)
env = TemplateInitializer.init_jinja2()
@@ -327,7 +285,7 @@ class GenTableService:
return preview_code_result
@classmethod
async def generate_code_services(cls, auth: AuthSchema, table_name: str) -> SuccessResponse:
async def generate_code_service(cls, auth: AuthSchema, table_name: str) -> SuccessResponse:
"""
生成代码至指定路径service
@@ -351,7 +309,7 @@ class GenTableService:
return SuccessResponse(msg='生成代码成功')
@classmethod
async def batch_gen_code_services(cls, auth: AuthSchema, table_names: List[str]) -> bytes:
async def batch_gen_code_service(cls, auth: AuthSchema, table_names: List[str]) -> bytes:
"""
批量生成代码service
@@ -381,14 +339,11 @@ class GenTableService:
:param table_name: 业务表名称
:return: 生成代码渲染模板相关信息
"""
# 确保db是AsyncSession类型
if not isinstance(auth.db, AsyncSession):
raise CustomException(msg='数据库会话类型不正确')
gen_table_dao = GenTableDao(auth=auth)
gen_table_dao = GenTableCRUD(auth=auth)
gen_table = await gen_table_dao.get_gen_table_by_name(auth.db, table_name)
if gen_table:
gen_table_schema = GenTableSchema(**CamelCaseUtil.transform_result(gen_table))
gen_table_schema = GenTableOutSchema(**CamelCaseUtil.transform_result(gen_table))
await cls.set_sub_table(auth, gen_table_schema)
await cls._set_pk_column(gen_table_schema)
context = TemplateUtils.prepare_context(gen_table_schema)
@@ -403,7 +358,7 @@ class GenTableService:
raise CustomException(msg=f'业务表 {table_name} 不存在')
@classmethod
def __get_gen_path(cls, gen_table: GenTableSchema, template: str) -> Optional[str]:
def __get_gen_path(cls, gen_table: GenTableOutSchema, template: str) -> Optional[str]:
"""
根据GenTableModel对象和模板名称生成路径
@@ -423,7 +378,7 @@ class GenTableService:
return None
@classmethod
async def sync_db_services(cls, auth: AuthSchema, table_name: str) -> SuccessResponse:
async def sync_db_service(cls, auth: AuthSchema, table_name: str) -> SuccessResponse:
"""
同步数据库service
@@ -431,21 +386,17 @@ class GenTableService:
:param table_name: 业务表名称
:return: 同步数据库结果
"""
# 确保db是AsyncSession类型
if not isinstance(auth.db, AsyncSession):
raise CustomException(msg='数据库会话类型不正确')
gen_table_dao = GenTableDao(auth=auth)
gen_table_column_dao = GenTableColumnDao(auth=auth)
gen_table_dao = GenTableCRUD(auth=auth)
gen_table_column_dao = GenTableColumnCRUD(auth=auth)
gen_table = await gen_table_dao.get_gen_table_by_name(auth.db, table_name)
if gen_table:
table = GenTableSchema(**CamelCaseUtil.transform_result(gen_table))
table = GenTableOutSchema(**CamelCaseUtil.transform_result(gen_table))
table_columns = table.columns or [] # 确保不为None
table_column_map = {column.column_name: column for column in table_columns}
query_db_table_columns = await gen_table_column_dao.get_gen_db_table_columns_by_name(auth.db, table_name)
db_table_columns = [
GenTableColumnSchema(**column) for column in CamelCaseUtil.transform_result(query_db_table_columns)
GenTableColumnOutSchema(**column) for column in CamelCaseUtil.transform_result(query_db_table_columns)
]
if not db_table_columns:
raise CustomException('同步数据失败,原表结构不存在')
@@ -476,21 +427,19 @@ class GenTableService:
for column in del_columns:
if column.column_id is not None:
await gen_table_column_dao.delete(ids=[column.column_id])
if isinstance(auth.db, AsyncSession):
await auth.db.commit()
await auth.db.commit()
return SuccessResponse(msg='同步成功')
except Exception as e:
if isinstance(auth.db, AsyncSession):
try:
await auth.db.rollback()
except:
pass # 忽略回滚错误
try:
await auth.db.rollback()
except:
pass # 忽略回滚错误
raise CustomException(msg=f'同步失败: {str(e)}')
else:
raise CustomException('业务表不存在')
@classmethod
async def set_sub_table(cls, auth: AuthSchema, gen_table: GenTableSchema) -> None:
async def set_sub_table(cls, auth: AuthSchema, gen_table: GenTableOutSchema) -> None:
"""
设置主子表信息
@@ -498,18 +447,15 @@ class GenTableService:
:param gen_table: 业务表信息
:return:
"""
# 确保db是AsyncSession类型
if not isinstance(auth.db, AsyncSession):
raise CustomException(msg='数据库会话类型不正确')
if gen_table.sub_table_name:
gen_table_dao = GenTableDao(auth=auth)
gen_table_dao = GenTableCRUD(auth=auth)
sub_table = await gen_table_dao.get_gen_table_by_name(auth.db, gen_table.sub_table_name)
if sub_table:
gen_table.sub_table = GenTableSchema(**CamelCaseUtil.transform_result(sub_table))
gen_table.sub_table = GenTableOutSchema(**CamelCaseUtil.transform_result(sub_table))
@classmethod
async def _set_pk_column(cls, gen_table: GenTableSchema) -> None:
async def _set_pk_column(cls, gen_table: GenTableOutSchema) -> None:
"""
设置主键列信息
@@ -533,7 +479,7 @@ class GenTableService:
gen_table.sub_table.pk_column = gen_table.sub_table.columns[0]
@classmethod
async def set_table_from_options(cls, gen_table: GenTableSchema) -> GenTableSchema:
async def set_table_from_options(cls, gen_table: GenTableOutSchema) -> GenTableOutSchema:
"""
设置代码生成其他选项值
@@ -551,18 +497,18 @@ class GenTableService:
return gen_table
@classmethod
async def validate_edit(cls, edit_gen_table: EditGenTableSchema):
async def validate_edit(cls, edit_gen_table: GenTableUpdateSchema):
"""
编辑保存参数校验
:param edit_gen_table: 编辑业务表对象
"""
if edit_gen_table.tpl_category == GenConstant.TPL_TREE:
# 检查params是否为None
if edit_gen_table.params is None:
# 从options字段获取参数,而不是params
if not edit_gen_table.options:
raise CustomException(msg='树表参数不能为空')
params_obj = edit_gen_table.params.model_dump(by_alias=True)
params_obj = json.loads(edit_gen_table.options)
if GenConstant.TREE_CODE not in params_obj:
raise CustomException(msg='树编码字段不能为空')
@@ -583,7 +529,7 @@ class GenTableColumnService:
"""
@classmethod
async def get_gen_table_column_list_by_table_id_services(cls, auth: AuthSchema, table_id: int):
async def get_gen_table_column_list_by_table_id_service(cls, auth: AuthSchema, table_id: int) -> List[GenTableColumnOutSchema]:
"""
获取业务表字段列表信息service
@@ -591,14 +537,10 @@ class GenTableColumnService:
:param table_id: 业务表格id
:return: 业务表字段列表信息对象
"""
# 确保db是AsyncSession类型
if not isinstance(auth.db, AsyncSession):
raise CustomException(msg='数据库会话类型不正确')
gen_table_column_dao = GenTableColumnDao(auth=auth)
gen_table_column_dao = GenTableColumnCRUD(auth=auth)
gen_table_column_list_result = await gen_table_column_dao.get_gen_table_column_list_by_table_id(auth.db, table_id)
return [
GenTableColumnSchema(**gen_table_column)
GenTableColumnOutSchema(**gen_table_column)
for gen_table_column in CamelCaseUtil.transform_result(gen_table_column_list_result)
]
@@ -1,94 +0,0 @@
# -*- coding:utf-8 -*-
from fastapi import APIRouter, Depends, Form
from sqlalchemy.ext.asyncio import AsyncSession
from starlette.requests import Request
from typing import List
from app.common.enums import BusinessType
from app.core.dependencies import get_db
from app.common.response import SuccessResponse
from app.core.dependencies import AuthPermission
from app.api.v1.module_system.auth.schema import AuthSchema
from app.api.v1.module_system.user.schema import UserOutSchema
from app.utils.common_util import bytes2file_response
from {{ packageName }}.entity.vo.{{ tableName }}_vo import {{ tableName|snake_to_pascal_case }}PageModel, {{ tableName|snake_to_pascal_case }}Model
from {{ packageName }}.service.{{ tableName }}_service import {{ tableName|snake_to_pascal_case }}Service
{{ tableName|snake_to_camel }}Controller = APIRouter(prefix='/{{ moduleName }}/{{ businessName }}', tags=["{{ functionName }}模块"])
@{{ tableName|snake_to_camel }}Controller.get('/list', summary="查询{{ functionName }}列表", description="查询{{ functionName }}列表")
async def get_{{ tableName }}_list(
auth: AuthSchema = Depends(AuthPermission(permissions=["{{ permissionPrefix }}:list"])),
page_query: {{ tableName|snake_to_pascal_case }}PageModel = Depends({{ tableName|snake_to_pascal_case }}PageModel.as_query)
):
{{ tableName }}_result = await {{ tableName|snake_to_pascal_case }}Service.get_{{ tableName }}_list_services(auth, page_query)
return SuccessResponse(data={{ tableName }}_result)
@{{ tableName|snake_to_camel }}Controller.get('/{id}', summary="获取{{ functionName }}详细信息", description="获取{{ functionName }}详细信息")
async def get_{{ tableName }}_by_id(
id: int,
auth: AuthSchema = Depends(AuthPermission(permissions=["{{ permissionPrefix }}:query"]))
):
{{ tableName }} = await {{ tableName|snake_to_pascal_case }}Service.get_{{ tableName }}_by_id_services(auth, id)
return SuccessResponse(data={{ tableName }})
@{{ tableName|snake_to_camel }}Controller.post('', summary="新增{{ functionName }}", description="新增{{ functionName }}")
async def add_{{ tableName }} (
add_model: {{ tableName|snake_to_pascal_case }}Model,
auth: AuthSchema = Depends(AuthPermission(permissions=["{{ permissionPrefix }}:add"])),
current_user: UserOutSchema = Depends(lambda auth: auth.user)
):
add_model.create_by = current_user.username
add_result = await {{ tableName|snake_to_pascal_case }}Service.add_{{ tableName }}_services(auth, add_model)
return SuccessResponse(msg="新增成功")
@{{ tableName|snake_to_camel }}Controller.put('', summary="修改{{ functionName }}", description="修改{{ functionName }}")
async def update_{{ tableName }}(
edit_model: {{ tableName|snake_to_pascal_case }}Model,
auth: AuthSchema = Depends(AuthPermission(permissions=["{{ permissionPrefix }}:edit"])),
current_user: UserOutSchema = Depends(lambda auth: auth.user)
):
edit_model.update_by = current_user.username
update_result = await {{ tableName|snake_to_pascal_case }}Service.update_{{ tableName }}_services(auth, edit_model)
return SuccessResponse(msg="修改成功")
@{{ tableName|snake_to_camel }}Controller.delete('/{ids}', summary="删除{{ functionName }}", description="删除{{ functionName }}")
async def del_{{ tableName }}(
ids: str,
auth: AuthSchema = Depends(AuthPermission(permissions=["{{ permissionPrefix }}:remove"]))
):
id_list = ids.split(',')
del_result = await {{ tableName|snake_to_pascal_case }}Service.del_{{ tableName }}_services(auth, id_list)
return SuccessResponse(msg="删除成功")
@{{ tableName|snake_to_camel }}Controller.post('/export', summary="导出{{ functionName }}", description="导出{{ functionName }}")
async def export_{{ tableName }}(
{{ tableName }}_form: {{ tableName|snake_to_pascal_case }}PageModel = Form(),
auth: AuthSchema = Depends(AuthPermission(permissions=["{{ permissionPrefix }}:export"]))
):
# 获取全量数据
export_result = await {{ tableName|snake_to_pascal_case }}Service.export_{{ tableName }}_list_services(
auth, {{ tableName }}_form
)
return bytes2file_response(export_result)
@{{ tableName|snake_to_camel }}Controller.post('/import', dependencies=[Depends(CheckUserInterfaceAuth('{{ permissionPrefix }}:import'))])
async def import_{{ tableName }}(request: Request,
import_model: ImportModel,
query_db: AsyncSession = Depends(get_db),
current_user: CurrentUserModel = Depends(LoginService.get_current_user)
):
"""
导入数据
"""
await ImportService.import_data(query_db, import_model, current_user)
return ResponseUtil.success()
@@ -1,185 +0,0 @@
# -*- coding:utf-8 -*-
from typing import List, Optional
from sqlalchemy import delete, func, select, update
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import selectinload
from {{ packageName }}.entity.do.{{ tableName }}_do import {{ tableName|snake_to_pascal_case }}Model
from {{ packageName }}.entity.vo.{{ tableName }}_vo import {{ tableName|snake_to_pascal_case }}PageModel, {{ tableName|snake_to_pascal_case }}Model
from app.core.base_crud import CRUDBase
from app.api.v1.module_system.auth.schema import AuthSchema
# -*- coding:utf-8 -*-
from typing import List
from datetime import datetime, time
from module_admin.entity.do.role_do import SysRoleDept
from sqlalchemy import and_, delete, desc, func, or_, select, update
from sqlalchemy.ext.asyncio import AsyncSession
from module_gen.constants.gen_constants import GenConstants
{% if subTable %}
from sqlalchemy.orm import selectinload
{% endif %}
from {{ packageName }}.entity.do.{{ tableName }}_do import {{ tableName|snake_to_pascal_case }}
from {{ packageName }}.entity.vo.{{ tableName }}_vo import {{ tableName|snake_to_pascal_case }}PageModel, {{ tableName|snake_to_pascal_case }}Model
from utils.page_util import PageUtil, PageResponseModel
from utils.common_util import CamelCaseUtil
class {{ tableName|snake_to_pascal_case }}CRUD:
@classmethod
async def get_by_id(cls, db: AsyncSession, {{ tableName }}_id: int) -> {{ tableName|snake_to_pascal_case }}:
"""根据主键获取单条记录"""
{{ tableName }} = (((await db.execute(
select({{ tableName|snake_to_pascal_case }})
.where({{ tableName|snake_to_pascal_case }}.id == {{ tableName }}_id)))
.scalars())
.first())
return {{ tableName }}
"""
查询
"""
@classmethod
async def get_{{ tableName }}_list(cls, db: AsyncSession,
query_object: {{ tableName|snake_to_pascal_case }}PageModel,
data_scope_sql: str = None,
is_page: bool = False) -> [list | PageResponseModel]:
query = (
select({{ tableName|snake_to_pascal_case }})
{% if subTable %}
.options(selectinload({{ tableName|snake_to_pascal_case }}.{{ subTable.table_name }}_list))
{% endif %}
.where(
{% for column in columns %}
{% if column.isQuery == "1" %}
{% if column.queryType == "LIKE" %}
{{ tableName|snake_to_pascal_case }}.{{ column.columnName }}.like(f"%{query_object.{{ column.columnName }}}%") if query_object.{{ column.columnName }} else True,
{% elif column.queryType == "EQ" %}
{{ tableName|snake_to_pascal_case }}.{{ column.columnName }} == query_object.{{ column.columnName }} if query_object.{{ column.columnName }} else True,
{% elif column.queryType == "GT" %}
{{ tableName|snake_to_pascal_case }}.{{ column.columnName }} > query_object.{{ column.columnName }} if query_object.{{ column.columnName }} else True,
{% elif column.queryType == "GTE" %}
{{ tableName|snake_to_pascal_case }}.{{ column.columnName }} >= query_object.{{ column.columnName }} if query_object.{{ column.columnName }} else True,
{% elif column.queryType == "NE" %}
{{ tableName|snake_to_pascal_case }}.{{ column.columnName }} != query_object.{{ column.columnName }} if query_object.{{ column.columnName }} else True,
{% elif column.queryType == "LT" %}
{{ tableName|snake_to_pascal_case }}.{{ column.columnName }} < query_object.{{ column.columnName }} if query_object.{{ column.columnName }} else True,
{% elif column.queryType == "LTE" %}
{{ tableName|snake_to_pascal_case }}.{{ column.columnName }} <= query_object.{{ column.columnName }} if query_object.{{ column.columnName }} else True,
{% elif column.queryType == "BETWEEN" %}
{{ tableName|snake_to_pascal_case }}.{{ column.columnName }}.between(query_object.begin_{{ column.columnName }}, query_object.end_{{ column.columnName }}) if query_object.{{ column.columnName }} else True,
{% endif %}
{% endif %}
{% endfor %}
{{ tableName|snake_to_pascal_case }}.del_flag == '0',
eval(data_scope_sql) if data_scope_sql else True,
)
.order_by(desc({{ tableName|snake_to_pascal_case }}.create_time))
.distinct()
)
{{ tableName }}_list = await PageUtil.paginate(db, query, query_object.page_num, query_object.page_size, is_page)
return {{ tableName }}_list
@classmethod
async def add_{{ tableName }}(cls, db: AsyncSession, add_model: {{ tableName|snake_to_pascal_case }}Model, auto_commit: bool = True) -> {{ tableName|snake_to_pascal_case }}Model:
"""
增加
"""
{{ tableName }} = {{ tableName|snake_to_pascal_case }}(**add_model.model_dump(exclude_unset=True, {% if subTable %}exclude={'{{ subTable.table_name }}_list',}{% endif %}))
db.add({{ tableName }})
await db.flush()
{{ tableName }}_model = {{ tableName|snake_to_pascal_case }}Model(**CamelCaseUtil.transform_result({{ tableName }}))
if auto_commit:
await db.commit()
return {{ tableName }}_model
@classmethod
async def edit_{{ tableName }}(cls, db: AsyncSession, edit_model: {{ tableName|snake_to_pascal_case }}Model, auto_commit: bool = True) -> {{ tableName|snake_to_pascal_case }}:
"""
修改
"""
edit_dict_data = edit_model.model_dump(exclude_unset=True, exclude={ {% if subTable %}'{{ subTable.table_name }}_list', {% endif %}*GenConstants.DAO_COLUMN_NOT_EDIT })
await db.execute(update({{ tableName|snake_to_pascal_case }}), [edit_dict_data])
await db.flush()
if auto_commit:
await db.commit()
return await cls.get_by_id(db, edit_model.{{ pkColumn.pythonField }})
@classmethod
async def del_{{ tableName }}(cls, db: AsyncSession, {{ tableName }}_ids: List[str], soft_del: bool = True, auto_commit: bool = True):
"""
删除
"""
if soft_del:
await db.execute(update({{ tableName|snake_to_pascal_case }}).where({{ tableName|snake_to_pascal_case }}.id.in_({{ tableName }}_ids)).values(del_flag='2'))
else:
await db.execute(delete({{ tableName|snake_to_pascal_case }}).where({{ tableName|snake_to_pascal_case }}.id.in_({{ tableName }}_ids)))
await db.flush()
if auto_commit:
await db.commit()
class {{ tableName|snake_to_pascal_case }}Dao(CRUDBase[{{ tableName|snake_to_pascal_case }}Model, {{ tableName|snake_to_pascal_case }}Model, {{ tableName|snake_to_pascal_case }}Model]):
"""
{{ functionName }}模块数据库操作层
"""
def __init__(self, auth: AuthSchema) -> None:
"""初始化CRUD"""
super().__init__(model={{ tableName|snake_to_pascal_case }}Model(), auth=auth)
async def get_{{ tableName }}_by_id(self, db: AsyncSession, {{ tableName }}_id: int) -> Optional[{{ tableName|snake_to_pascal_case }}Model]:
"""
根据{{ tableName }}id获取{{ functionName }}信息
:param db: orm对象
:param {{ tableName }}_id: {{ tableName }}id
:return: {{ functionName }}信息对象
"""
{{ tableName }}_info = (
(
await db.execute(
select({{ tableName|snake_to_pascal_case }}Model)
.where({{ tableName|snake_to_pascal_case }}Model.id == {{ tableName }}_id)
)
)
.scalars()
.first()
)
return {{ tableName }}_info
async def get_{{ tableName }}_list(self, db: AsyncSession, query_object: {{ tableName|snake_to_pascal_case }}PageModel, is_page: bool = False):
"""
根据查询参数获取{{ functionName }}列表信息
:param db: orm对象
:param query_object: 查询参数对象
:param is_page: 是否开启分页
:return: {{ functionName }}列表信息对象
"""
query = select({{ tableName|snake_to_pascal_case }}Model)
# 执行查询
result = await db.execute(query)
all_data = list(result.scalars().all())
# 使用PaginationService.paginate进行分页
if is_page:
paginated_result = await PaginationService.paginate(
data_list=all_data,
page_no=query_object.page_no,
page_size=query_object.page_size
)
return paginated_result
else:
return {
"items": all_data,
"total": len(all_data),
"page_no": None,
"page_size": None,
"has_next": False
}
@@ -1,67 +0,0 @@
# -*- coding: utf-8 -*-
from datetime import datetime
from typing import Optional
from sqlalchemy import String, Integer, Text, DateTime
from sqlalchemy.orm import Mapped, mapped_column
from app.core.base_model import Base
# -*- coding:utf-8 -*-
from sqlalchemy import Column, ForeignKey, {{ importList }}
from config.database import BaseMixin, Base
{% if subTable %}
from sqlalchemy.orm import relationship
{% endif %}
class {{ tableName|snake_to_pascal_case }}Model(Base, BaseMixin):
"""
{{ functionName }}表
"""
__tablename__ = "{{ tableName }}"
{% for column in columns %}
{% if not column.columnName | is_base_column %}
{{ column.columnName }} = Column({{ column.columnType|get_sqlalchemy_type }}, {{ column | get_column_options }})
{% endif %}
{% endfor %}
{% if subTable %}
{{ subTable.table_name }}_list = relationship('{{ subClassName }}', back_populates='{{ tableName }}')
{% endif %}
{% if subTable %}
class {{ subClassName }}(Base, BaseMixin):
"""
{{ functionName }}表
"""
__tablename__ = '{{ subTableName }}'
{% for column in subTable.columns %}
{% if not column.column_name | is_base_column %}
{{ column.column_name }} = Column({{ column.column_type | get_sqlalchemy_type }}, {% if column.column_name == subTableFkName %}ForeignKey('{{ tableName }}.id'), {% endif %}{% if column.pk %}primary_key=True, {% endif %}{% if column.increment %}autoincrement=True, {% endif %}{% if column.required %}nullable=True{% else %}nullable=False{% endif %}, comment='{{ column.column_comment }}')
{% endif %}
{% endfor %}
{% if subTable %}
{{ tableName }} = relationship('{{ ClassName }}', back_populates='{{ subTable.table_name }}_list')
{% endif %}
{% endif %}
class {{ tableName|snake_to_pascal_case }}Model(Base):
"""
{{ functionName }}
"""
__tablename__ = '{{ tableName }}'
__table_args__ = {'comment': '{{ functionName }}'}
{% for column in columns %}
{{ column.columnName }}: Mapped[Optional[{{ column.pythonType }}]] = mapped_column({{ column.columnType|get_sqlalchemy_type }}, nullable=True, comment='{{ column.columnComment }}')
{% endfor %}
def __repr__(self):
return f"<{{ tableName|snake_to_pascal_case }}Model(id={self.id})>"
@@ -1,35 +0,0 @@
# -*- coding: utf-8 -*-
from datetime import datetime
from typing import Optional
from fastapi import Query
from app.core.validator import DateTimeStr
class {{ tableName|snake_to_pascal_case }}QueryParam:
"""示例查询参数"""
def __init__(
self,
name: Optional[str] = Query(None, description="名称"),
status: Optional[bool] = Query(None, description="是否启用"),
creator: Optional[int] = Query(None, description="创建人"),
start_time: Optional[DateTimeStr] = Query(None, description="开始时间", example="2023-01-01 00:00:00"),
end_time: Optional[DateTimeStr] = Query(None, description="结束时间", example="2023-12-31 23:59:59"),
) -> None:
super().__init__()
# 模糊查询字段
self.name = ("like", name)
# 精确查询字段
self.creator_id = creator
self.status = status
# 时间范围查询
if start_time and end_time:
start_datetime = datetime.strptime(str(start_time), '%Y-%m-%d %H:%M:%S')
end_datetime = datetime.strptime(str(end_time), '%Y-%m-%d %H:%M:%S')
self.created_at = ("between", (start_datetime, end_datetime))
@@ -1,111 +0,0 @@
# -*- coding:utf-8 -*-
from datetime import datetime
from typing import List, Optional
from pydantic import BaseModel, ConfigDict, Field
from pydantic.alias_generators import to_camel
from app.core.base_params import PageBaseParam
from app.core.base_schema import PageBaseSchema
# -*- coding:utf-8 -*-
from datetime import datetime
from pydantic import BaseModel, ConfigDict, Field
from pydantic.alias_generators import to_camel
from typing import List, Literal, Optional, Union
from module_admin.annotation.pydantic_annotation import as_query
class {{ tableName|snake_to_pascal_case }}{% if subTable %}Base{% endif %}Model(BaseModel):
"""
表对应pydantic模型
"""
model_config = ConfigDict(alias_generator=to_camel, from_attributes=True)
{% for column in columns %}
{{ column.columnName }}: Optional[{{ column.pythonType }}] = Field(default=None, description='{{ column.columnComment }}')
{% if column.queryType == 'BETWEEN' %}
begin_{{ column.columnName }}: Optional[{{ column.pythonType }}] = Field(default=None, description='{{ column.columnComment }}最小值')
{% endif %}
{% if column.queryType == 'BETWEEN' %}
end_{{ column.columnName }}: Optional[{{ column.pythonType }}] = Field(default=None, description='{{ column.columnComment }}最大值')
{% endif %}
{% endfor %}
{% if subTable %}
class {{ tableName|snake_to_pascal_case }}Model({{ tableName|snake_to_pascal_case }}BaseModel):
{{ subTableName }}_list: Optional[List['{{ subTable.table_name | snake_to_pascal_case }}Model']] = Field(default=None, description='子表列信息')
{% endif %}
@as_query
class {{ tableName|snake_to_pascal_case }}PageModel({{ tableName|snake_to_pascal_case }}{% if subTable %}Base{% endif %}Model):
"""
分页查询模型
"""
page_num: int = Field(default=1, description='当前页码')
page_size: int = Field(default=10, description='每页记录数')
{% if subTable %}
class {{ subTable.table_name | snake_to_pascal_case }}Schema(BaseModel):
"""
{{ subTable.function_name }}表对应pydantic模型
"""
model_config = ConfigDict(alias_generator=to_camel, from_attributes=True)
{% for sub_column in subTable.columns %}
{{ sub_column.column_name }}: Optional[{{ sub_column.python_type }}] = Field(default=None, description='{{ sub_column.column_comment}}')
{% endfor %}
{% for sub_column in subTable.columns %}
{% if sub_column.required %}
{% set parentheseIndex = sub_column.column_comment.find("") %}
{% set comment = sub_column.column_comment[:parentheseIndex] if parentheseIndex != -1 else sub_column.column_comment %}
@NotBlank(field_name='{{ sub_column.column_name }}', message='{{ comment }}不能为空')
def get_{{ sub_column.column_name }}(self):
return self.{{ sub_column.column_name }}
{% if not loop.last %}{{ "\n" }}{% endif %}
{% endif %}
{% endfor %}
{% endif %}
class {{ tableName|snake_to_pascal_case }}BaseModel(BaseModel):
"""
{{ functionName }}对应pydantic模型
"""
model_config = ConfigDict(alias_generator=to_camel, from_attributes=True)
{% for column in columns %}
{{ column.columnName }}: Optional[{{ column.pythonType }}] = Field(default=None, description='{{ column.columnComment }}')
{% endfor %}
class {{ tableName|snake_to_pascal_case }}Model({{ tableName|snake_to_pascal_case }}BaseModel):
"""
{{ functionName }}表模型
"""
id: Optional[int] = Field(default=None, description='编号')
create_by: Optional[str] = Field(default=None, description='创建者')
create_time: Optional[datetime] = Field(default=None, description='创建时间')
update_by: Optional[str] = Field(default=None, description='更新者')
update_time: Optional[datetime] = Field(default=None, description='更新时间')
remark: Optional[str] = Field(default=None, description='备注')
class {{ tableName|snake_to_pascal_case }}PageModel(PageBaseParam, {{ tableName|snake_to_pascal_case }}BaseModel):
"""
{{ functionName }}分页查询模型
"""
pass
class {{ tableName|snake_to_pascal_case }}PageObject(PageBaseSchema, {{ tableName|snake_to_pascal_case }}Model):
"""
{{ functionName }}分页查询结果模型
"""
pass
@@ -1,244 +0,0 @@
# -*- coding:utf-8 -*-
from datetime import datetime
from sqlalchemy.ext.asyncio import AsyncSession
from typing import Any, List, Dict, Optional
from app.core.exceptions import CustomException
from app.common.response import SuccessResponse
from app.api.v1.module_system.user.schema import UserOutSchema
from app.api.v1.module_system.auth.schema import AuthSchema
from {{ packageName }}.entity.vo.{{ tableName }}_vo import {{ tableName|snake_to_pascal_case }}PageModel, {{ tableName|snake_to_pascal_case }}Model
from {{ packageName }}.dao.{{ tableName }}_dao import {{ tableName|snake_to_pascal_case }}Dao
class {{ tableName|snake_to_pascal_case }}Service:
"""
{{ functionName }}服务层
"""
@classmethod
async def get_{{ tableName }}_list_services(
cls, auth: AuthSchema, query_object: {{ tableName|snake_to_pascal_case }}PageModel
):
"""
获取{{ functionName }}列表信息service
:param auth: 认证信息
:param query_object: 查询参数对象
:return: {{ functionName }}列表信息对象
"""
# 确保db是AsyncSession类型
if not isinstance(auth.db, AsyncSession):
raise CustomException(msg='数据库会话类型不正确')
{{ tableName }}_dao = {{ tableName|snake_to_pascal_case }}Dao(auth=auth)
{{ tableName }}_list_result = await {{ tableName }}_dao.get_{{ tableName }}_list(auth.db, query_object, is_page=True)
return {{ tableName }}_list_result
@classmethod
async def get_{{ tableName }}_by_id_services(cls, auth: AuthSchema, {{ tableName }}_id: int) -> {{ tableName|snake_to_pascal_case }}Model:
"""
根据{{ tableName }}id获取{{ functionName }}信息service
:param auth: 认证信息
:param {{ tableName }}_id: {{ tableName }}id
:return: {{ functionName }}信息对象
"""
# 确保db是AsyncSession类型
if not isinstance(auth.db, AsyncSession):
raise CustomException(msg='数据库会话类型不正确')
{{ tableName }}_dao = {{ tableName|snake_to_pascal_case }}Dao(auth=auth)
{{ tableName }} = await {{ tableName }}_dao.get_{{ tableName }}_by_id(auth.db, {{ tableName }}_id)
if {{ tableName }}:
return {{ tableName }}
else:
raise CustomException(msg='{{ functionName }}不存在')
@classmethod
async def add_{{ tableName }}_services(
cls, auth: AuthSchema, page_object: {{ tableName|snake_to_pascal_case }}Model
) -> SuccessResponse:
"""
新增{{ functionName }}service
:param auth: 认证信息
:param page_object: 新增{{ functionName }}对象
:return: 新增{{ functionName }}结果
"""
# 确保db是AsyncSession类型
if not isinstance(auth.db, AsyncSession):
raise CustomException(msg='数据库会话类型不正确')
{{ tableName }}_dao = {{ tableName|snake_to_pascal_case }}Dao(auth=auth)
try:
page_object.create_time = datetime.now()
await {{ tableName }}_dao.create(data=page_object.model_dump())
if isinstance(auth.db, AsyncSession):
await auth.db.commit()
return SuccessResponse(msg='新增成功')
except Exception as e:
if isinstance(auth.db, AsyncSession):
try:
await auth.db.rollback()
except:
pass # 忽略回滚错误
raise CustomException(msg=f'新增失败: {str(e)}')
@classmethod
async def update_{{ tableName }}_services(cls, auth: AuthSchema, page_object: {{ tableName|snake_to_pascal_case }}Model) -> SuccessResponse:
"""
编辑{{ functionName }}service
:param auth: 认证信息
:param page_object: 编辑{{ functionName }}对象
:return: 编辑{{ functionName }}结果
"""
# 确保db是AsyncSession类型
if not isinstance(auth.db, AsyncSession):
raise CustomException(msg='数据库会话类型不正确')
{{ tableName }}_dao = {{ tableName|snake_to_pascal_case }}Dao(auth=auth)
# 检查必要字段是否存在
if page_object.id is None:
raise CustomException(msg='{{ functionName }}ID不能为空')
edit_{{ tableName }} = page_object.model_dump(exclude_unset=True)
{{ tableName }}_info = await cls.get_{{ tableName }}_by_id_services(auth, page_object.id)
if {{ tableName }}_info:
try:
edit_{{ tableName }}['update_time'] = datetime.now()
await {{ tableName }}_dao.update(id=page_object.id, data=edit_{{ tableName }})
if isinstance(auth.db, AsyncSession):
await auth.db.commit()
return SuccessResponse(msg='更新成功')
except Exception as e:
if isinstance(auth.db, AsyncSession):
try:
await auth.db.rollback()
except:
pass # 忽略回滚错误
raise CustomException(msg=f'更新失败: {str(e)}')
else:
raise CustomException(msg='{{ functionName }}不存在')
@classmethod
async def del_{{ tableName }}_services(cls, auth: AuthSchema, ids: List[str]) -> SuccessResponse:
"""
删除{{ functionName }}service
:param auth: 认证信息
:param ids: {{ functionName }}id列表
:return: 删除{{ functionName }}结果
"""
# 确保db是AsyncSession类型
if not isinstance(auth.db, AsyncSession):
raise CustomException(msg='数据库会话类型不正确')
{{ tableName }}_dao = {{ tableName|snake_to_pascal_case }}Dao(auth=auth)
try:
id_list = [int(id) for id in ids]
await {{ tableName }}_dao.delete(ids=id_list)
if isinstance(auth.db, AsyncSession):
await auth.db.commit()
return SuccessResponse(msg='删除成功')
except Exception as e:
if isinstance(auth.db, AsyncSession):
try:
await auth.db.rollback()
except:
pass # 忽略回滚错误
raise CustomException(msg=f'删除失败: {str(e)}')
@classmethod
async def export_{{ tableName }}_list_services(cls, auth: AuthSchema, query_object: {{ tableName|snake_to_pascal_case }}PageModel) -> bytes:
"""
导出{{ functionName }}service
:param auth: 认证信息
:param query_object: 查询参数对象
:return: 导出{{ functionName }}结果
"""
# 确保db是AsyncSession类型
if not isinstance(auth.db, AsyncSession):
raise CustomException(msg='数据库会话类型不正确')
{{ tableName }}_dao = {{ tableName|snake_to_pascal_case }}Dao(auth=auth)
{{ tableName }}_list_result = await {{ tableName }}_dao.get_{{ tableName }}_list(auth.db, query_object, is_page=False)
# 这里应该实现导出逻辑,例如生成Excel文件
# 为简化起见,我们返回一个简单的文本文件
export_data = "ID,名称\n"
for item in {{ tableName }}_list_result.get("items", []):
export_data += f"{item.id},{getattr(item, 'name', '')}\n"
return export_data.encode('utf-8')
# -*- coding:utf-8 -*-
from typing import List
from sqlalchemy.ext.asyncio import AsyncSession
from utils.common_util import CamelCaseUtil, export_list2excel
from module_admin.entity.vo.sys_table_vo import SysTablePageModel
from module_admin.service.sys_table_service import SysTableService
from utils.page_util import PageResponseModel
from {{ packageName }}.dao.{{ tableName }}_dao import {{ tableName|snake_to_pascal_case }}Dao
from {{ packageName }}.entity.do.{{ tableName }}_do import {{ tableName|snake_to_pascal_case }}
from {{ packageName }}.entity.vo.{{ tableName }}_vo import {{ tableName|snake_to_pascal_case }}PageModel, {{ tableName|snake_to_pascal_case }}Model
class {{ tableName|snake_to_pascal_case }}Service:
"""
{{ tableName|snake_to_pascal_case }}管理模块服务层
"""
@classmethod
async def get_{{ tableName }}_list(cls, query_db: AsyncSession, query_object: {{ tableName|snake_to_pascal_case }}PageModel, data_scope_sql: str) -> [list | PageResponseModel]:
{{ tableName }}_list = await {{ tableName|snake_to_pascal_case }}Dao.get_{{ tableName }}_list(query_db, query_object, data_scope_sql, is_page=True)
return {{ tableName }}_list
@classmethod
async def get_{{ tableName }}_by_id(cls, query_db: AsyncSession, {{ tableName }}_id: int) -> {{ tableName|snake_to_pascal_case }}Model:
{{ tableName }} = await {{ tableName|snake_to_pascal_case }}Dao.get_by_id(query_db, {{ tableName }}_id)
{{ tableName }}_model = {{ tableName|snake_to_pascal_case }}Model(**CamelCaseUtil.transform_result({{ tableName }}))
return {{ tableName }}_model
@classmethod
async def add_{{ tableName }}(cls, query_db: AsyncSession, query_object: {{ tableName|snake_to_pascal_case }}Model) -> {{ tableName|snake_to_pascal_case }}Model:
{{ tableName }}_model = await {{ tableName|snake_to_pascal_case }}Dao.add_{{ tableName }}(query_db, query_object)
return {{ tableName }}_model
@classmethod
async def update_{{ tableName }}(cls, query_db: AsyncSession, query_object: {{ tableName|snake_to_pascal_case }}Model) -> {{ tableName|snake_to_pascal_case }}Model:
{{ tableName }} = await {{ tableName|snake_to_pascal_case }}Dao.edit_{{ tableName }}(query_db, query_object)
{{ tableName }}_model = {{ tableName|snake_to_pascal_case }}Model(**CamelCaseUtil.transform_result({{ tableName }}))
return {{ tableName }}_model
@classmethod
async def del_{{ tableName }}(cls, query_db: AsyncSession, {{ tableName }}_ids: List[str]):
await {{ tableName|snake_to_pascal_case }}Dao.del_{{ tableName }}(query_db, {{ tableName }}_ids)
@classmethod
async def export_{{ tableName }}_list(cls, query_db: AsyncSession, query_object: {{ tableName|snake_to_pascal_case }}PageModel, data_scope_sql) -> bytes:
{{ tableName }}_list = await {{ tableName|snake_to_pascal_case }}Dao.get_{{ tableName }}_list(query_db, query_object, data_scope_sql, is_page=False)
filed_list = await SysTableService.get_sys_table_list(query_db, SysTablePageModel(tableName='{{ tableName }}'), is_page=False)
filtered_filed = sorted(filter(lambda x: x["show"] == '1', filed_list), key=lambda x: x["sequence"])
new_data = []
for item in {{ tableName }}_list:
mapping_dict = {}
for fild in filtered_filed:
if fild["prop"] in item:
mapping_dict[fild["label"]] = item[fild["prop"]]
new_data.append(mapping_dict)
binary_data = export_list2excel(new_data)
return binary_data
@@ -1,36 +0,0 @@
-- 菜单 SQL
insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
values('{{functionName}}', '{{parentMenuId}}', '1', '{{businessName}}', '{{moduleName}}/{{businessName}}/index', 1, 0, 'C', '0', '0', '{{permissionPrefix}}:list', '#', 'admin', sysdate(), '', null, '{{functionName}}菜单');
-- 按钮父菜单ID
SELECT @parentId := LAST_INSERT_ID();
-- 按钮 SQL
insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
values('{{functionName}}查询', @parentId, '1', '#', '', 1, 0, 'F', '0', '0', '{{permissionPrefix}}:query', '#', 'admin', sysdate(), '', null, '');
insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
values('{{functionName}}新增', @parentId, '2', '#', '', 1, 0, 'F', '0', '0', '{{permissionPrefix}}:add', '#', 'admin', sysdate(), '', null, '');
insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
values('{{functionName}}修改', @parentId, '3', '#', '', 1, 0, 'F', '0', '0', '{{permissionPrefix}}:edit', '#', 'admin', sysdate(), '', null, '');
insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
values('{{functionName}}删除', @parentId, '4', '#', '', 1, 0, 'F', '0', '0', '{{permissionPrefix}}:remove', '#', 'admin', sysdate(), '', null, '');
insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
values('{{functionName}}导出', @parentId, '5', '#', '', 1, 0, 'F', '0', '0', '{{permissionPrefix}}:export', '#', 'admin', sysdate(), '', null, '');
insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
values('{{functionName}}导入', @parentId, '6', '#', '', 1, 0, 'F', '0', '0', '{{permissionPrefix}}:import', '#', 'admin', sysdate(), '', null, '');
{% for column in columns %}
{% set pythonField = column.pythonField | snake_to_camel %}
{% set parentheseIndex = column.columnComment.find("") %}
{% set comment = column.columnComment[:parentheseIndex] if parentheseIndex != -1 else column.columnComment %}
{% if column.isList %}
INSERT INTO `sys_table` (`table_name`, `field_name`, `prop`, `label`, `sequence`) VALUES ('{{ tableName }}', '{{ column.pythonField }}', '{{ pythonField }}', '{{ comment }}', {{ loop.index0 }});
{% endif %}
{% endfor %}
INSERT INTO `sys_table` (`table_name`, `field_name`, `prop`, `label`, `sequence`, `fixed`) VALUES ('{{ tableName }}', 'operate', 'operate', '操作', {{ columns|length }}, '2');
@@ -1,53 +0,0 @@
import request from '@/utils/request'
// 查询{{functionName}}列表
export function list{{BusinessName}}(query) {
return request({
url: '/{{moduleName}}/{{businessName}}/list',
method: 'get',
params: query
})
}
// 查询{{functionName}}详细
export function get{{BusinessName}}(id) {
return request({
url: '/{{moduleName}}/{{businessName}}/' + id,
method: 'get'
})
}
// 新增{{functionName}}
export function add{{BusinessName}}(data) {
return request({
url: '/{{moduleName}}/{{businessName}}/create',
method: 'post',
data: data
})
}
// 修改{{functionName}}
export function update{{BusinessName}}(data) {
return request({
url: '/{{moduleName}}/{{businessName}}/update',
method: 'put',
data: data
})
}
// 删除{{functionName}}
export function del{{BusinessName}}(ids) {
return request({
url: '/{{moduleName}}/{{businessName}}/' + ids,
method: 'delete'
})
}
// 导入{{functionName}}
export function import{{BusinessName}}(data) {
return request({
url: '/{{moduleName}}/{{businessName}}/import',
method: 'post',
data: data
})
}
@@ -1,628 +0,0 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="68px">
{% for column in columns %}
{% if column.isQuery == "1" %}
{% set dictType = column.dictType %}
{% set parentheseIndex = column.columnComment.find("") %}
{% set comment = column.columnComment[:parentheseIndex] if parentheseIndex != -1 else column.columnComment %}
{% if column.htmlType == "input" %}
<el-form-item label="{{ comment }}" prop="{{ column.pythonField | snake_to_camel }}">
<el-input
v-model="queryParams.{{ column.pythonField | snake_to_camel }}"
placeholder="请输入{{ comment }}"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
{% elif (column.htmlType == "select" or column.htmlType == "radio") and dictType != "" %}
<el-form-item label="{{ comment }}" prop="{{ column.pythonField | snake_to_camel }}">
<el-select
v-model="queryParams.{{ column.pythonField | snake_to_camel }}"
placeholder="请选择{{ comment }}"
style="width: 180px"
clearable>
<el-option
v-for="dict in {{ dictType }}"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
{% elif (column.htmlType == "select" or column.htmlType == "radio") and dictType %}
<el-form-item label="{{ comment }}" prop="{{ column.pythonField | snake_to_camel }}">
<el-select v-model="queryParams.{{ column.pythonField | snake_to_camel }}" placeholder="请选择{{ comment }}" clearable>
<el-option label="请选择字典生成" value="" />
</el-select>
</el-form-item>
{% elif column.htmlType == "datetime" and column.queryType != "BETWEEN" %}
<el-form-item label="{{ comment }}" prop="{{ column.pythonField | snake_to_camel }}">
<el-date-picker clearable
v-model="queryParams.{{ column.pythonField | snake_to_camel }}"
type="date"
value-format="YYYY-MM-DD"
placeholder="请选择{{ comment }}">
</el-date-picker>
</el-form-item>
{% elif column.htmlType == "datetime" and column.queryType == "BETWEEN" %}
<el-form-item label="{{ comment }}" style="width: 308px">
<el-date-picker
v-model="daterange{{ column.pythonField | snake_to_pascal_case }}"
value-format="YYYY-MM-DD"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
></el-date-picker>
</el-form-item>
{% endif %}
{% endif %}
{% endfor %}
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-card class="base-table" ref="fullTable">
<TableSetup
ref="tSetup"
@onStripe="onStripe"
@onRefresh="onRefresh"
@onChange="onChange"
@onfullTable="onfullTable"
@onSearchChange="onSearchChange"
:columns="columns"
:isTable="isTable"
>
<template v-slot:operate>
<el-button
type="primary"
plain
icon="Plus"
@click="handleAdd"
v-hasPermi="['{{ moduleName }}:{{ businessName }}:add']"
>新增</el-button>
<el-button
type="success"
plain
icon="Edit"
:disabled="single"
@click="handleUpdate"
v-hasPermi="['{{ moduleName }}:{{ businessName }}:edit']"
>修改</el-button>
<el-button
type="danger"
plain
icon="Delete"
:disabled="multiple"
@click="handleDelete"
v-hasPermi="['{{ moduleName }}:{{ businessName }}:remove']"
>删除</el-button>
<el-button
type="primary"
plain
icon="Upload"
@click="handleImport"
v-hasPermi="['{{ moduleName }}:{{ businessName }}:import']"
>导入</el-button
>
<el-button
type="warning"
plain
icon="Download"
@click="handleExport"
v-hasPermi="['{{ moduleName }}:{{ businessName }}:export']"
>导出</el-button>
</template>
</TableSetup>
<auto-table
ref="multipleTable"
class="mytable"
:tableData="{{ businessName }}List"
:columns="columns"
:loading="loading"
:stripe="stripe"
:tableHeight="tableHeight"
@onColumnWidthChange="onColumnWidthChange"
@onSelectionChange="handleSelectionChange"
>
{% for column in columns %}
{% set pythonField = column.pythonField | snake_to_camel %}
{% set parentheseIndex = column.columnComment.find("") %}
{% set comment = column.columnComment[:parentheseIndex] if parentheseIndex != -1 else column.columnComment %}
{% if column.isList and column.htmlType == "datetime" %}
<template #{{ pythonField }}="{ row }">
<span>{% raw %}{{{% endraw %} parseTime(row.{{ pythonField }}, '{y}-{m}-{d}') {% raw %}}}{% endraw %}</span>
</template>
{% elif column.isList == "1" and column.htmlType == "imageUpload" %}
<template #{{ pythonField }}="{ row }">
<image-preview :src="fullUrl(row.{{ pythonField }})" v-if="row.{{ pythonField }}" :width="50" :height="50"/>
</template>
{% elif column.isList == "1" and column.dictType != "" %}
<template #{{ pythonField }}="{ row }">
{% if column.htmlType == "checkbox" %}
<dict-tag :options="{{ column.dictType }}" :value="row.{{ pythonField }} ? row.{{ pythonField }}.split(',') : []"/>
{% else %}
<dict-tag :options="{{ column.dictType }}" :value="row.{{ pythonField }}"/>
{% endif %}
</template>
{% endif %}
{% endfor %}
<template #operate="{ row }">
<el-button link type="primary" icon="Edit" @click="handleUpdate(row)" v-hasPermi="['{{ moduleName }}:{{ businessName }}:edit']">修改</el-button>
<el-button link type="primary" icon="Delete" @click="handleDelete(row)" v-hasPermi="['{{ moduleName }}:{{ businessName }}:remove']">删除</el-button>
</template>
</auto-table>
<div class="table-pagination">
<pagination
v-show="total > 0"
:total="total"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</div>
</el-card>
<!-- 添加或修改{{ functionName }}对话框 -->
<el-dialog :title="title" v-model="open" width="800px" append-to-body>
<el-form ref="{{ businessName }}Ref" :model="form" :rules="rules" label-width="80px">
{% for column in columns %}
{% set field = column.pythonField | snake_to_camel %}
{% if column.isInsert == "1" and not column.isPk == "1" %}
{% if column.usableColumn or not column.superColumn %}
{% set parentheseIndex = column.columnComment.find("") %}
{% set comment = column.columnComment[:parentheseIndex] if parentheseIndex != -1 else column.columnComment %}
{% set dictType = column.dictType %}
{% if column.htmlType == "input" %}
<el-form-item label="{{ comment }}" prop="{{ field }}">
<el-input v-model="form.{{ field }}" placeholder="请输入{{ comment }}" />
</el-form-item>
{% elif column.htmlType == "imageUpload" %}
<el-form-item label="{{ comment }}" prop="{{ field }}">
<image-upload v-model="form.{{ field }}"/>
</el-form-item>
{% elif column.htmlType == "fileUpload" %}
<el-form-item label="{{ comment }}" prop="{{ field }}">
<file-upload v-model="form.{{ field }}"/>
</el-form-item>
{% elif column.htmlType == "editor" %}
<el-form-item label="{{ comment }}">
<editor v-model="form.{{ field }}" :min-height="192"/>
</el-form-item>
{% elif column.htmlType == "select" and dictType != "" %}
<el-form-item label="{{ comment }}" prop="{{ field }}">
<el-select v-model="form.{{ field }}" placeholder="请选择{{ comment }}">
<el-option
v-for="dict in {{ dictType }}"
:key="dict.value"
:label="dict.label"
{% if column.pythonType == "int" %}
:value="parseInt(dict.value)"
{% else %}
:value="dict.value"
{% endif %}
></el-option>
</el-select>
</el-form-item>
{% elif column.htmlType == "select" and dictType %}
<el-form-item label="{{ comment }}" prop="{{ field }}">
<el-select v-model="form.{{ field }}" placeholder="请选择{{ comment }}">
<el-option label="请选择字典生成" value="" />
</el-select>
</el-form-item>
{% elif column.htmlType == "checkbox" and dictType != "" %}
<el-form-item label="{{ comment }}" prop="{{ field }}">
<el-checkbox-group v-model="form.{{ field }}">
<el-checkbox
v-for="dict in {{ dictType }}"
:key="dict.value"
:label="dict.value">
{{ dict.label }}
</el-checkbox>
</el-checkbox-group>
</el-form-item>
{% elif column.htmlType == "checkbox" and dictType %}
<el-form-item label="{{ comment }}" prop="{{ field }}">
<el-checkbox-group v-model="form.{{ field }}">
<el-checkbox>请选择字典生成</el-checkbox>
</el-checkbox-group>
</el-form-item>
{% elif column.htmlType == "radio" and dictType != "" %}
<el-form-item label="{{ comment }}" prop="{{ field }}">
<el-radio-group v-model="form.{{ field }}">
<el-radio
v-for="dict in {{ dictType }}"
:key="dict.value"
{% if column.pythonType == "int" %}
:label="parseInt(dict.value)"
{% else %}
:label="dict.value"
{% endif %}
>{{ dict.label }}</el-radio>
</el-radio-group>
</el-form-item>
{% elif column.htmlType == "radio" and dictType %}
<el-form-item label="{{ comment }}" prop="{{ field }}">
<el-radio-group v-model="form.{{ field }}">
<el-radio label="1">请选择字典生成</el-radio>
</el-radio-group>
</el-form-item>
{% elif column.htmlType == "datetime" %}
<el-form-item label="{{ comment }}" prop="{{ field }}">
<el-date-picker clearable
v-model="form.{{ field }}"
type="date"
value-format="YYYY-MM-DD"
placeholder="请选择{{ comment }}">
</el-date-picker>
</el-form-item>
{% elif column.htmlType == "textarea" %}
<el-form-item label="{{ comment }}" prop="{{ field }}">
<el-input v-model="form.{{ field }}" type="textarea" placeholder="请输入内容" />
</el-form-item>
{% endif %}
{% endif %}
{% endif %}
{% endfor %}
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm">确 定</el-button>
<el-button @click="cancel">取 消</el-button>
</div>
</template>
</el-dialog>
<!-- 导入数据对话框 -->
<ImportData
v-if="openImport"
v-model="openImport"
tableName="{{ tableName }}"
@success="handleImportSuccess"
/>
</div>
</template>
<script setup name="{{ tableName|snake_to_pascal_case }}">
import { list{{ BusinessName }}, get{{ BusinessName }}, del{{ BusinessName }}, add{{ BusinessName }}, update{{ BusinessName }}, import{{ BusinessName }} } from "@/api/{{ moduleName }}/{{ businessName }}";
import { listAllTable } from '@/api/system/table'
import TableSetup from '@/components/TableSetup'
import AutoTable from '@/components/AutoTable'
import ImportData from '@/components/ImportData'
const { proxy } = getCurrentInstance();
{% if dicts != '' %}
{% set dictsNoSymbol = dicts.replace("'", "") %}
const { {{ dictsNoSymbol }} } = proxy.useDict({{ dicts }});
{% endif %}
const {{ businessName }}List = ref([]);
{#{% if table.sub %}#}
{# const {{ subclassName }}List = ref([]);#}
{#{% endif %}#}
const open = ref(false);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref([]);
{#{% if table.sub %}#}
{# const checked{{ subClassName }} = ref([]);#}
{#{% endif %}#}
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const title = ref("");
{% for column in columns %}
{% if column.htmlType == "datetime" and column.queryType == "BETWEEN" %}
const daterange{{ column.pythonField | snake_to_pascal_case }} = ref([]);
{% endif %}
{% endfor %}
const columns = ref([])
const stripe = ref(true)
const isTable = ref(true)
const tableHeight = ref(500)
const fullScreen = ref(false)
const openImport = ref(false)
const data = reactive({
form: {},
queryParams: {
pageNum: 1,
pageSize: 10,
{% for column in columns %}
{% if column.isQuery == "1" %}
{{ column.pythonField | snake_to_camel }}: null{% if not loop.last %},{% endif %}
{% endif %}
{% endfor %}
},
rules: {
{% for column in columns %}
{% if column.isRequired == "1" %}
{% set parentheseIndex = column.columnComment.find("") %}
{% set comment = column.columnComment[:parentheseIndex] if parentheseIndex != -1 else column.columnComment %}
{{ column.pythonField | snake_to_camel }}: [
{ required: true, message: "{{ comment }}不能为空", trigger: "{% if column.htmlType == "select" or column.htmlType == "radio" %}change{% else %}blur{% endif %}" }
]{% if not loop.last %},{% endif %}
{% endif %}
{% endfor %}
}
});
const { queryParams, form, rules } = toRefs(data);
/** 查询{{ functionName }}列表 */
function getList() {
loading.value = true;
{% for column in columns %}
{% if column.htmlType == "datetime" and column.queryType == "BETWEEN" %}
queryParams.value.params = {};
{% endif %}
{% endfor %}
{% for column in columns %}
{% if column.htmlType == "datetime" and column.queryType == "BETWEEN" %}
if (null != daterange{{ column.pythonField | snake_to_pascal_case }} && '' != daterange{{ column.pythonField | snake_to_pascal_case }}) {
queryParams.value.params["begin{{ column.pythonField | snake_to_pascal_case }}"] = daterange{{ column.pythonField | snake_to_pascal_case }}.value[0];
queryParams.value.params["end{{ column.pythonField | snake_to_pascal_case }}"] = daterange{{ column.pythonField | snake_to_pascal_case }}.value[1];
}
{% endif %}
{% endfor %}
list{{ BusinessName }}(queryParams.value).then(response => {
{{ businessName }}List.value = response.rows;
total.value = response.total;
loading.value = false;
});
}
function getColumns() {
listAllTable({ tableName: '{{ tableName }}' })
.then((response) => {
columns.value = response.data
})
.then(() => {
getList()
})
}
// 取消按钮
function cancel() {
open.value = false;
reset();
}
// 表单重置
function reset() {
form.value = {
{% for column in columns %}
{% if column.htmlType == "checkbox" %}
{{ column.pythonField | snake_to_camel }}: []{% if not loop.last %},{% endif %}
{% else %}
{{ column.pythonField | snake_to_camel }}: null{% if not loop.last %},{% endif %}
{% endif %}
{% endfor %}
};
{# {% if table.sub %}#}
{# {{ subclassName }}List.value = [];#}
{# {% endif %}#}
proxy.resetForm("{{ businessName }}Ref");
}
/** 搜索按钮操作 */
function handleQuery() {
queryParams.value.pageNum = 1;
getList();
}
/** 重置按钮操作 */
function resetQuery() {
{% for column in columns %}
{% if column.htmlType == "datetime" and column.queryType == "BETWEEN" %}
daterange{{ column.pythonField | snake_to_pascal_case }}.value = [];
{% endif %}
{% endfor %}
proxy.resetForm("queryRef");
handleQuery();
}
// 多选框选中数据
function handleSelectionChange(selection) {
ids.value = selection.map(item => item.{{ pkColumn.pythonField }});
single.value = selection.length != 1;
multiple.value = !selection.length;
}
/** 新增按钮操作 */
function handleAdd() {
reset();
open.value = true;
title.value = "添加{{ functionName }}";
}
/** 新增按钮操作 */
function handleImport() {
openImport.value = true
}
/** 修改按钮操作 */
function handleUpdate(row) {
reset();
const {{ tableName | snake_to_camel }}{{ pkColumn.pythonField | snake_to_pascal_case }} = row.{{ pkColumn.pythonField | snake_to_camel }} || ids.value
get{{ BusinessName }}({{ tableName | snake_to_camel }}{{ pkColumn.pythonField | snake_to_pascal_case }}).then(response => {
form.value = response.data;
{% for column in columns %}
{% if column.htmlType == "checkbox" %}
form.value.{{ column.pythonField | snake_to_camel }} = form.value.{{ column.pythonField | snake_to_camel }}.split(",");
{% endif %}
{% endfor %}
{# {% if table.sub %}#}
{# {{ subclassName }}List.value = response.data.{{ subclassName }}List;#}
{# {% endif %}#}
open.value = true;
title.value = "修改{{ functionName }}";
});
}
/** 提交按钮 */
function submitForm() {
proxy.$refs["{{ businessName }}Ref"].validate(valid => {
if (valid) {
{% for column in columns %}
{% if column.htmlType == "checkbox" %}
form.value.{{ column.pythonField | snake_to_camel }} = form.value.{{ column.pythonField | snake_to_camel }}.join(",");
{% endif %}
{% endfor %}
{# {% if table.sub %}#}
{# form.value.{{ subclassName }}List = {{ subclassName }}List.value;#}
{# {% endif %}#}
if (form.value.{{ pkColumn.pythonField }} != null) {
update{{ BusinessName }}(form.value).then(response => {
proxy.$modal.msgSuccess("修改成功");
open.value = false;
getList();
});
} else {
add{{ BusinessName }}(form.value).then(response => {
proxy.$modal.msgSuccess("新增成功");
open.value = false;
getList();
});
}
}
});
}
/** 删除按钮操作 */
function handleDelete(row) {
const _{{ pkColumn.pythonField }}s = row.{{ pkColumn.pythonField }} || ids.value;
proxy.$modal.confirm('是否确认删除{{ functionName }}编号为"' + _{{ pkColumn.pythonField }}s + '"的数据项?').then(function() {
return del{{ BusinessName }}(_{{ pkColumn.pythonField }}s);
}).then(() => {
getList();
proxy.$modal.msgSuccess("删除成功");
}).catch(() => {});
}
{#{% if table.sub %}#}
{# /** {{ subTable.functionName }}序号 */#}
{# function row{{ subClassName }}Index({ row, rowIndex }) {#}
{# row.index = rowIndex + 1;#}
{# }#}
{##}
{# /** {{ subTable.functionName }}添加按钮操作 */#}
{# function handleAdd{{ subClassName }}() {#}
{# let obj = {};#}
{# {% for column in subTable.columns %}#}
{# {% if column.pk or column.pythonField == subTableFkclassName %}#}
{# {% elif column.list and pythonField != "" %}#}
{# obj.{{ column.pythonField }} = "";#}
{# {% endif %}#}
{# {% endfor %}#}
{# {{ subclassName }}List.value.push(obj);#}
{# }#}
{##}
{# /** {{ subTable.functionName }}删除按钮操作 */#}
{# function handleDelete{{ subClassName }}() {#}
{# if (checked{{ subClassName }}.value.length == 0) {#}
{# proxy.$modal.msgError("请先选择要删除的{{ subTable.functionName }}数据");#}
{# } else {#}
{# const {{ subclassName }}s = {{ subclassName }}List.value;#}
{# const checked{{ subClassName }}s = checked{{ subClassName }}.value;#}
{# {{ subclassName }}List.value = {{ subclassName }}s.filter(function(item) {#}
{# return checked{{ subClassName }}s.indexOf(item.index) == -1#}
{# });#}
{# }#}
{# }#}
{##}
{# /** 复选框选中数据 */#}
{# function handle{{ subClassName }}SelectionChange(selection) {#}
{# checked{{ subClassName }}.value = selection.map(item => item.index)#}
{# }#}
{#{% endif %}#}
/** 导出按钮操作 */
function handleExport() {
proxy.download('{{ moduleName }}/{{ businessName }}/export', {
...queryParams.value
}, `{{ businessName }}_${new Date().getTime()}.xlsx`)
}
//表格全屏
function onfullTable() {
proxy.$refs.tSetup.onFull(proxy.$refs.fullTable.$el)
fullScreen.value = !fullScreen.value
updateTableHeight()
}
//表格刷新
function onRefresh() {
getList()
}
//搜索框显示隐藏
function onSearchChange() {
showSearch.value = !showSearch.value
}
function onStripe(val) {
stripe.value = val
}
//改变表头数据
function onChange(val) {
columns.value = val
}
//改变表格宽度
function onColumnWidthChange(column) {
proxy.$refs.tSetup.tableWidth(column)
}
//更新表格高度
function updateTableHeight() {
if (
proxy.$refs.tSetup &&
proxy.$refs.queryRef &&
document.querySelector('.table-pagination')
) {
if (fullScreen.value) {
tableHeight.value = window.innerHeight - 145
} else {
tableHeight.value =
window.innerHeight -
proxy.$refs.tSetup.$el.clientHeight -
proxy.$refs.queryRef.$el.clientHeight -
document.querySelector('.table-pagination').clientHeight -
220
}
}
}
//导入成功
function handleImportSuccess(sheetName, filedInfo, fileName) {
let data = {
tableName: '{{ tableName }}',
filedInfo: filedInfo,
fileName: fileName,
sheetName: sheetName
}
import{{ BusinessName }}(data).then(() => {
proxy.$modal.msgSuccess('导入成功')
openImport.value = false
getList()
})
getList()
}
onMounted(() => {
updateTableHeight() // 初始化计算高度
window.addEventListener('resize', updateTableHeight) // 监听窗口大小变化
})
onUnmounted(() => {
window.removeEventListener('resize', updateTableHeight) // 销毁监听
})
getColumns()
</script>
@@ -6,6 +6,7 @@ from .cache.controller import CacheRouter
from .job.controller import JobRouter
from .online.controller import OnlineRouter
from .server.controller import ServerRouter
from .resource.controller import ResourceRouter
MonitorRouter = APIRouter(prefix="/monitor")
@@ -14,4 +15,5 @@ MonitorRouter = APIRouter(prefix="/monitor")
MonitorRouter.include_router(CacheRouter)
MonitorRouter.include_router(JobRouter)
MonitorRouter.include_router(OnlineRouter)
MonitorRouter.include_router(ServerRouter)
MonitorRouter.include_router(ServerRouter)
MonitorRouter.include_router(ResourceRouter)
@@ -4,10 +4,8 @@ from fastapi import APIRouter, Body, Depends, Path, Query, Request, UploadFile,
from fastapi.responses import JSONResponse, StreamingResponse, FileResponse
from typing import List, Optional
from app.common.request import PaginationService
from app.common.response import StreamResponse, SuccessResponse
from app.utils.common_util import bytes2file_response
from app.core.base_params import PaginationQueryParam
from app.core.dependencies import AuthPermission
from app.core.router_class import OperationLogRoute
from app.core.logger import logger
@@ -23,15 +21,15 @@ from .schema import (
from .service import ResourceService
ResourceFileRouter = APIRouter(route_class=OperationLogRoute, prefix="/resource", tags=["资源管理"])
ResourceRouter = APIRouter(route_class=OperationLogRoute, prefix="/resource", tags=["资源管理"])
@ResourceFileRouter.get("/list", summary="获取目录列表", description="获取指定目录下的文件和子目录列表")
@ResourceRouter.get("/list", summary="获取目录列表", description="获取指定目录下的文件和子目录列表")
async def get_directory_list_controller(
request: Request,
path: Optional[str] = Query(None, description="目录路径"),
include_hidden: bool = Query(False, description="是否包含隐藏文件"),
auth: AuthSchema = Depends(AuthPermission(permissions=["resource:file:query"]))
auth: AuthSchema = Depends(AuthPermission(permissions=["monitor:resource:query"]))
) -> JSONResponse:
"""获取目录列表"""
result_dict = await ResourceService.get_directory_list_service(
@@ -44,11 +42,11 @@ async def get_directory_list_controller(
return SuccessResponse(data=result_dict, msg="获取目录列表成功")
@ResourceFileRouter.post("/search", summary="搜索资源", description="根据条件搜索资源")
@ResourceRouter.post("/search", summary="搜索资源", description="根据条件搜索资源")
async def search_resources_controller(
request: Request,
search: ResourceSearchSchema,
auth: AuthSchema = Depends(AuthPermission(permissions=["resource:file:search"]))
auth: AuthSchema = Depends(AuthPermission(permissions=["monitor:resource:search"]))
) -> JSONResponse:
"""搜索资源"""
result_list = await ResourceService.search_resources_service(
@@ -60,12 +58,12 @@ async def search_resources_controller(
return SuccessResponse(data=result_list, msg=f"搜索成功,找到 {len(result_list)} 个结果")
@ResourceFileRouter.post("/upload", summary="上传文件", description="上传文件到指定目录")
@ResourceRouter.post("/upload", summary="上传文件", description="上传文件到指定目录")
async def upload_file_controller(
file: UploadFile,
request: Request,
target_path: Optional[str] = Form(None, description="目标目录路径"),
auth: AuthSchema = Depends(AuthPermission(permissions=["resource:file:upload"]))
auth: AuthSchema = Depends(AuthPermission(permissions=["monitor:resource:upload"]))
) -> JSONResponse:
"""上传文件"""
result_dict = await ResourceService.upload_file_service(
@@ -78,11 +76,11 @@ async def upload_file_controller(
return SuccessResponse(data=result_dict, msg="上传文件成功")
@ResourceFileRouter.get("/download", summary="下载文件", description="下载指定文件")
@ResourceRouter.get("/download", summary="下载文件", description="下载指定文件")
async def download_file_controller(
request: Request,
path: str = Query(..., description="文件路径"),
auth: AuthSchema = Depends(AuthPermission(permissions=["resource:file:download"]))
auth: AuthSchema = Depends(AuthPermission(permissions=["monitor:resource:download"]))
) -> FileResponse:
"""下载文件"""
file_path = await ResourceService.download_file_service(
@@ -103,10 +101,10 @@ async def download_file_controller(
)
@ResourceFileRouter.delete("/delete", summary="删除文件", description="删除指定文件或目录")
@ResourceRouter.delete("/delete", summary="删除文件", description="删除指定文件或目录")
async def delete_files_controller(
paths: List[str] = Body(..., description="文件路径列表"),
auth: AuthSchema = Depends(AuthPermission(permissions=["resource:file:delete"]))
auth: AuthSchema = Depends(AuthPermission(permissions=["monitor:resource:delete"]))
) -> JSONResponse:
"""删除文件"""
await ResourceService.delete_file_service(auth=auth, paths=paths)
@@ -114,10 +112,10 @@ async def delete_files_controller(
return SuccessResponse(msg="删除文件成功")
@ResourceFileRouter.post("/move", summary="移动文件", description="移动文件或目录")
@ResourceRouter.post("/move", summary="移动文件", description="移动文件或目录")
async def move_file_controller(
data: ResourceMoveSchema,
auth: AuthSchema = Depends(AuthPermission(permissions=["resource:file:move"]))
auth: AuthSchema = Depends(AuthPermission(permissions=["monitor:resource:move"]))
) -> JSONResponse:
"""移动文件"""
await ResourceService.move_file_service(auth=auth, data=data)
@@ -125,10 +123,10 @@ async def move_file_controller(
return SuccessResponse(msg="移动文件成功")
@ResourceFileRouter.post("/copy", summary="复制文件", description="复制文件或目录")
@ResourceRouter.post("/copy", summary="复制文件", description="复制文件或目录")
async def copy_file_controller(
data: ResourceCopySchema,
auth: AuthSchema = Depends(AuthPermission(permissions=["resource:file:copy"]))
auth: AuthSchema = Depends(AuthPermission(permissions=["monitor:resource:copy"]))
) -> JSONResponse:
"""复制文件"""
await ResourceService.copy_file_service(auth=auth, data=data)
@@ -136,10 +134,10 @@ async def copy_file_controller(
return SuccessResponse(msg="复制文件成功")
@ResourceFileRouter.post("/rename", summary="重命名文件", description="重命名文件或目录")
@ResourceRouter.post("/rename", summary="重命名文件", description="重命名文件或目录")
async def rename_file_controller(
data: ResourceRenameSchema,
auth: AuthSchema = Depends(AuthPermission(permissions=["resource:file:rename"]))
auth: AuthSchema = Depends(AuthPermission(permissions=["rmonitor:resource:rename"]))
) -> JSONResponse:
"""重命名文件"""
await ResourceService.rename_file_service(auth=auth, data=data)
@@ -147,10 +145,10 @@ async def rename_file_controller(
return SuccessResponse(msg="重命名文件成功")
@ResourceFileRouter.post("/create-dir", summary="创建目录", description="在指定路径创建新目录")
@ResourceRouter.post("/create-dir", summary="创建目录", description="在指定路径创建新目录")
async def create_directory_controller(
data: ResourceCreateDirSchema,
auth: AuthSchema = Depends(AuthPermission(permissions=["resource:file:create_dir"]))
auth: AuthSchema = Depends(AuthPermission(permissions=["monitor:resource:create_dir"]))
) -> JSONResponse:
"""创建目录"""
await ResourceService.create_directory_service(auth=auth, data=data)
@@ -158,10 +156,10 @@ async def create_directory_controller(
return SuccessResponse(msg="创建目录成功")
@ResourceFileRouter.get("/stats", summary="获取资源统计", description="获取资源统计信息")
@ResourceRouter.get("/stats", summary="获取资源统计", description="获取资源统计信息")
async def get_resource_stats_controller(
request: Request,
auth: AuthSchema = Depends(AuthPermission(permissions=["resource:stats:query"]))
auth: AuthSchema = Depends(AuthPermission(permissions=["monitor:resource:query"]))
) -> JSONResponse:
"""获取资源统计"""
result_dict = await ResourceService.get_stats_service(
@@ -172,11 +170,11 @@ async def get_resource_stats_controller(
return SuccessResponse(data=result_dict, msg="获取资源统计成功")
@ResourceFileRouter.post("/export", summary="导出资源列表", description="导出资源列表")
@ResourceRouter.post("/export", summary="导出资源列表", description="导出资源列表")
async def export_resource_list_controller(
request: Request,
search: ResourceSearchSchema,
auth: AuthSchema = Depends(AuthPermission(permissions=["resource:file:export"]))
auth: AuthSchema = Depends(AuthPermission(permissions=["monitor:resource:export"]))
) -> StreamingResponse:
"""导出资源列表"""
# 获取搜索结果
@@ -1,11 +0,0 @@
# -*- coding: utf-8 -*-
from fastapi import APIRouter
from .resource.controller import ResourceFileRouter
ResourceRouter = APIRouter(prefix="/resource")
# 包含所有子路由
ResourceRouter.include_router(ResourceFileRouter)
+1 -7
View File
@@ -11,13 +11,9 @@ from .dict.controller import DictRouter
from .params.controller import ParamsRouter
from .notice.controller import NoticeRouter
from .log.controller import LogRouter
from .version.controller import VersionRouter
from .ticket.controller import TicketRouter
SystemRouter = APIRouter(prefix="/system")
# 包含所有子路由
SystemRouter.include_router(AuthRouter)
SystemRouter.include_router(UserRouter)
SystemRouter.include_router(RoleRouter)
@@ -27,6 +23,4 @@ SystemRouter.include_router(PositionRouter)
SystemRouter.include_router(DictRouter)
SystemRouter.include_router(ParamsRouter)
SystemRouter.include_router(NoticeRouter)
SystemRouter.include_router(LogRouter)
SystemRouter.include_router(VersionRouter)
SystemRouter.include_router(TicketRouter)
SystemRouter.include_router(LogRouter)
@@ -161,8 +161,6 @@ class ParamsService:
@classmethod
async def upload_service(cls, base_url: str, file: UploadFile) -> Dict:
"""上传文件"""
if not file:
raise CustomException(msg="请选择要上传的文件")
filename, filepath, file_url = await UploadUtil.upload_file(file=file, base_url=base_url)
return UploadResponseSchema(
@@ -1,71 +0,0 @@
# -*- coding: utf-8 -*-
from fastapi import APIRouter, Body, Depends, Path
from fastapi.responses import JSONResponse
from app.common.response import SuccessResponse
from app.common.request import PaginationService
from app.core.router_class import OperationLogRoute
from app.core.base_params import PaginationQueryParam
from app.core.dependencies import AuthPermission
from app.core.logger import logger
from app.api.v1.module_system.auth.schema import AuthSchema
from .param import TicketQueryParam
from .service import TicketService
from .schema import TicketCreateSchema, TicketUpdateSchema
TicketRouter = APIRouter(route_class=OperationLogRoute, prefix="/ticket", tags=["工单管理"])
@TicketRouter.get("/detail/{id}", summary="获取工单详情", description="获取工单详情")
async def get_ticket_detail_controller(
id: int = Path(..., description="工单ID"),
auth: AuthSchema = Depends(AuthPermission(permissions=["system:ticket:query"]))
) -> JSONResponse:
result_dict = await TicketService.get_ticket_detail_service(auth=auth, id=id)
logger.info(f"获取工单详情成功 {id}")
return SuccessResponse(data=result_dict, msg="获取工单详情成功")
@TicketRouter.get("/list", summary="查询工单列表", description="查询工单列表")
async def get_ticket_list_controller(
page: PaginationQueryParam = Depends(),
search: TicketQueryParam = Depends(),
auth: AuthSchema = Depends(AuthPermission(permissions=["system:ticket:query"]))
) -> JSONResponse:
result_dict_list = await TicketService.get_ticket_list_service(auth=auth, search=search, order_by=page.order_by)
result_dict = await PaginationService.paginate(data_list=result_dict_list, page_no=page.page_no, page_size=page.page_size)
logger.info("查询工单列表成功")
return SuccessResponse(data=result_dict, msg="查询工单列表成功")
@TicketRouter.post("/create", summary="创建工单", description="创建工单")
async def create_ticket_controller(
data: TicketCreateSchema,
auth: AuthSchema = Depends(AuthPermission(permissions=["system:ticket:create"]))
) -> JSONResponse:
result_dict = await TicketService.create_ticket_service(auth=auth, data=data)
logger.info(f"创建工单成功: {result_dict}")
return SuccessResponse(data=result_dict, msg="创建工单成功")
@TicketRouter.put("/update/{id}", summary="修改工单", description="修改工单")
async def update_ticket_controller(
data: TicketUpdateSchema,
id: int = Path(..., description="工单ID"),
auth: AuthSchema = Depends(AuthPermission(permissions=["system:ticket:update"]))
) -> JSONResponse:
result_dict = await TicketService.update_ticket_service(auth=auth, id=id, data=data)
logger.info(f"修改工单成功: {result_dict}")
return SuccessResponse(data=result_dict, msg="修改工单成功")
@TicketRouter.delete("/delete", summary="删除工单", description="删除工单")
async def delete_ticket_controller(
ids: list[int] = Body(..., description="ID列表"),
auth: AuthSchema = Depends(AuthPermission(permissions=["system:ticket:delete"]))
) -> JSONResponse:
await TicketService.delete_ticket_service(auth=auth, ids=ids)
logger.info(f"删除工单成功: {ids}")
return SuccessResponse(msg="删除工单成功")
@@ -1,56 +0,0 @@
from typing import Dict, List, Optional, Sequence
from app.core.base_crud import CRUDBase
from app.api.v1.module_system.auth.schema import AuthSchema
from .model import TicketModel
from .schema import TicketCreateSchema, TicketUpdateSchema
class TicketCRUD(CRUDBase[TicketModel, TicketCreateSchema, TicketUpdateSchema]):
"""工单 CRUD 操作"""
def __init__(self, auth: AuthSchema) -> None:
"""初始化工单CRUD"""
self.auth = auth
super().__init__(model=TicketModel, auth=auth)
async def get_by_id_crud(self, id: int) -> Optional[TicketModel]:
"""根据id获取工单信息
:param id: 工单ID
:return: 工单信息
"""
return await self.get(id=id)
async def get_list_crud(self, search: Optional[Dict] = None, order_by: Optional[List[Dict[str, str]]] = None) -> Sequence[TicketModel]:
"""获取工单列表
:param search: 搜索条件
:param order_by: 排序字段
:return: 工单列表
"""
return await self.list(search=search or {}, order_by=order_by or [])
async def create_crud(self, data: TicketCreateSchema) -> Optional[TicketModel]:
"""创建工单
:param data: 工单创建数据
:return: 创建的工单
"""
return await self.create(data=data)
async def update_crud(self, id: int, data: TicketUpdateSchema) -> Optional[TicketModel]:
"""更新工单
:param id: 工单ID
:param data: 工单更新数据
:return: 更新的工单
"""
return await self.update(id=id, data=data)
async def delete_crud(self, ids: List[int]) -> None:
"""批量删除工单
:param ids: 工单ID列表
"""
return await self.delete(ids=ids)
@@ -1,30 +0,0 @@
# -*- coding: utf-8 -*-
from typing import Optional
from sqlalchemy import String, Text, DateTime, ForeignKey
from sqlalchemy.orm import Mapped, mapped_column, relationship
from datetime import datetime
from app.core.base_model import CreatorMixin
class TicketModel(CreatorMixin):
"""
工单模型 - SQLAlchemy 2.0 语法
兼容 MySQL 和 PostgreSQL
"""
__tablename__ = 'system_ticket'
__table_args__ = ({'comment': '工单表'})
title: Mapped[str] = mapped_column(String(255), nullable=False, comment='工单标题')
status: Mapped[str] = mapped_column(String(50), default='pending', comment='工单状态(pending:待处理 progress:处理中 resolved:已解决 closed:已关闭)')
priority: Mapped[str] = mapped_column(String(50), default='medium', comment='优先级(low:低, medium:中, high:高, urgent:紧急)')
type: Mapped[Optional[str]] = mapped_column(String(50), comment='工单类型(bug:缺陷, feature:功能, task:任务)')
assignee_id: Mapped[Optional[int]] = mapped_column(ForeignKey('system_users.id'), comment='指派给用户ID')
reporter_id: Mapped[Optional[int]] = mapped_column(ForeignKey('system_users.id'), comment='报告人ID')
project: Mapped[Optional[str]] = mapped_column(String(100), comment='所属项目或模块')
version: Mapped[Optional[str]] = mapped_column(String(50), comment='版本号')
# 关系
assignee = relationship('UserModel', foreign_keys=[assignee_id])
reporter = relationship('UserModel', foreign_keys=[reporter_id])
@@ -1,36 +0,0 @@
# -*- coding: utf-8 -*-
from datetime import datetime
from typing import Optional
from fastapi import Query
from app.core.validator import DateTimeStr
class TicketQueryParam:
"""工单管理查询参数"""
def __init__(
self,
title: Optional[str] = Query(None, description="工单标题"),
status: Optional[bool] = Query(None, description="工单状态"),
priority: Optional[str] = Query(None, description="优先级"),
creator: Optional[int] = Query(None, description="创建人"),
start_time: Optional[DateTimeStr] = Query(None, description="开始时间", example="2023-01-01 00:00:00"),
end_time: Optional[DateTimeStr] = Query(None, description="结束时间", example="2023-12-31 23:59:59"),
) -> None:
super().__init__()
# 模糊查询字段
self.title = ("like", title)
# 精确查询字段
self.creator_id = creator
self.status = status
self.priority = priority
# 时间范围查询
if start_time and end_time:
start_datetime = datetime.strptime(str(start_time), '%Y-%m-%d %H:%M:%S')
end_datetime = datetime.strptime(str(end_time), '%Y-%m-%d %H:%M:%S')
self.created_at = ("between", (start_datetime, end_datetime))
@@ -1,31 +0,0 @@
# -*- coding: utf-8 -*-
from typing import Optional
from pydantic import BaseModel, ConfigDict, Field
from datetime import datetime
from app.core.base_schema import BaseSchema
class TicketCreateSchema(BaseModel):
"""创建工单"""
title: str = Field(..., max_length=255, description='工单标题')
description: Optional[str] = Field(default=None, description='工单描述')
status: Optional[str] = Field(default='pending', description='工单处理状态(pending:待处理 progress:处理中 resolved:已解决 closed:已关闭)')
priority: Optional[str] = Field(default='medium', description='优先级(low, medium, high, urgent)')
type: Optional[str] = Field(default='bug', description='工单类型(bug, feature, task)')
assignee_id: Optional[int] = Field(default=None, description='指派给用户ID')
reporter_id: int = Field(..., description='报告人ID')
project: Optional[str] = Field(default=None, max_length=100, description='所属项目')
version: Optional[str] = Field(default=None, max_length=50, description='版本号')
class TicketUpdateSchema(TicketCreateSchema):
"""更新工单"""
...
class TicketOutSchema(TicketCreateSchema, BaseSchema):
"""工单输出"""
model_config = ConfigDict(from_attributes=True)
...
@@ -1,60 +0,0 @@
# -*- coding: utf-8 -*-
from typing import Dict, List, Optional, Union
from app.api.v1.module_system.auth.schema import AuthSchema
from app.core.exceptions import CustomException
from .crud import TicketCRUD
from .schema import TicketCreateSchema, TicketUpdateSchema, TicketOutSchema
from .param import TicketQueryParam
class TicketService:
"""工单模块服务层"""
@classmethod
async def get_ticket_detail_service(cls, auth: AuthSchema, id: int) -> Dict:
"""获取工单详情"""
obj = await TicketCRUD(auth).get_by_id_crud(id=id)
return TicketOutSchema.model_validate(obj).model_dump()
@classmethod
async def get_ticket_list_service(cls, auth: AuthSchema, search: Optional[TicketQueryParam] = None, order_by: Optional[Union[str, List[Dict[str, str]]]] = None) -> List[Dict]:
"""获取工单列表"""
# 处理排序参数
processed_order_by = None
if order_by:
if isinstance(order_by, str):
processed_order_by = eval(order_by)
else:
processed_order_by = order_by
obj_list = await TicketCRUD(auth).get_list_crud(search=search.__dict__ if search else None, order_by=processed_order_by)
return [TicketOutSchema.model_validate(obj).model_dump() for obj in obj_list]
@classmethod
async def create_ticket_service(cls, auth: AuthSchema, data: TicketCreateSchema) -> Dict:
"""创建工单"""
obj = await TicketCRUD(auth).create_crud(data=data)
return TicketOutSchema.model_validate(obj).model_dump()
@classmethod
async def update_ticket_service(cls, auth: AuthSchema, id: int, data: TicketUpdateSchema) -> Dict:
"""更新工单"""
# 检查工单是否存在
obj = await TicketCRUD(auth).get_by_id_crud(id=id)
if not obj:
raise CustomException(msg='更新失败,该工单不存在')
obj = await TicketCRUD(auth).update_crud(id=id, data=data)
return TicketOutSchema.model_validate(obj).model_dump()
@classmethod
async def delete_ticket_service(cls, auth: AuthSchema, ids: List[int]) -> None:
"""删除工单"""
if len(ids) < 1:
raise CustomException(msg='删除失败,删除对象不能为空')
for id in ids:
obj = await TicketCRUD(auth).get_by_id_crud(id=id)
if not obj:
raise CustomException(msg='删除失败,该工单不存在')
await TicketCRUD(auth).delete_crud(ids=ids)
@@ -1,71 +0,0 @@
# -*- coding: utf-8 -*-
from fastapi import APIRouter, Body, Depends, Path
from fastapi.responses import JSONResponse
from app.common.response import SuccessResponse
from app.common.request import PaginationService
from app.core.router_class import OperationLogRoute
from app.core.base_params import PaginationQueryParam
from app.core.dependencies import AuthPermission
from app.core.logger import logger
from app.api.v1.module_system.auth.schema import AuthSchema
from .param import VersionQueryParam
from .service import VersionService
from .schema import VersionCreateSchema, VersionUpdateSchema
VersionRouter = APIRouter(route_class=OperationLogRoute, prefix="/version", tags=["版本管理"])
@VersionRouter.get("/detail/{id}", summary="获取版本详情", description="获取版本详情")
async def get_version_detail_controller(
id: int = Path(..., description="版本ID"),
auth: AuthSchema = Depends(AuthPermission(permissions=["system:version:query"]))
) -> JSONResponse:
result_dict = await VersionService.get_version_detail_service(auth=auth, id=id)
logger.info(f"获取版本详情成功 {id}")
return SuccessResponse(data=result_dict, msg="获取版本详情成功")
@VersionRouter.get("/list", summary="查询版本列表", description="查询版本列表")
async def get_version_list_controller(
page: PaginationQueryParam = Depends(),
search: VersionQueryParam = Depends(),
auth: AuthSchema = Depends(AuthPermission(permissions=["system:version:query"]))
) -> JSONResponse:
result_dict_list = await VersionService.get_version_list_service(auth=auth, search=search, order_by=page.order_by)
result_dict = await PaginationService.paginate(data_list=result_dict_list, page_no=page.page_no, page_size=page.page_size)
logger.info("查询版本列表成功")
return SuccessResponse(data=result_dict, msg="查询版本列表成功")
@VersionRouter.post("/create", summary="创建版本", description="创建版本")
async def create_version_controller(
data: VersionCreateSchema,
auth: AuthSchema = Depends(AuthPermission(permissions=["system:version:create"]))
) -> JSONResponse:
result_dict = await VersionService.create_version_service(auth=auth, data=data)
logger.info(f"创建版本成功: {result_dict}")
return SuccessResponse(data=result_dict, msg="创建版本成功")
@VersionRouter.put("/update/{id}", summary="修改版本", description="修改版本")
async def update_version_controller(
data: VersionUpdateSchema,
id: int = Path(..., description="版本ID"),
auth: AuthSchema = Depends(AuthPermission(permissions=["system:version:update"]))
) -> JSONResponse:
result_dict = await VersionService.update_version_service(auth=auth, id=id, data=data)
logger.info(f"修改版本成功: {result_dict}")
return SuccessResponse(data=result_dict, msg="修改版本成功")
@VersionRouter.delete("/delete", summary="删除版本", description="删除版本")
async def delete_version_controller(
ids: list[int] = Body(..., description="ID列表"),
auth: AuthSchema = Depends(AuthPermission(permissions=["system:version:delete"]))
) -> JSONResponse:
await VersionService.delete_version_service(auth=auth, ids=ids)
logger.info(f"删除版本成功: {ids}")
return SuccessResponse(msg="删除版本成功")
@@ -1,96 +0,0 @@
# -*- coding: utf-8 -*-
from typing import Dict, List, Optional, Sequence
from datetime import datetime
from app.core.base_crud import CRUDBase
from app.api.v1.module_system.auth.schema import AuthSchema
from .model import VersionModel
from .schema import VersionCreateSchema, VersionUpdateSchema
class VersionCRUD(CRUDBase[VersionModel, VersionCreateSchema, VersionUpdateSchema]):
"""版本模块数据层"""
def __init__(self, auth: AuthSchema) -> None:
"""初始化版本CRUD"""
self.auth = auth
super().__init__(model=VersionModel, auth=auth)
async def get_by_id_crud(self, id: int) -> Optional[VersionModel]:
"""根据id获取版本信息
:param id: 版本ID
:return: 版本信息
"""
return await self.get(id=id)
async def get_list_crud(self, search: Optional[Dict] = None, order_by: Optional[List[Dict[str, str]]] = None) -> Sequence[VersionModel]:
"""获取版本列表
:param search: 搜索条件
:param order_by: 排序字段
:return: 版本列表
"""
return await self.list(search=search or {}, order_by=order_by or [])
async def create_crud(self, data: VersionCreateSchema) -> Optional[VersionModel]:
"""创建版本
:param data: 版本创建数据
:return: 创建的版本
"""
# 处理 released_at 字段,确保字符串转换为 datetime 对象
data_dict = data.model_dump()
if data_dict.get('released_at') and isinstance(data_dict['released_at'], str):
try:
data_dict['released_at'] = datetime.strptime(data_dict['released_at'], '%Y-%m-%d %H:%M:%S')
except ValueError:
# 如果格式不正确,让数据库层处理错误
pass
return await self.create(data=data_dict)
async def update_crud(self, id: int, data: VersionUpdateSchema) -> Optional[VersionModel]:
"""更新版本
:param id: 版本ID
:param data: 版本更新数据
:return: 更新的版本
"""
# 处理 released_at 字段,确保字符串转换为 datetime 对象
data_dict = data.model_dump(exclude_unset=True, exclude={"id"})
if data_dict.get('released_at') and isinstance(data_dict['released_at'], str):
try:
data_dict['released_at'] = datetime.strptime(data_dict['released_at'], '%Y-%m-%d %H:%M:%S')
except ValueError:
# 如果格式不正确,让数据库层处理错误
pass
return await self.update(id=id, data=data_dict)
async def delete_crud(self, ids: List[int]) -> None:
"""批量删除版本
:param ids: 版本ID列表
"""
return await self.delete(ids=ids)
async def get_by_number_crud(self, version_number: str) -> Optional[VersionModel]:
"""根据版本号获取版本
:param version_number: 版本号
:return: 版本信息
"""
return await self.get(version_number=version_number)
async def get_by_status_crud(self, search: Optional[Dict] = None, order_by: Optional[List[Dict[str, str]]] = None) -> Sequence[VersionModel]:
"""根据状态获取版本列表
:param search: 搜索条件
:param order_by: 排序字段
:return: 版本列表
"""
if search is None:
search = {}
return await self.list(search=search, order_by=order_by or [])
@@ -1,24 +0,0 @@
# -*- coding: utf-8 -*-
from typing import Optional
from sqlalchemy import String, Text, DateTime
from sqlalchemy.orm import Mapped, mapped_column
from datetime import datetime
from app.core.base_model import CreatorMixin
class VersionModel(CreatorMixin):
"""
版本模型 - SQLAlchemy 2.0 语法
兼容 MySQL PostgreSQL
"""
__tablename__ = 'system_version'
__table_args__ = ({'comment': '版本表'})
version_number: Mapped[str] = mapped_column(String(50), nullable=False, comment='版本号')
title: Mapped[str] = mapped_column(String(255), nullable=False, comment='版本标题')
release_notes: Mapped[Optional[str]] = mapped_column(Text, comment='发布说明')
status: Mapped[str] = mapped_column(String(50), default='draft', comment='版本状态(draft: 草稿, released: 已发布, archived: 已归档)')
project: Mapped[Optional[str]] = mapped_column(String(100), comment='所属项目')
released_at: Mapped[Optional[datetime]] = mapped_column(DateTime, comment='发布时间')
@@ -1,34 +0,0 @@
# -*- coding: utf-8 -*-
from datetime import datetime
from typing import Optional
from fastapi import Query
from app.core.validator import DateTimeStr
class VersionQueryParam:
"""版本管理查询参数"""
def __init__(
self,
title: Optional[str] = Query(None, description="版本标题"),
status: Optional[str] = Query(None, description="版本状态"),
creator: Optional[int] = Query(None, description="创建人"),
start_time: Optional[DateTimeStr] = Query(None, description="开始时间", example="2023-01-01 00:00:00"),
end_time: Optional[DateTimeStr] = Query(None, description="结束时间", example="2023-12-31 23:59:59"),
) -> None:
super().__init__()
# 模糊查询字段
self.title = ("like", title)
# 精确查询字段
self.creator_id = creator
self.status = status
# 时间范围查询
if start_time and end_time:
start_datetime = datetime.strptime(str(start_time), '%Y-%m-%d %H:%M:%S')
end_datetime = datetime.strptime(str(end_time), '%Y-%m-%d %H:%M:%S')
self.created_at = ("between", (start_datetime, end_datetime))
@@ -1,30 +0,0 @@
# -*- coding: utf-8 -*-
from typing import Optional
from pydantic import BaseModel, ConfigDict, Field
from datetime import datetime
from app.core.base_schema import BaseSchema
from app.core.validator import DateTimeStr
class VersionCreateSchema(BaseModel):
"""创建版本"""
version_number: str = Field(..., max_length=50, description='版本号')
title: str = Field(..., max_length=255, description='版本标题')
release_notes: Optional[str] = Field(default=None, description='发布说明')
description: Optional[str] = Field(default=None, description='工单描述')
status: Optional[str] = Field(default='draft', description='版本状态(draft: 草稿, released: 已发布, archived: 已归档)')
project: Optional[str] = Field(default=None, max_length=100, description='所属项目')
released_at: Optional[DateTimeStr] = Field(default=None, description='发布时间')
class VersionUpdateSchema(VersionCreateSchema):
"""更新版本"""
...
class VersionOutSchema(VersionCreateSchema, BaseSchema):
"""版本输出"""
model_config = ConfigDict(from_attributes=True)
...
@@ -1,87 +0,0 @@
# -*- coding: utf-8 -*-
from typing import Dict, List, Optional, Union
from app.api.v1.module_system.auth.schema import AuthSchema
from app.core.exceptions import CustomException
from .crud import VersionCRUD
from .schema import VersionCreateSchema, VersionUpdateSchema, VersionOutSchema
from .param import VersionQueryParam
class VersionService:
"""版本模块服务层"""
@classmethod
async def get_version_detail_service(cls, auth: AuthSchema, id: int) -> Dict:
"""获取版本详情"""
obj = await VersionCRUD(auth).get_by_id_crud(id=id)
return VersionOutSchema.model_validate(obj).model_dump()
@classmethod
async def get_version_list_service(cls, auth: AuthSchema, search: Optional[VersionQueryParam] = None, order_by: Optional[Union[str, List[Dict[str, str]]]] = None) -> List[Dict]:
"""获取版本列表"""
# 处理排序参数
processed_order_by = None
if order_by:
if isinstance(order_by, str):
processed_order_by = eval(order_by)
else:
processed_order_by = order_by
obj_list = await VersionCRUD(auth).get_list_crud(search=search.__dict__ if search else None, order_by=processed_order_by)
return [VersionOutSchema.model_validate(obj).model_dump() for obj in obj_list]
@classmethod
async def create_version_service(cls, auth: AuthSchema, data: VersionCreateSchema) -> Dict:
"""创建版本"""
# 检查版本号是否已存在
exist_obj = await VersionCRUD(auth).get_by_number_crud(version_number=data.version_number)
if exist_obj:
raise CustomException(msg='创建失败,版本号已存在')
obj = await VersionCRUD(auth).create_crud(data=data)
return VersionOutSchema.model_validate(obj).model_dump()
@classmethod
async def update_version_service(cls, auth: AuthSchema, id: int, data: VersionUpdateSchema) -> Dict:
"""更新版本"""
# 检查版本是否存在
obj = await VersionCRUD(auth).get_by_id_crud(id=id)
if not obj:
raise CustomException(msg='更新失败,该版本不存在')
# 检查版本号是否重复(如果提供了版本号)
if data.version_number:
exist_obj = await VersionCRUD(auth).get_by_number_crud(version_number=data.version_number)
if exist_obj and exist_obj.id != id:
raise CustomException(msg='更新失败,版本号已存在')
obj = await VersionCRUD(auth).update_crud(id=id, data=data)
return VersionOutSchema.model_validate(obj).model_dump()
@classmethod
async def delete_version_service(cls, auth: AuthSchema, ids: List[int]) -> None:
"""删除版本"""
if len(ids) < 1:
raise CustomException(msg='删除失败,删除对象不能为空')
for id in ids:
obj = await VersionCRUD(auth).get_by_id_crud(id=id)
if not obj:
raise CustomException(msg='删除失败,该版本不存在')
await VersionCRUD(auth).delete_crud(ids=ids)
@classmethod
async def get_version_by_status_service(cls, auth: AuthSchema, search: Optional[VersionQueryParam] = None, order_by: Optional[Union[str, List[Dict[str, str]]]] = None) -> List[Dict]:
"""根据状态获取版本列表"""
# 处理排序参数
processed_order_by = None
if order_by:
if isinstance(order_by, str):
processed_order_by = eval(order_by)
else:
processed_order_by = order_by
obj_list = await VersionCRUD(auth).get_by_status_crud(
search=search.__dict__ if search else None,
order_by=processed_order_by
)
return [VersionOutSchema.model_validate(obj).model_dump() for obj in obj_list]