refactor: 重构代码结构并优化命名一致性

feat(backend): 新增模块示例和路径配置
fix(backend): 修复菜单模型名称唯一性约束
refactor(backend): 重构日志模块和路径配置
style(backend): 统一API方法命名规范
refactor(frontend): 重构API方法命名规范
fix(frontend): 修复权限校验问题
refactor: 移动定时任务测试文件到正确模块
docs: 更新模板文件路径
refactor: 优化日志记录和错误处理
fix: 修复排序默认值问题
This commit is contained in:
zhangtao
2025-11-19 02:24:16 +08:00
parent fce6333722
commit 279f547f84
132 changed files with 1670 additions and 1106 deletions
@@ -8,7 +8,7 @@ from app.common.request import PaginationService
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
from app.core.logger import log
from app.api.v1.module_system.auth.schema import AuthSchema
from .param import McpQueryParam
@@ -34,7 +34,7 @@ async def chat_controller(
- StreamingResponse: 流式响应,每次返回一个聊天响应
"""
user_name = auth.user.name if auth.user else "未知用户"
logger.info(f"用户 {user_name} 发起智能对话: {query.message[:50]}...")
log.info(f"用户 {user_name} 发起智能对话: {query.message[:50]}...")
async def generate_response():
try:
@@ -43,7 +43,7 @@ async def chat_controller(
if chunk:
yield chunk.encode('utf-8') if isinstance(chunk, str) else chunk
except Exception as e:
logger.error(f"流式响应出错: {str(e)}")
log.error(f"流式响应出错: {str(e)}")
yield f"抱歉,处理您的请求时出现了错误: {str(e)}".encode('utf-8')
return StreamResponse(generate_response(), media_type="text/plain; charset=utf-8")
@@ -64,7 +64,7 @@ async def detail_controller(
- JSONResponse: 包含 MCP 服务器详情的 JSON 响应
"""
result_dict = await McpService.detail_service(auth=auth, id=id)
logger.info(f"获取 MCP 服务器详情成功 {id}")
log.info(f"获取 MCP 服务器详情成功 {id}")
return SuccessResponse(data=result_dict, msg="获取 MCP 服务器详情成功")
@@ -87,7 +87,7 @@ async def list_controller(
"""
result_dict_list = await McpService.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(f"查询 MCP 服务器列表成功")
log.info(f"查询 MCP 服务器列表成功")
return SuccessResponse(data=result_dict, msg="查询 MCP 服务器列表成功")
@@ -107,7 +107,7 @@ async def create_controller(
- JSONResponse: 包含创建 MCP 服务器结果的 JSON 响应
"""
result_dict = await McpService.create_service(auth=auth, data=data)
logger.info(f"创建 MCP 服务器成功: {result_dict}")
log.info(f"创建 MCP 服务器成功: {result_dict}")
return SuccessResponse(data=result_dict, msg="创建 MCP 服务器成功")
@@ -129,7 +129,7 @@ async def update_controller(
- JSONResponse: 包含修改 MCP 服务器结果的 JSON 响应
"""
result_dict = await McpService.update_service(auth=auth, id=id, data=data)
logger.info(f"修改 MCP 服务器成功: {result_dict}")
log.info(f"修改 MCP 服务器成功: {result_dict}")
return SuccessResponse(data=result_dict, msg="修改 MCP 服务器成功")
@@ -149,7 +149,7 @@ async def delete_controller(
- JSONResponse: 包含删除 MCP 服务器结果的 JSON 响应
"""
await McpService.delete_service(auth=auth, ids=ids)
logger.info(f"删除 MCP 服务器成功: {ids}")
log.info(f"删除 MCP 服务器成功: {ids}")
return SuccessResponse(msg="删除 MCP 服务器成功")
@@ -172,9 +172,9 @@ async def websocket_chat_controller(
if chunk:
await websocket.send_text(chunk)
except Exception as e:
logger.error(f"处理聊天查询出错: {str(e)}")
log.error(f"处理聊天查询出错: {str(e)}")
await websocket.send_text(f"抱歉,处理您的请求时出现了错误: {str(e)}")
except Exception as e:
logger.error(f"WebSocket聊天出错: {str(e)}")
log.error(f"WebSocket聊天出错: {str(e)}")
finally:
await websocket.close()
@@ -3,9 +3,9 @@
from typing import List, Dict, Optional, Any
from app.core.exceptions import CustomException
from app.utils.ai_util import AIClient
from app.api.v1.module_system.auth.schema import AuthSchema
from .tools.ai_util import AIClient
from .schema import McpCreateSchema, McpUpdateSchema, McpOutSchema, ChatQuerySchema
from .param import McpQueryParam
from .crud import McpCRUD
@@ -0,0 +1,116 @@
# -*- coding: utf-8 -*-
from typing import Any, AsyncGenerator
from openai import AsyncOpenAI, OpenAI
from openai.types.chat.chat_completion import ChatCompletion
import httpx
from app.config.setting import settings
from app.core.logger import log
class AIClient:
"""
AI客户端类,用于与OpenAI API交互。
"""
def __init__(self):
self.model = settings.OPENAI_MODEL
# 创建一个不带冲突参数的httpx客户端
self.http_client = httpx.AsyncClient(
timeout=30.0,
follow_redirects=True
)
# 使用自定义的http客户端
self.client = AsyncOpenAI(
api_key=settings.OPENAI_API_KEY,
base_url=settings.OPENAI_BASE_URL,
http_client=self.http_client
)
def _friendly_error_message(self, e: Exception) -> str:
"""将 OpenAI 或网络异常转换为友好的中文提示。"""
# 尝试获取状态码与错误体
status_code = getattr(e, "status_code", None)
body = getattr(e, "body", None)
message = None
error_type = None
error_code = None
try:
if isinstance(body, dict) and "error" in body:
err = body.get("error") or {}
error_type = err.get("type")
error_code = err.get("code")
message = err.get("message")
except Exception:
# 忽略解析失败
pass
text = str(e)
msg = message or text
# 特定错误映射
# 欠费/账户状态异常
if (error_code == "Arrearage") or (error_type == "Arrearage") or ("in good standing" in (msg or "")):
return "账户欠费或结算异常,访问被拒绝。请检查账号状态或更换有效的 API Key。"
# 鉴权失败
if status_code == 401 or "invalid api key" in msg.lower():
return "鉴权失败,API Key 无效或已过期。请检查系统配置中的 API Key。"
# 权限不足或被拒绝
if status_code == 403 or error_type in {"PermissionDenied", "permission_denied"}:
return "访问被拒绝,权限不足或账号受限。请检查账户权限设置。"
# 配额不足或限流
if status_code == 429 or error_type in {"insufficient_quota", "rate_limit_exceeded"}:
return "请求过于频繁或配额已用尽。请稍后重试或提升账户配额。"
# 客户端错误
if status_code == 400:
return f"请求参数错误或服务拒绝:{message or '请检查输入内容。'}"
# 服务端错误
if status_code in {500, 502, 503, 504}:
return "服务暂时不可用,请稍后重试。"
# 默认兜底
return f"处理您的请求时出现错误:{msg}"
async def process(self, query: str) -> AsyncGenerator[str, None]:
"""
处理查询并返回流式响应
参数:
- query (str): 用户查询。
返回:
- AsyncGenerator[str, None]: 流式响应内容。
"""
system_prompt = """你是一个有用的AI助手,可以帮助用户回答问题和提供帮助。请用中文回答用户的问题。"""
try:
# 使用 await 调用异步客户端
response = await self.client.chat.completions.create(
model=self.model,
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": query}
],
stream=True
)
# 流式返回响应
async for chunk in response:
if chunk.choices and chunk.choices[0].delta.content:
yield chunk.choices[0].delta.content
except Exception as e:
# 记录详细错误,返回友好提示
log.error(f"AI处理查询失败: {str(e)}")
yield self._friendly_error_message(e)
async def close(self) -> None:
"""
关闭客户端连接
"""
if hasattr(self, 'client'):
await self.client.close()
if hasattr(self, 'http_client'):
await self.http_client.aclose()
@@ -9,7 +9,7 @@ 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
from app.core.logger import log
from app.api.v1.module_system.auth.schema import AuthSchema
from .param import JobQueryParam, JobLogQueryParam
@@ -39,7 +39,7 @@ async def get_obj_detail_controller(
- JSONResponse: 包含定时任务详情的JSON响应
"""
result_dict = await JobService.get_job_detail_service(id=id, auth=auth)
logger.info(f"获取定时任务详情成功 {id}")
log.info(f"获取定时任务详情成功 {id}")
return SuccessResponse(data=result_dict, msg="获取定时任务详情成功")
@JobRouter.get("/list", summary="查询定时任务", description="查询定时任务")
@@ -61,7 +61,7 @@ async def get_obj_list_controller(
"""
result_dict_list = await JobService.get_job_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(f"查询定时任务列表成功")
log.info(f"查询定时任务列表成功")
return SuccessResponse(data=result_dict, msg="查询定时任务列表成功")
@JobRouter.post("/create", summary="创建定时任务", description="创建定时任务")
@@ -80,7 +80,7 @@ async def create_obj_controller(
- JSONResponse: 包含创建定时任务结果的JSON响应
"""
result_dict = await JobService.create_job_service(auth=auth, data=data)
logger.info(f"创建定时任务成功: {result_dict}")
log.info(f"创建定时任务成功: {result_dict}")
return SuccessResponse(data=result_dict, msg="创建定时任务成功")
@JobRouter.put("/update/{id}", summary="修改定时任务", description="修改定时任务")
@@ -101,7 +101,7 @@ async def update_obj_controller(
- JSONResponse: 包含修改定时任务结果的JSON响应
"""
result_dict = await JobService.update_job_service(auth=auth, id=id, data=data)
logger.info(f"修改定时任务成功: {result_dict}")
log.info(f"修改定时任务成功: {result_dict}")
return SuccessResponse(data=result_dict, msg="修改定时任务成功")
@JobRouter.delete("/delete", summary="删除定时任务", description="删除定时任务")
@@ -120,7 +120,7 @@ async def delete_obj_controller(
- JSONResponse: 包含删除定时任务结果的JSON响应
"""
await JobService.delete_job_service(auth=auth, ids=ids)
logger.info(f"删除定时任务成功: {ids}")
log.info(f"删除定时任务成功: {ids}")
return SuccessResponse(msg="删除定时任务成功")
@JobRouter.post('/export', summary="导出定时任务", description="导出定时任务")
@@ -140,7 +140,7 @@ async def export_obj_list_controller(
"""
result_dict_list = await JobService.get_job_list_service(search=search, auth=auth)
export_result = await JobService.export_job_service(data_list=result_dict_list)
logger.info('导出定时任务成功')
log.info('导出定时任务成功')
return StreamResponse(
data=bytes2file_response(export_result),
@@ -164,7 +164,7 @@ async def clear_obj_log_controller(
- JSONResponse: 包含清空定时任务结果的JSON响应
"""
await JobService.clear_job_service(auth=auth)
logger.info(f"清空定时任务成功")
log.info(f"清空定时任务成功")
return SuccessResponse(msg="清空定时任务成功")
@JobRouter.put("/option", summary="暂停/恢复/重启定时任务", description="暂停/恢复/重启定时任务")
@@ -185,7 +185,7 @@ async def option_obj_controller(
- JSONResponse: 包含操作定时任务结果的JSON响应
"""
await JobService.option_job_service(auth=auth, id=id, option=option)
logger.info(f"操作定时任务成功: {id}")
log.info(f"操作定时任务成功: {id}")
return SuccessResponse(msg="操作定时任务成功")
@JobRouter.get("/log", summary="获取定时任务日志", description="获取定时任务日志", dependencies=[Depends(AuthPermission(["module_application:job:query"]))])
@@ -235,7 +235,7 @@ async def get_job_log_detail_controller(
- JSONResponse: 获取定时任务日志详情的JSON响应
"""
result_dict = await JobLogService.get_job_log_detail_service(id=id, auth=auth)
logger.info(f"获取定时任务日志详情成功 {id}")
log.info(f"获取定时任务日志详情成功 {id}")
return SuccessResponse(data=result_dict, msg="获取定时任务日志详情成功")
@@ -259,7 +259,7 @@ async def get_job_log_list_controller(
order_by = [{"create_time": "desc"}]
result_dict_list = await JobLogService.get_job_log_list_service(auth=auth, search=search, order_by=order_by)
result_dict = await PaginationService.paginate(data_list=result_dict_list, page_no=page.page_no, page_size=page.page_size)
logger.info(f"查询定时任务日志列表成功")
log.info(f"查询定时任务日志列表成功")
return SuccessResponse(data=result_dict, msg="查询定时任务日志列表成功")
@@ -279,7 +279,7 @@ async def delete_job_log_controller(
- JSONResponse: 包含删除定时任务日志结果的JSON响应
"""
await JobLogService.delete_job_log_service(auth=auth, ids=ids)
logger.info(f"删除定时任务日志成功: {ids}")
log.info(f"删除定时任务日志成功: {ids}")
return SuccessResponse(msg="删除定时任务日志成功")
@@ -297,7 +297,7 @@ async def clear_job_log_controller(
- JSONResponse: 包含清空定时任务日志结果的JSON响应
"""
await JobLogService.clear_job_log_service(auth=auth)
logger.info(f"清空定时任务日志成功")
log.info(f"清空定时任务日志成功")
return SuccessResponse(msg="清空定时任务日志成功")
@@ -318,7 +318,7 @@ async def export_job_log_list_controller(
"""
result_dict_list = await JobLogService.get_job_log_list_service(search=search, auth=auth)
export_result = await JobLogService.export_job_log_service(data_list=result_dict_list)
logger.info('导出定时任务日志成功')
log.info('导出定时任务日志成功')
return StreamResponse(
data=bytes2file_response(export_result),
@@ -0,0 +1,39 @@
# -*- coding: utf-8 -*-
import time
from datetime import datetime
from app.core.logger import log
def job(*args, **kwargs) -> None:
"""
定时任务执行同步函数示例
参数:
- args: 位置参数。
- kwargs: 关键字参数。
"""
try:
print(f"开始执行任务: {args}-{kwargs}")
time.sleep(3)
print(f'{datetime.now()}同步函数执行完成')
except Exception as e:
log.error(f"同步任务执行失败: {e}")
raise
async def async_job(*args, **kwargs) -> None:
"""
定时任务执行异步函数示例
参数:
- args: 位置参数。
- kwargs: 关键字参数。
"""
try:
print(f"开始执行任务: {args}-{kwargs}")
time.sleep(3)
print(f'{datetime.now()}异步函数执行完成')
except Exception as e:
log.error(f"异步任务执行失败: {e}")
raise
@@ -9,7 +9,7 @@ from app.core.base_params import PaginationQueryParam
from app.core.dependencies import AuthPermission
from app.core.router_class import OperationLogRoute
from app.core.base_schema import BatchSetAvailable
from app.core.logger import logger
from app.core.logger import log
from app.api.v1.module_system.auth.schema import AuthSchema
from .param import ApplicationQueryParam
@@ -38,7 +38,7 @@ async def get_obj_detail_controller(
- JSONResponse: 包含应用详情的JSON响应
"""
result_dict = await ApplicationService.detail_service(id=id, auth=auth)
logger.info(f"获取应用详情成功 {id}")
log.info(f"获取应用详情成功 {id}")
return SuccessResponse(data=result_dict, msg="获取应用详情成功")
@MyAppRouter.get("/list", summary="查询应用列表", description="查询应用列表")
@@ -60,7 +60,7 @@ async def get_obj_list_controller(
"""
result_dict_list = await ApplicationService.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(f"查询应用列表成功")
log.info(f"查询应用列表成功")
return SuccessResponse(data=result_dict, msg="查询应用列表成功")
@MyAppRouter.post("/create", summary="创建应用", description="创建应用")
@@ -79,7 +79,7 @@ async def create_obj_controller(
- JSONResponse: 包含创建应用详情的JSON响应
"""
result_dict = await ApplicationService.create_service(auth=auth, data=data)
logger.info(f"创建应用成功: {result_dict}")
log.info(f"创建应用成功: {result_dict}")
return SuccessResponse(data=result_dict, msg="创建应用成功")
@MyAppRouter.put("/update/{id}", summary="修改应用", description="修改应用")
@@ -100,7 +100,7 @@ async def update_obj_controller(
- JSONResponse: 包含修改应用详情的JSON响应
"""
result_dict = await ApplicationService.update_service(auth=auth, id=id, data=data)
logger.info(f"修改应用成功: {result_dict}")
log.info(f"修改应用成功: {result_dict}")
return SuccessResponse(data=result_dict, msg="修改应用成功")
@MyAppRouter.delete("/delete", summary="删除应用", description="删除应用")
@@ -119,7 +119,7 @@ async def delete_obj_controller(
- JSONResponse: 包含删除应用详情的JSON响应
"""
await ApplicationService.delete_service(auth=auth, ids=ids)
logger.info(f"删除应用成功: {ids}")
log.info(f"删除应用成功: {ids}")
return SuccessResponse(msg="删除应用成功")
@MyAppRouter.patch("/available/setting", summary="批量修改应用状态", description="批量修改应用状态")
@@ -138,5 +138,5 @@ async def batch_set_available_obj_controller(
- JSONResponse: 批量修改应用状态成功
"""
await ApplicationService.set_available_service(auth=auth, data=data)
logger.info(f"批量修改应用状态成功: {data.ids}")
log.info(f"批量修改应用状态成功: {data.ids}")
return SuccessResponse(msg="批量修改应用状态成功")
@@ -6,7 +6,7 @@ from fastapi.responses import JSONResponse, FileResponse
from app.core.dependencies import AuthPermission
from app.core.router_class import OperationLogRoute
from app.core.logger import logger
from app.core.logger import log
from app.common.response import SuccessResponse, UploadFileResponse
from app.utils.upload_util import UploadUtil
@@ -31,7 +31,7 @@ async def upload_controller(
- JSONResponse: 包含上传文件详情的JSON响应
"""
result_dict = await FileService.upload_service(base_url=str(request.base_url), file=file)
logger.info(f"上传文件成功 {result_dict}")
log.info(f"上传文件成功 {result_dict}")
return SuccessResponse(data=result_dict, msg="上传文件成功")
@FileRouter.post("/download", summary="下载文件", description="下载文件", dependencies=[Depends(AuthPermission(["module_common:file:download"]))])
@@ -54,5 +54,5 @@ async def download_controller(
result = await FileService.download_service(file_path=file_path)
if delete:
background_tasks.add_task(UploadUtil.delete_file, Path(file_path))
logger.info(f"下载文件成功")
log.info(f"下载文件成功")
return UploadFileResponse(file_path=result.file_path, filename=result.file_name)
@@ -0,0 +1 @@
# -*- coding: utf-8 -*-
@@ -10,7 +10,7 @@ from app.core.base_params import PaginationQueryParam
from app.core.dependencies import AuthPermission
from app.core.router_class import OperationLogRoute
from app.core.base_schema import BatchSetAvailable
from app.core.logger import logger
from app.core.logger import log
from app.api.v1.module_system.auth.schema import AuthSchema
@@ -27,7 +27,7 @@ DemoRouter = APIRouter(route_class=OperationLogRoute, prefix="/demo", tags=["示
@DemoRouter.get("/detail/{id}", summary="获取示例详情", description="获取示例详情")
async def get_obj_detail_controller(
id: int = Path(..., description="示例ID"),
auth: AuthSchema = Depends(AuthPermission(["module_generator:demo:query"]))
auth: AuthSchema = Depends(AuthPermission(["module_example:demo:query"]))
) -> JSONResponse:
"""
获取示例详情
@@ -40,14 +40,14 @@ async def get_obj_detail_controller(
- JSONResponse: 包含示例详情的JSON响应
"""
result_dict = await DemoService.detail_service(id=id, auth=auth)
logger.info(f"获取示例详情成功 {id}")
log.info(f"获取示例详情成功 {id}")
return SuccessResponse(data=result_dict, msg="获取示例详情成功")
@DemoRouter.get("/list", summary="查询示例列表", description="查询示例列表")
async def get_obj_list_controller(
page: PaginationQueryParam = Depends(),
search: DemoQueryParam = Depends(),
auth: AuthSchema = Depends(AuthPermission(["module_generator:demo:query"]))
auth: AuthSchema = Depends(AuthPermission(["module_example:demo:query"]))
) -> JSONResponse:
"""
查询示例列表
@@ -68,13 +68,13 @@ async def get_obj_list_controller(
search=search,
order_by=page.order_by
)
logger.info("查询示例列表成功")
log.info("查询示例列表成功")
return SuccessResponse(data=result_dict, msg="查询示例列表成功")
@DemoRouter.post("/create", summary="创建示例", description="创建示例")
async def create_obj_controller(
data: DemoCreateSchema,
auth: AuthSchema = Depends(AuthPermission(["module_generator:demo:create"]))
auth: AuthSchema = Depends(AuthPermission(["module_example:demo:create"]))
) -> JSONResponse:
"""
创建示例
@@ -87,14 +87,14 @@ async def create_obj_controller(
- JSONResponse: 包含创建示例详情的JSON响应
"""
result_dict = await DemoService.create_service(auth=auth, data=data)
logger.info(f"创建示例成功: {result_dict.get('name')}")
log.info(f"创建示例成功: {result_dict.get('name')}")
return SuccessResponse(data=result_dict, msg="创建示例成功")
@DemoRouter.put("/update/{id}", summary="修改示例", description="修改示例")
async def update_obj_controller(
data: DemoUpdateSchema,
id: int = Path(..., description="示例ID"),
auth: AuthSchema = Depends(AuthPermission(["module_generator:demo:update"]))
auth: AuthSchema = Depends(AuthPermission(["module_example:demo:update"]))
) -> JSONResponse:
"""
修改示例
@@ -108,13 +108,13 @@ async def update_obj_controller(
- JSONResponse: 包含修改示例详情的JSON响应
"""
result_dict = await DemoService.update_service(auth=auth, id=id, data=data)
logger.info(f"修改示例成功: {result_dict.get('name')}")
log.info(f"修改示例成功: {result_dict.get('name')}")
return SuccessResponse(data=result_dict, msg="修改示例成功")
@DemoRouter.delete("/delete", summary="删除示例", description="删除示例")
async def delete_obj_controller(
ids: list[int] = Body(..., description="ID列表"),
auth: AuthSchema = Depends(AuthPermission(["module_generator:demo:delete"]))
auth: AuthSchema = Depends(AuthPermission(["module_example:demo:delete"]))
) -> JSONResponse:
"""
删除示例
@@ -127,13 +127,13 @@ async def delete_obj_controller(
- JSONResponse: 包含删除示例详情的JSON响应
"""
await DemoService.delete_service(auth=auth, ids=ids)
logger.info(f"删除示例成功: {ids}")
log.info(f"删除示例成功: {ids}")
return SuccessResponse(msg="删除示例成功")
@DemoRouter.patch("/available/setting", summary="批量修改示例状态", description="批量修改示例状态")
async def batch_set_available_obj_controller(
data: BatchSetAvailable,
auth: AuthSchema = Depends(AuthPermission(["module_generator:demo:patch"]))
auth: AuthSchema = Depends(AuthPermission(["module_example:demo:patch"]))
) -> JSONResponse:
"""
批量修改示例状态
@@ -146,13 +146,13 @@ async def batch_set_available_obj_controller(
- JSONResponse: 包含批量修改示例状态详情的JSON响应
"""
await DemoService.set_available_service(auth=auth, data=data)
logger.info(f"批量修改示例状态成功: {data.ids}")
log.info(f"批量修改示例状态成功: {data.ids}")
return SuccessResponse(msg="批量修改示例状态成功")
@DemoRouter.post('/export', summary="导出示例", description="导出示例")
async def export_obj_list_controller(
search: DemoQueryParam = Depends(),
auth: AuthSchema = Depends(AuthPermission(["module_generator:demo:export"]))
auth: AuthSchema = Depends(AuthPermission(["module_example:demo:export"]))
) -> StreamingResponse:
"""
导出示例
@@ -166,7 +166,7 @@ async def export_obj_list_controller(
"""
result_dict_list = await DemoService.list_service(search=search, auth=auth)
export_result = await DemoService.batch_export_service(obj_list=result_dict_list)
logger.info('导出示例成功')
log.info('导出示例成功')
return StreamResponse(
data=bytes2file_response(export_result),
@@ -179,7 +179,7 @@ async def export_obj_list_controller(
@DemoRouter.post('/import', summary="导入示例", description="导入示例")
async def import_obj_list_controller(
file: UploadFile,
auth: AuthSchema = Depends(AuthPermission(["module_generator:demo:import"]))
auth: AuthSchema = Depends(AuthPermission(["module_example:demo:import"]))
) -> JSONResponse:
"""
导入示例
@@ -192,10 +192,10 @@ async def import_obj_list_controller(
- JSONResponse: 包含导入示例详情的JSON响应
"""
batch_import_result = await DemoService.batch_import_service(file=file, auth=auth, update_support=True)
logger.info(f"导入示例成功: {batch_import_result}")
log.info(f"导入示例成功: {batch_import_result}")
return SuccessResponse(data=batch_import_result, msg="导入示例成功")
@DemoRouter.post('/download/template', summary="获取示例导入模板", description="获取示例导入模板", dependencies=[Depends(AuthPermission(["module_generator:demo:download"]))])
@DemoRouter.post('/download/template', summary="获取示例导入模板", description="获取示例导入模板", dependencies=[Depends(AuthPermission(["module_example:demo:download"]))])
async def export_obj_template_controller() -> StreamingResponse:
"""
获取示例导入模板
@@ -204,7 +204,7 @@ async def export_obj_template_controller() -> StreamingResponse:
- StreamingResponse: 包含示例导入模板的Excel文件流响应
"""
example_import_template_result = await DemoService.import_template_download_service()
logger.info('获取示例导入模板成功')
log.info('获取示例导入模板成功')
return StreamResponse(
data=bytes2file_response(example_import_template_result),
@@ -8,7 +8,7 @@ import pandas as pd
from app.core.base_schema import BatchSetAvailable
from app.core.exceptions import CustomException
from app.utils.excel_util import ExcelUtil
from app.core.logger import logger
from app.core.logger import log
from app.api.v1.module_system.auth.schema import AuthSchema
from .schema import DemoCreateSchema, DemoUpdateSchema, DemoOutSchema
@@ -286,7 +286,7 @@ class DemoService:
return result
except Exception as e:
logger.error(f"批量导入用户失败: {str(e)}")
log.error(f"批量导入用户失败: {str(e)}")
raise CustomException(msg=f"导入失败: {str(e)}")
@classmethod
@@ -0,0 +1,129 @@
# -*- coding:utf-8 -*-
from fastapi import APIRouter, Depends, UploadFile, Body, Path
from fastapi.responses import StreamingResponse, JSONResponse
from app.common.response import SuccessResponse, StreamResponse
from app.core.dependencies import AuthPermission
from app.core.router_class import OperationLogRoute
from app.api.v1.module_system.auth.schema import AuthSchema
from app.core.base_params import PaginationQueryParam
from app.utils.common_util import bytes2file_response
from app.core.logger import log
from app.core.base_schema import BatchSetAvailable
from .service import GenDemo01Service
from .schema import GenDemo01CreateSchema, GenDemo01UpdateSchema
from .param import GenDemo01QueryParam
GenDemo01Router = APIRouter(route_class=OperationLogRoute, prefix='/gen_demo01', tags=["示例模块"])
@GenDemo01Router.get("/detail/{id}", summary="获取示例详情", description="获取示例详情")
async def get_gen_demo01_detail_controller(
id: int = Path(..., description="ID"),
auth: AuthSchema = Depends(AuthPermission(["module_gencode:gen_demo01:query"]))
) -> JSONResponse:
"""获取示例详情接口"""
result_dict = await GenDemo01Service.detail_gen_demo01_service(auth=auth, id=id)
log.info(f"获取示例详情成功 {id}")
return SuccessResponse(data=result_dict, msg="获取示例详情成功")
@GenDemo01Router.get("/list", summary="查询示例列表", description="查询示例列表")
async def get_gen_demo01_list_controller(
page: PaginationQueryParam = Depends(),
search: GenDemo01QueryParam = Depends(),
auth: AuthSchema = Depends(AuthPermission(["module_gencode:gen_demo01:query"]))
) -> JSONResponse:
"""查询示例列表接口(数据库分页)"""
result_dict = await GenDemo01Service.page_service(
auth=auth,
page_no=page.page_no if page.page_no is not None else 1,
page_size=page.page_size if page.page_size is not None else 10,
search=search,
order_by=page.order_by
)
log.info("查询示例列表成功")
return SuccessResponse(data=result_dict, msg="查询示例列表成功")
@GenDemo01Router.post("/create", summary="创建示例", description="创建示例")
async def create_gen_demo01_controller(
data: GenDemo01CreateSchema,
auth: AuthSchema = Depends(AuthPermission(["module_gencode:gen_demo01:create"]))
) -> JSONResponse:
"""创建示例接口"""
result_dict = await GenDemo01Service.create_gen_demo01_service(auth=auth, data=data)
log.info("创建示例成功")
return SuccessResponse(data=result_dict, msg="创建示例成功")
@GenDemo01Router.put("/update/{id}", summary="修改示例", description="修改示例")
async def update_gen_demo01_controller(
data: GenDemo01UpdateSchema,
id: int = Path(..., description="ID"),
auth: AuthSchema = Depends(AuthPermission(["module_gencode:gen_demo01:update"]))
) -> JSONResponse:
"""修改示例接口"""
result_dict = await GenDemo01Service.update_gen_demo01_service(auth=auth, id=id, data=data)
log.info("修改示例成功")
return SuccessResponse(data=result_dict, msg="修改示例成功")
@GenDemo01Router.delete("/delete", summary="删除示例", description="删除示例")
async def delete_gen_demo01_controller(
ids: list[int] = Body(..., description="ID列表"),
auth: AuthSchema = Depends(AuthPermission(["module_gencode:gen_demo01:delete"]))
) -> JSONResponse:
"""删除示例接口"""
await GenDemo01Service.delete_gen_demo01_service(auth=auth, ids=ids)
log.info(f"删除示例成功: {ids}")
return SuccessResponse(msg="删除示例成功")
@GenDemo01Router.patch("/available/setting", summary="批量修改示例状态", description="批量修改示例状态")
async def batch_set_available_gen_demo01_controller(
data: BatchSetAvailable,
auth: AuthSchema = Depends(AuthPermission(["module_gencode:gen_demo01:patch"]))
) -> JSONResponse:
"""批量修改示例状态接口"""
await GenDemo01Service.set_available_gen_demo01_service(auth=auth, data=data)
log.info(f"批量修改示例状态成功: {data.ids}")
return SuccessResponse(msg="批量修改示例状态成功")
@GenDemo01Router.post('/export', summary="导出示例", description="导出示例")
async def export_gen_demo01_list_controller(
search: GenDemo01QueryParam = Depends(),
auth: AuthSchema = Depends(AuthPermission(["module_gencode:gen_demo01:export"]))
) -> StreamingResponse:
"""导出示例接口"""
result_dict_list = await GenDemo01Service.list_gen_demo01_service(search=search, auth=auth)
export_result = await GenDemo01Service.batch_export_service(obj_list=result_dict_list)
log.info('导出示例成功')
return StreamResponse(
data=bytes2file_response(export_result),
media_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
headers={
'Content-Disposition': 'attachment; filename=gen_demo01.xlsx'
}
)
@GenDemo01Router.post('/import', summary="导入示例", description="导入示例")
async def import_gen_demo01_list_controller(
file: UploadFile,
auth: AuthSchema = Depends(AuthPermission(["module_gencode:gen_demo01:import"]))
) -> JSONResponse:
"""导入示例接口"""
batch_import_result = await GenDemo01Service.batch_import_gen_demo01_service(file=file, auth=auth, update_support=True)
log.info("导入示例成功")
return SuccessResponse(data=batch_import_result, msg="导入示例成功")
@GenDemo01Router.post('/download/template', summary="获取示例导入模板", description="获取示例导入模板", dependencies=[Depends(AuthPermission(["module_gencode:gen_demo01:download"]))])
async def export_gen_demo01_template_controller() -> StreamingResponse:
"""获取示例导入模板接口"""
example_import_template_result = await GenDemo01Service.import_template_download_gen_demo01_service()
log.info('获取示例导入模板成功')
return StreamResponse(
data=bytes2file_response(example_import_template_result),
media_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
headers={
'Content-Disposition': 'attachment; filename=gen_demo01_template.xlsx'
}
)
@@ -0,0 +1,123 @@
# -*- coding:utf-8 -*-
from typing import Dict, List, Optional, Sequence, Union, Any
from app.core.base_crud import CRUDBase
from app.api.v1.module_system.auth.schema import AuthSchema
from .model import GenDemo01Model
from .schema import GenDemo01CreateSchema, GenDemo01UpdateSchema, GenDemo01OutSchema
class GenDemo01CRUD(CRUDBase[GenDemo01Model, GenDemo01CreateSchema, GenDemo01UpdateSchema]):
"""示例数据层"""
def __init__(self, auth: AuthSchema) -> None:
"""
初始化CRUD数据层
参数:
- auth (AuthSchema): 认证信息模型
"""
super().__init__(model=GenDemo01Model, auth=auth)
async def get_by_id_gen_demo01_crud(self, id: int, preload: Optional[List[Union[str, Any]]] = None) -> Optional[GenDemo01Model]:
"""
详情
参数:
- id (int): 对象ID
- preload (Optional[List[Union[str, Any]]]): 预加载关系,未提供时使用模型默认项
返回:
- Optional[GenDemo01Model]: 模型实例或None
"""
return await self.get(id=id, preload=preload)
async def list_gen_demo01_crud(self, search: Optional[Dict] = None, order_by: Optional[List[Dict[str, str]]] = None, preload: Optional[List[Union[str, Any]]] = None) -> Sequence[GenDemo01Model]:
"""
列表查询
参数:
- search (Optional[Dict]): 查询参数
- order_by (Optional[List[Dict[str, str]]]): 排序参数
- preload (Optional[List[Union[str, Any]]]): 预加载关系,未提供时使用模型默认项
返回:
- Sequence[GenDemo01Model]: 模型实例序列
"""
return await self.list(search=search, order_by=order_by, preload=preload)
async def create_gen_demo01_crud(self, data: GenDemo01CreateSchema) -> Optional[GenDemo01Model]:
"""
创建
参数:
- data (GenDemo01CreateSchema): 创建模型
返回:
- Optional[GenDemo01Model]: 模型实例或None
"""
return await self.create(data=data)
async def update_gen_demo01_crud(self, id: int, data: GenDemo01UpdateSchema) -> Optional[GenDemo01Model]:
"""
更新
参数:
- id (int): 对象ID
- data (GenDemo01UpdateSchema): 更新模型
返回:
- Optional[GenDemo01Model]: 模型实例或None
"""
return await self.update(id=id, data=data)
async def delete_gen_demo01_crud(self, ids: List[int]) -> None:
"""
批量删除
参数:
- ids (List[int]): 对象ID列表
返回:
- None
"""
return await self.delete(ids=ids)
async def set_available_gen_demo01_crud(self, ids: List[int], status: bool) -> None:
"""
批量设置可用状态
参数:
- ids (List[int]): 对象ID列表
- status (bool): 可用状态
返回:
- None
"""
return await self.set(ids=ids, status=status)
async def page_gen_demo01_crud(self, offset: int, limit: int, order_by: Optional[List[Dict[str, str]]] = None, search: Optional[Dict] = None, preload: Optional[List[Union[str, Any]]] = None) -> Dict:
"""
分页查询
参数:
- offset (int): 偏移量
- limit (int): 每页数量
- order_by (Optional[List[Dict[str, str]]]): 排序参数
- search (Optional[Dict]): 查询参数
- preload (Optional[List[Union[str, Any]]]): 预加载关系,未提供时使用模型默认项
返回:
- Dict: 分页数据
"""
order_by_list = order_by or [{'id': 'asc'}]
search_dict = search or {}
return await self.page(
offset=offset,
limit=limit,
order_by=order_by_list,
search=search_dict,
out_schema=GenDemo01OutSchema,
preload=preload
)
@@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
from sqlalchemy import Integer, Text, String, SmallInteger, DateTime
from typing import Optional
from sqlalchemy.orm import Mapped, mapped_column
from app.core.base_model import CreatorMixin
class GenDemo01Model(CreatorMixin):
"""
示例表
"""
__tablename__ = 'gen_demo01'
__table_args__ = {'comment': '示例'}
__loader_options__ = ["creator"]
name: Mapped[Optional[str]] = mapped_column(String(64), nullable=True, comment='名称')
status: Mapped[Optional[int]] = mapped_column(SmallInteger, nullable=True, comment='是否启用(True:启用 False:禁用)')
@@ -0,0 +1,31 @@
# -*- coding: utf-8 -*-
from typing import Optional
from fastapi import Query
from app.core.validator import DateTimeStr
class GenDemo01QueryParam:
"""示例查询参数"""
def __init__(
self,
name: Optional[str] = Query(None, description="名称"),
creator: Optional[int] = Query(None, description="创建人"),
start_time: Optional[DateTimeStr] = Query(None, description="开始时间", example="2025-01-01 00:00:00"),
end_time: Optional[DateTimeStr] = Query(None, description="结束时间", example="2025-12-31 23:59:59"),
) -> None:
# 模糊查询字段
self.name = ("like", name)
# 精确查询字段
self.status = status
self.creator_id = creator_id
self.creator_id = creator
# 时间范围查询
if start_time and end_time:
self.created_at = ("between", (start_time, end_time))
@@ -0,0 +1,29 @@
# -*- coding:utf-8 -*-
from typing import Optional
from pydantic import BaseModel, ConfigDict, Field
from app.core.base_schema import BaseSchema
class GenDemo01CreateSchema(BaseModel):
"""
示例新增模型
"""
name: Optional[str] = Field(default=None, description='名称')
status: Optional[int] = Field(default=None, description='是否启用(True:启用 False:禁用)')
description: Optional[str] = Field(default=None, description='备注/描述')
class GenDemo01UpdateSchema(GenDemo01CreateSchema):
"""
示例更新模型
"""
...
class GenDemo01OutSchema(GenDemo01CreateSchema, BaseSchema):
"""
示例响应模型
"""
model_config = ConfigDict(from_attributes=True)
@@ -0,0 +1,202 @@
# -*- coding:utf-8 -*-
import io
from typing import Any, List, Dict, Optional
from fastapi import UploadFile
import pandas as pd
from app.core.base_schema import BatchSetAvailable
from app.core.exceptions import CustomException
from app.utils.excel_util import ExcelUtil
from app.core.logger import log
from app.api.v1.module_system.auth.schema import AuthSchema
from .schema import GenDemo01CreateSchema, GenDemo01UpdateSchema, GenDemo01OutSchema
from .param import GenDemo01QueryParam
from .crud import GenDemo01CRUD
class GenDemo01Service:
"""
示例服务层
"""
@classmethod
async def detail_gen_demo01_service(cls, auth: AuthSchema, id: int) -> Dict:
"""详情"""
obj = await GenDemo01CRUD(auth).get_by_id_gen_demo01_crud(id=id)
if not obj:
raise CustomException(msg="该数据不存在")
return GenDemo01OutSchema.model_validate(obj).model_dump()
@classmethod
async def list_gen_demo01_service(cls, auth: AuthSchema, search: Optional[GenDemo01QueryParam] = None, order_by: Optional[List[Dict[str, str]]] = None) -> List[Dict]:
"""列表查询"""
search_dict = search.__dict__ if search else None
obj_list = await GenDemo01CRUD(auth).list_gen_demo01_crud(search=search_dict, order_by=order_by)
return [GenDemo01OutSchema.model_validate(obj).model_dump() for obj in obj_list]
@classmethod
async def page_gen_demo01_service(cls, auth: AuthSchema, page_no: int, page_size: int, search: Optional[GenDemo01QueryParam] = None, order_by: Optional[List[Dict[str, str]]] = None) -> Dict:
"""分页查询(数据库分页)"""
search_dict = search.__dict__ if search else {}
order_by_list = order_by or [{'id': 'asc'}]
offset = (page_no - 1) * page_size
result = await GenDemo01CRUD(auth).page_gen_demo01_crud(
offset=offset,
limit=page_size,
order_by=order_by_list,
search=search_dict
)
return result
@classmethod
async def create_gen_demo01_service(cls, auth: AuthSchema, data: GenDemo01CreateSchema) -> Dict:
"""创建"""
# 检查唯一性约束
obj = await GenDemo01CRUD(auth).create_gen_demo01_crud(data=data)
return GenDemo01OutSchema.model_validate(obj).model_dump()
@classmethod
async def update_gen_demo01_service(cls, auth: AuthSchema, id: int, data: GenDemo01UpdateSchema) -> Dict:
"""更新"""
# 检查数据是否存在
obj = await GenDemo01CRUD(auth).get_by_id_gen_demo01_crud(id=id)
if not obj:
raise CustomException(msg='更新失败,该数据不存在')
# 检查唯一性约束
obj = await GenDemo01CRUD(auth).update_gen_demo01_crud(id=id, data=data)
return GenDemo01OutSchema.model_validate(obj).model_dump()
@classmethod
async def delete_gen_demo01_service(cls, auth: AuthSchema, ids: List[int]) -> None:
"""删除"""
if len(ids) < 1:
raise CustomException(msg='删除失败,删除对象不能为空')
for id in ids:
obj = await GenDemo01CRUD(auth).get_by_id_gen_demo01_crud(id=id)
if not obj:
raise CustomException(msg=f'删除失败,ID为{id}的数据不存在')
await GenDemo01CRUD(auth).delete_gen_demo01_crud(ids=ids)
@classmethod
async def set_available_gen_demo01_service(cls, auth: AuthSchema, data: BatchSetAvailable) -> None:
"""批量设置状态"""
await GenDemo01CRUD(auth).set_available_gen_demo01_crud(ids=data.ids, status=data.status)
@classmethod
async def batch_export_gen_demo01_service(cls, obj_list: List[Dict[str, Any]]) -> bytes:
"""批量导出"""
mapping_dict = {
'name': '名称',
'status': '是否启用(True:启用 False:禁用)',
'creator_id': '创建人ID',
'id': '主键ID',
'description': '备注/描述',
'created_at': '创建时间',
'updated_at': '更新时间',
'creator': '创建者',
}
data = obj_list.copy()
for item in data:
# 状态转换
if 'status' in item:
item['status'] = '正常' if item.get('status') else '停用'
# 创建者转换
creator_info = item.get('creator')
if isinstance(creator_info, dict):
item['creator'] = creator_info.get('name', '未知')
elif creator_info is None:
item['creator'] = '未知'
return ExcelUtil.export_list2excel(list_data=data, mapping_dict=mapping_dict)
@classmethod
async def batch_import_gen_demo01_service(cls, auth: AuthSchema, file: UploadFile, update_support: bool = False) -> str:
"""批量导入"""
header_dict = {
'名称': 'name',
'是否启用(True:启用 False:禁用)': 'status',
'创建人ID': 'creator_id',
'主键ID': 'id',
'备注/描述': 'description',
'创建时间': 'created_at',
'更新时间': 'updated_at',
}
try:
contents = await file.read()
df = pd.read_excel(io.BytesIO(contents))
await file.close()
if df.empty:
raise CustomException(msg="导入文件为空")
missing_headers = [header for header in header_dict.keys() if header not in df.columns]
if missing_headers:
raise CustomException(msg=f"导入文件缺少必要的列: {', '.join(missing_headers)}")
df.rename(columns=header_dict, inplace=True)
# 验证必填字段
error_msgs = []
success_count = 0
count = 0
for index, row in df.iterrows():
count += 1
try:
data = {
"name": row['name'],
"status": row['status'],
"creator_id": row['creator_id'],
"id": row['id'],
"description": row['description'],
"created_at": row['created_at'],
"updated_at": row['updated_at'],
}
# 使用CreateSchema做校验后入库
create_schema = GenDemo01CreateSchema.model_validate(data)
# 检查唯一性约束
await GenDemo01CRUD(auth).create_crud(data=create_schema)
success_count += 1
except Exception as e:
error_msgs.append(f"{count}行: {str(e)}")
continue
result = f"成功导入 {success_count} 条数据"
if error_msgs:
result += "\n错误信息:\n" + "\n".join(error_msgs)
return result
except Exception as e:
log.error(f"批量导入失败: {str(e)}")
raise CustomException(msg=f"导入失败: {str(e)}")
@classmethod
async def import_template_download_gen_demo01_service(cls) -> bytes:
"""下载导入模板"""
header_list = [
'名称',
'是否启用(True:启用 False:禁用)',
'创建人ID',
'主键ID',
'备注/描述',
'创建时间',
'更新时间',
]
selector_header_list = []
option_list = []
# 添加下拉选项
return ExcelUtil.get_excel_template(
header_list=header_list,
selector_header_list=selector_header_list,
option_list=option_list
)
@@ -10,7 +10,7 @@ from app.core.router_class import OperationLogRoute
from app.core.base_params import PaginationQueryParam
from app.common.request import PaginationService
from app.utils.common_util import bytes2file_response
from app.core.logger import logger
from app.core.logger import log
from app.api.v1.module_system.auth.schema import AuthSchema
from .param import GenTableQueryParam
@@ -40,7 +40,7 @@ async def gen_table_list_controller(
"""
result_dict_list = await GenTableService.get_gen_table_list_service(auth=auth, search=search)
result_dict = await PaginationService.paginate(data_list=result_dict_list, page_no=page.page_no, page_size=page.page_size)
logger.info('获取代码生成业务表列表成功')
log.info('获取代码生成业务表列表成功')
return SuccessResponse(data=result_dict, msg="获取代码生成业务表列表成功")
@@ -63,7 +63,7 @@ async def get_gen_db_table_list_controller(
"""
result_dict_list = await GenTableService.get_gen_db_table_list_service(auth=auth, search=search)
result_dict = await PaginationService.paginate(data_list=result_dict_list, page_no=page.page_no, page_size=page.page_size)
logger.info('获取数据库表列表成功')
log.info('获取数据库表列表成功')
return SuccessResponse(data=result_dict, msg="获取数据库表列表成功")
@@ -84,7 +84,7 @@ async def import_gen_table_controller(
"""
add_gen_table_list = await GenTableService.get_gen_db_table_list_by_name_service(auth, table_names)
result = await GenTableService.import_gen_table_service(auth, add_gen_table_list)
logger.info('导入表结构成功')
log.info('导入表结构成功')
return SuccessResponse(msg="导入表结构成功", data=result)
@@ -104,7 +104,7 @@ async def gen_table_detail_controller(
- JSONResponse: 包含业务表详细信息的JSON响应
"""
gen_table_detail_result = await GenTableService.get_gen_table_detail_service(auth, table_id)
logger.info(f'获取table_id为{table_id}的信息成功')
log.info(f'获取table_id为{table_id}的信息成功')
return SuccessResponse(data=gen_table_detail_result, msg="获取业务表详细信息成功")
@@ -124,7 +124,7 @@ async def create_table_controller(
- JSONResponse: 包含创建结果的JSON响应
"""
result = await GenTableService.create_table_service(auth, sql)
logger.info('创建表结构成功')
log.info('创建表结构成功')
return SuccessResponse(msg="创建表结构成功", data=result)
@@ -146,7 +146,7 @@ async def update_gen_table_controller(
- JSONResponse: 包含编辑结果的JSON响应
"""
result_dict = await GenTableService.update_gen_table_service(auth, data, table_id)
logger.info('编辑业务表信息成功')
log.info('编辑业务表信息成功')
return SuccessResponse(data=result_dict, msg="编辑业务表信息成功")
@@ -166,14 +166,14 @@ async def delete_gen_table_controller(
- JSONResponse: 包含删除结果的JSON响应
"""
result = await GenTableService.delete_gen_table_service(auth, ids)
logger.info('删除业务表信息成功')
log.info('删除业务表信息成功')
return SuccessResponse(msg="删除业务表信息成功", data=result)
@GenRouter.patch("/batch/output", summary="批量生成代码", description="批量生成代码")
async def batch_gen_code_controller(
table_names: List[str] = Body(..., description="表名列表"),
auth: AuthSchema = Depends(AuthPermission(["module_generator:gencode:operate"]))
auth: AuthSchema = Depends(AuthPermission(["module_generator:gencode:patch"]))
) -> StreamResponse:
"""
批量生成代码
@@ -186,7 +186,7 @@ async def batch_gen_code_controller(
- StreamResponse: 包含批量生成代码的ZIP文件流响应
"""
batch_gen_code_result = await GenTableService.batch_gen_code_service(auth, table_names)
logger.info(f'批量生成代码成功,表名列表:{table_names}')
log.info(f'批量生成代码成功,表名列表:{table_names}')
return StreamResponse(
data=bytes2file_response(batch_gen_code_result),
media_type='application/zip',
@@ -210,7 +210,7 @@ async def gen_code_local_controller(
- JSONResponse: 包含生成结果的JSON响应
"""
result = await GenTableService.generate_code_service(auth, table_name)
logger.info(f'生成代码,表名:{table_name},到指定路径成功')
log.info(f'生成代码,表名:{table_name},到指定路径成功')
return SuccessResponse(msg="生成代码到指定路径成功", data=result)
@@ -230,7 +230,7 @@ async def preview_code_controller(
- JSONResponse: 包含预览代码的JSON响应
"""
preview_code_result = await GenTableService.preview_code_service(auth, table_id)
logger.info(f'预览代码,表id{table_id},成功')
log.info(f'预览代码,表id{table_id},成功')
return SuccessResponse(data=preview_code_result, msg="预览代码成功")
@@ -250,5 +250,5 @@ async def sync_db_controller(
- JSONResponse: 包含同步数据库结果的JSON响应
"""
result = await GenTableService.sync_db_service(auth, table_name)
logger.info(f'同步数据库,表名:{table_name},成功')
log.info(f'同步数据库,表名:{table_name},成功')
return SuccessResponse(msg="同步数据库成功", data=result)
@@ -5,7 +5,7 @@ from sqlalchemy import and_, select, text
from typing import List, Optional, Sequence, Dict, Union, Any
from sqlglot.expressions import Expression
from app.core.logger import logger
from app.core.logger import log
from app.config.setting import settings
from app.core.base_crud import CRUDBase
@@ -262,7 +262,7 @@ class GenTableCRUD(CRUDBase[GenTableModel, GenTableSchema, GenTableSchema]):
else:
gen_db_table_list = (await self.db.execute(text(query_sql), {"table_names": tuple(unique_table_names)})).fetchall()
except Exception as e:
logger.error(f"查询表信息时发生错误: {e}")
log.error(f"查询表信息时发生错误: {e}")
# 查询错误时直接抛出,不需要事务处理
raise
@@ -299,7 +299,7 @@ class GenTableCRUD(CRUDBase[GenTableModel, GenTableSchema, GenTableSchema]):
result = await self.db.execute(query, {"table_name": table_name})
return result.scalar() is not None
except Exception as e:
logger.error(f"检查表格存在性时发生错误: {e}")
log.error(f"检查表格存在性时发生错误: {e}")
# 出错时返回False,避免误报表已存在
return False
@@ -324,7 +324,7 @@ class GenTableCRUD(CRUDBase[GenTableModel, GenTableSchema, GenTableSchema]):
await self.db.execute(text(sql))
return True
except Exception as e:
logger.error(f"创建表时发生错误: {e}")
log.error(f"创建表时发生错误: {e}")
return False
@@ -487,7 +487,7 @@ class GenTableColumnCRUD(CRUDBase[GenTableColumnModel, GenTableColumnSchema, Gen
)
return columns_list
except Exception as e:
logger.error(f"获取表{table_name}的字段列表时出错: {str(e)}")
log.error(f"获取表{table_name}的字段列表时出错: {str(e)}")
# 确保即使出错也返回空列表而不是None
raise
@@ -7,13 +7,14 @@ from typing import Any, List, Dict, Literal, Optional
from sqlglot.expressions import Add, Alter, Create, Delete, Drop, Expression, Insert, Table, TruncateTable, Update
from sqlglot import parse as sqlglot_parse
from app.config.path_conf import BASE_DIR
from app.config.setting import settings
from app.core.logger import logger
from app.core.logger import log
from app.core.exceptions import CustomException
from app.utils.gen_util import GenUtils
from app.utils.jinja2_template_util import Jinja2TemplateUtil
from app.api.v1.module_system.auth.schema import AuthSchema
from .tools.jinja2_template_util import Jinja2TemplateUtil
from .tools.gen_util import GenUtils
from .schema import GenTableSchema, GenTableOutSchema, GenTableColumnSchema, GenTableColumnOutSchema
from .param import GenTableQueryParam
from .crud import GenTableColumnCRUD, GenTableCRUD
@@ -294,7 +295,7 @@ class GenTableService:
table_out = GenTableOutSchema.model_validate(gen_table)
result.append(table_out)
except Exception as e:
logger.warning(f"转换业务表时出错: {str(e)}")
log.error(f"转换业务表时出错: {str(e)}")
continue
return result
@@ -318,7 +319,7 @@ class GenTableService:
render_content = await env.get_template(template).render_async(**context)
preview_code_result[template] = render_content
except Exception as e:
logger.error(f"渲染模板 {template} 时出错: {str(e)}")
log.error(f"渲染模板 {template} 时出错: {str(e)}")
# 即使某个模板渲染失败,也继续处理其他模板
preview_code_result[template] = f"渲染错误: {str(e)}"
return preview_code_result
@@ -376,7 +377,7 @@ class GenTableService:
render_content = await env.get_template(template_file).render_async(**render_info[2])
zip_file.writestr(output_file, render_content)
except Exception as e:
logger.error(f"批量生成代码时处理表 {table_name} 出错: {str(e)}")
log.error(f"批量生成代码时处理表 {table_name} 出错: {str(e)}")
# 继续处理其他表,不中断整个过程
continue
@@ -507,18 +508,18 @@ class GenTableService:
try:
file_name = Jinja2TemplateUtil.get_file_name(template, gen_table)
# 默认写入到项目根目录(backend的上一级)
project_root = str(settings.BASE_DIR.parent)
project_root = str(BASE_DIR.parent)
full_path = os.path.join(project_root, file_name)
# 确保路径在项目根目录内,防止路径遍历攻击
if not os.path.abspath(full_path).startswith(os.path.abspath(project_root)):
logger.warning(f"路径越界,回退到项目根目录: {file_name}")
log.error(f"路径越界,回退到项目根目录: {file_name}")
# 回退到项目根目录下的generated文件夹
full_path = os.path.join(project_root, "generated", os.path.basename(file_name))
return full_path
except Exception as e:
logger.error(f"生成路径时出错: {str(e)}")
log.error(f"生成路径时出错: {str(e)}")
return None
@@ -0,0 +1,129 @@
# -*- coding:utf-8 -*-
from fastapi import APIRouter, Depends, UploadFile, Body, Path
from fastapi.responses import StreamingResponse, JSONResponse
from app.common.response import SuccessResponse, StreamResponse
from app.core.dependencies import AuthPermission
from app.core.router_class import OperationLogRoute
from app.api.v1.module_system.auth.schema import AuthSchema
from app.core.base_params import PaginationQueryParam
from app.utils.common_util import bytes2file_response
from app.core.logger import log
from app.core.base_schema import BatchSetAvailable
from .service import {{ class_name }}Service
from .schema import {{ class_name }}CreateSchema, {{ class_name }}UpdateSchema
from .param import {{ class_name }}QueryParam
{{ class_name }}Router = APIRouter(route_class=OperationLogRoute, prefix='/{{ business_name }}', tags=["{{ function_name }}模块"])
@{{ class_name }}Router.get("/detail/{id}", summary="获取{{ function_name }}详情", description="获取{{ function_name }}详情")
async def get_{{ business_name }}_detail_controller(
id: int = Path(..., description="ID"),
auth: AuthSchema = Depends(AuthPermission(["{{ permission_prefix }}:query"]))
) -> JSONResponse:
"""获取{{ function_name }}详情接口"""
result_dict = await {{ class_name }}Service.detail_{{ business_name }}_service(auth=auth, id=id)
log.info(f"获取{{ function_name }}详情成功 {id}")
return SuccessResponse(data=result_dict, msg="获取{{ function_name }}详情成功")
@{{ class_name }}Router.get("/list", summary="查询{{ function_name }}列表", description="查询{{ function_name }}列表")
async def get_{{ business_name }}_list_controller(
page: PaginationQueryParam = Depends(),
search: {{ class_name }}QueryParam = Depends(),
auth: AuthSchema = Depends(AuthPermission(["{{ permission_prefix }}:query"]))
) -> JSONResponse:
"""查询{{ function_name }}列表接口(数据库分页)"""
result_dict = await {{ class_name }}Service.page_service(
auth=auth,
page_no=page.page_no if page.page_no is not None else 1,
page_size=page.page_size if page.page_size is not None else 10,
search=search,
order_by=page.order_by
)
log.info("查询{{ function_name }}列表成功")
return SuccessResponse(data=result_dict, msg="查询{{ function_name }}列表成功")
@{{ class_name }}Router.post("/create", summary="创建{{ function_name }}", description="创建{{ function_name }}")
async def create_{{ business_name }}_controller(
data: {{ class_name }}CreateSchema,
auth: AuthSchema = Depends(AuthPermission(["{{ permission_prefix }}:create"]))
) -> JSONResponse:
"""创建{{ function_name }}接口"""
result_dict = await {{ class_name }}Service.create_{{ business_name }}_service(auth=auth, data=data)
log.info("创建{{ function_name }}成功")
return SuccessResponse(data=result_dict, msg="创建{{ function_name }}成功")
@{{ class_name }}Router.put("/update/{id}", summary="修改{{ function_name }}", description="修改{{ function_name }}")
async def update_{{ business_name }}_controller(
data: {{ class_name }}UpdateSchema,
id: int = Path(..., description="ID"),
auth: AuthSchema = Depends(AuthPermission(["{{ permission_prefix }}:update"]))
) -> JSONResponse:
"""修改{{ function_name }}接口"""
result_dict = await {{ class_name }}Service.update_{{ business_name }}_service(auth=auth, id=id, data=data)
log.info("修改{{ function_name }}成功")
return SuccessResponse(data=result_dict, msg="修改{{ function_name }}成功")
@{{ class_name }}Router.delete("/delete", summary="删除{{ function_name }}", description="删除{{ function_name }}")
async def delete_{{ business_name }}_controller(
ids: list[int] = Body(..., description="ID列表"),
auth: AuthSchema = Depends(AuthPermission(["{{ permission_prefix }}:delete"]))
) -> JSONResponse:
"""删除{{ function_name }}接口"""
await {{ class_name }}Service.delete_{{ business_name }}_service(auth=auth, ids=ids)
log.info(f"删除{{ function_name }}成功: {ids}")
return SuccessResponse(msg="删除{{ function_name }}成功")
@{{ class_name }}Router.patch("/available/setting", summary="批量修改{{ function_name }}状态", description="批量修改{{ function_name }}状态")
async def batch_set_available_{{ business_name }}_controller(
data: BatchSetAvailable,
auth: AuthSchema = Depends(AuthPermission(["{{ permission_prefix }}:patch"]))
) -> JSONResponse:
"""批量修改{{ function_name }}状态接口"""
await {{ class_name }}Service.set_available_{{ business_name }}_service(auth=auth, data=data)
log.info(f"批量修改{{ function_name }}状态成功: {data.ids}")
return SuccessResponse(msg="批量修改{{ function_name }}状态成功")
@{{ class_name }}Router.post('/export', summary="导出{{ function_name }}", description="导出{{ function_name }}")
async def export_{{ business_name }}_list_controller(
search: {{ class_name }}QueryParam = Depends(),
auth: AuthSchema = Depends(AuthPermission(["{{ permission_prefix }}:export"]))
) -> StreamingResponse:
"""导出{{ function_name }}接口"""
result_dict_list = await {{ class_name }}Service.list_{{ business_name }}_service(search=search, auth=auth)
export_result = await {{ class_name }}Service.batch_export_service(obj_list=result_dict_list)
log.info('导出{{ function_name }}成功')
return StreamResponse(
data=bytes2file_response(export_result),
media_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
headers={
'Content-Disposition': 'attachment; filename={{ table_name }}.xlsx'
}
)
@{{ class_name }}Router.post('/import', summary="导入{{ function_name }}", description="导入{{ function_name }}")
async def import_{{ business_name }}_list_controller(
file: UploadFile,
auth: AuthSchema = Depends(AuthPermission(["{{ permission_prefix }}:import"]))
) -> JSONResponse:
"""导入{{ function_name }}接口"""
batch_import_result = await {{ class_name }}Service.batch_import_{{ business_name }}_service(file=file, auth=auth, update_support=True)
log.info("导入{{ function_name }}成功")
return SuccessResponse(data=batch_import_result, msg="导入{{ function_name }}成功")
@{{ class_name }}Router.post('/download/template', summary="获取{{ function_name }}导入模板", description="获取{{ function_name }}导入模板", dependencies=[Depends(AuthPermission(["{{ permission_prefix }}:download"]))])
async def export_{{ business_name }}_template_controller() -> StreamingResponse:
"""获取{{ function_name }}导入模板接口"""
example_import_template_result = await {{ class_name }}Service.import_template_download_{{ business_name }}_service()
log.info('获取{{ function_name }}导入模板成功')
return StreamResponse(
data=bytes2file_response(example_import_template_result),
media_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
headers={
'Content-Disposition': 'attachment; filename={{ table_name }}_template.xlsx'
}
)
@@ -0,0 +1,123 @@
# -*- coding:utf-8 -*-
from typing import Dict, List, Optional, Sequence, Union, Any
from app.core.base_crud import CRUDBase
from app.api.v1.module_system.auth.schema import AuthSchema
from .model import {{ class_name }}Model
from .schema import {{ class_name }}CreateSchema, {{ class_name }}UpdateSchema, {{ class_name }}OutSchema
class {{ class_name }}CRUD(CRUDBase[{{ class_name }}Model, {{ class_name }}CreateSchema, {{ class_name }}UpdateSchema]):
"""{{ function_name }}数据层"""
def __init__(self, auth: AuthSchema) -> None:
"""
初始化CRUD数据层
参数:
- auth (AuthSchema): 认证信息模型
"""
super().__init__(model={{ class_name }}Model, auth=auth)
async def get_by_id_{{ business_name }}_crud(self, id: int, preload: Optional[List[Union[str, Any]]] = None) -> Optional[{{ class_name }}Model]:
"""
详情
参数:
- id (int): 对象ID
- preload (Optional[List[Union[str, Any]]]): 预加载关系,未提供时使用模型默认项
返回:
- Optional[{{ class_name }}Model]: 模型实例或None
"""
return await self.get(id=id, preload=preload)
async def list_{{ business_name }}_crud(self, search: Optional[Dict] = None, order_by: Optional[List[Dict[str, str]]] = None, preload: Optional[List[Union[str, Any]]] = None) -> Sequence[{{ class_name }}Model]:
"""
列表查询
参数:
- search (Optional[Dict]): 查询参数
- order_by (Optional[List[Dict[str, str]]]): 排序参数
- preload (Optional[List[Union[str, Any]]]): 预加载关系,未提供时使用模型默认项
返回:
- Sequence[{{ class_name }}Model]: 模型实例序列
"""
return await self.list(search=search, order_by=order_by, preload=preload)
async def create_{{ business_name }}_crud(self, data: {{ class_name }}CreateSchema) -> Optional[{{ class_name }}Model]:
"""
创建
参数:
- data ({{ class_name }}CreateSchema): 创建模型
返回:
- Optional[{{ class_name }}Model]: 模型实例或None
"""
return await self.create(data=data)
async def update_{{ business_name }}_crud(self, id: int, data: {{ class_name }}UpdateSchema) -> Optional[{{ class_name }}Model]:
"""
更新
参数:
- id (int): 对象ID
- data ({{ class_name }}UpdateSchema): 更新模型
返回:
- Optional[{{ class_name }}Model]: 模型实例或None
"""
return await self.update(id=id, data=data)
async def delete_{{ business_name }}_crud(self, ids: List[int]) -> None:
"""
批量删除
参数:
- ids (List[int]): 对象ID列表
返回:
- None
"""
return await self.delete(ids=ids)
async def set_available_{{ business_name }}_crud(self, ids: List[int], status: bool) -> None:
"""
批量设置可用状态
参数:
- ids (List[int]): 对象ID列表
- status (bool): 可用状态
返回:
- None
"""
return await self.set(ids=ids, status=status)
async def page_{{ business_name }}_crud(self, offset: int, limit: int, order_by: Optional[List[Dict[str, str]]] = None, search: Optional[Dict] = None, preload: Optional[List[Union[str, Any]]] = None) -> Dict:
"""
分页查询
参数:
- offset (int): 偏移量
- limit (int): 每页数量
- order_by (Optional[List[Dict[str, str]]]): 排序参数
- search (Optional[Dict]): 查询参数
- preload (Optional[List[Union[str, Any]]]): 预加载关系,未提供时使用模型默认项
返回:
- Dict: 分页数据
"""
order_by_list = order_by or [{'id': 'asc'}]
search_dict = search or {}
return await self.page(
offset=offset,
limit=limit,
order_by=order_by_list,
search=search_dict,
out_schema={{ class_name }}OutSchema,
preload=preload
)
@@ -0,0 +1,32 @@
# -*- coding: utf-8 -*-
{% for model_import in model_import_list %}
{{ model_import }}
{% endfor %}
{% if table.sub %}
from sqlalchemy.orm import relationship
{% endif %}
from typing import Optional
from sqlalchemy.orm import Mapped, mapped_column
from app.core.base_model import CreatorMixin
class {{ class_name }}Model(CreatorMixin):
"""
{{ function_name }}表
"""
__tablename__ = '{{ table_name }}'
__table_args__ = {'comment': '{{ function_name }}'}
__loader_options__ = ["creator"]
{% for column in columns %}
{% if column.column_name not in ['id', 'creator_id', 'description', 'created_at', 'updated_at'] %}
{{ column.column_name }}: Mapped[Optional[{{ column.python_type }}]] = mapped_column({{ column.column_type|get_sqlalchemy_type }}, {% if column.pk %}primary_key=True, {% endif %}{% if column.increment %}autoincrement=True, {% endif %}{% if column.required or column.pk %}nullable=False{% else %}nullable=True{% endif %}, comment='{{ column.column_comment }}')
{% endif %}
{% endfor %}
{% if table.sub %}
{{ sub_class_name }}_list = relationship('{{ sub_class_name }}', back_populates='{{ business_name }}')
{% endif %}
@@ -0,0 +1,47 @@
# -*- coding: utf-8 -*-
from typing import Optional
from fastapi import Query
from app.core.validator import DateTimeStr
class {{ class_name }}QueryParam:
"""{{ function_name }}查询参数"""
def __init__(
self,
{% for column in columns %}
{% if column.query_type == 'LIKE' %}
{{ column.column_name }}: Optional[{{ column.python_type }}] = Query(None, description="{{ column.column_comment }}"),
{% endif %}
{% endfor %}
{% for column in columns %}
{% if column.column_name == 'EQ' %}
{{ column.column_name }}: Optional[{{ column.python_type }}] = Query(None, description="{{ column.column_comment }}"),
{% endif %}
{% endfor %}
creator: Optional[int] = Query(None, description="创建人"),
start_time: Optional[DateTimeStr] = Query(None, description="开始时间", example="2025-01-01 00:00:00"),
end_time: Optional[DateTimeStr] = Query(None, description="结束时间", example="2025-12-31 23:59:59"),
) -> None:
# 模糊查询字段
{% for column in columns %}
{% if column.query_type == 'LIKE' %}
self.{{ column.column_name }} = ("like", {{ column.column_name }})
{% endif %}
{% endfor %}
# 精确查询字段
{% for column in columns %}
{% if column.query_type == 'EQ' %}
self.{{ column.column_name }} = {{ column.column_name }}
{% endif %}
{% endfor %}
self.creator_id = creator
# 时间范围查询
if start_time and end_time:
self.created_at = ("between", (start_time, end_time))
@@ -0,0 +1,35 @@
# -*- coding:utf-8 -*-
{% if table.sub %}
from typing import List, Optional
{% else %}
from typing import Optional
{% endif %}
from pydantic import BaseModel, ConfigDict, Field
from app.core.base_schema import BaseSchema
class {{ class_name }}CreateSchema(BaseModel):
"""
{{ function_name }}新增模型
"""
{% for column in columns %}
{% if column.column_name not in ['id', 'creator_id', 'created_at', 'updated_at'] %}
{{ column.column_name }}: Optional[{{ column.python_type }}] = Field(default=None, description='{{ column.column_comment }}')
{% endif %}
{% endfor %}
class {{ class_name }}UpdateSchema({{ class_name }}CreateSchema):
"""
{{ function_name }}更新模型
"""
...
class {{ class_name }}OutSchema({{ class_name }}CreateSchema, BaseSchema):
"""
{{ function_name }}响应模型
"""
model_config = ConfigDict(from_attributes=True)
@@ -0,0 +1,225 @@
# -*- coding:utf-8 -*-
import io
from typing import Any, List, Dict, Optional
from fastapi import UploadFile
import pandas as pd
from app.core.base_schema import BatchSetAvailable
from app.core.exceptions import CustomException
from app.utils.excel_util import ExcelUtil
from app.core.logger import log
from app.api.v1.module_system.auth.schema import AuthSchema
from .schema import {{ class_name }}CreateSchema, {{ class_name }}UpdateSchema, {{ class_name }}OutSchema
from .param import {{ class_name }}QueryParam
from .crud import {{ class_name }}CRUD
class {{ class_name }}Service:
"""
{{ function_name }}服务层
"""
@classmethod
async def detail_{{ business_name }}_service(cls, auth: AuthSchema, id: int) -> Dict:
"""详情"""
obj = await {{ class_name }}CRUD(auth).get_by_id_{{ business_name }}_crud(id=id)
if not obj:
raise CustomException(msg="该数据不存在")
return {{ class_name }}OutSchema.model_validate(obj).model_dump()
@classmethod
async def list_{{ business_name }}_service(cls, auth: AuthSchema, search: Optional[{{ class_name }}QueryParam] = None, order_by: Optional[List[Dict[str, str]]] = None) -> List[Dict]:
"""列表查询"""
search_dict = search.__dict__ if search else None
obj_list = await {{ class_name }}CRUD(auth).list_{{ business_name }}_crud(search=search_dict, order_by=order_by)
return [{{ class_name }}OutSchema.model_validate(obj).model_dump() for obj in obj_list]
@classmethod
async def page_{{ business_name }}_service(cls, auth: AuthSchema, page_no: int, page_size: int, search: Optional[{{ class_name }}QueryParam] = None, order_by: Optional[List[Dict[str, str]]] = None) -> Dict:
"""分页查询(数据库分页)"""
search_dict = search.__dict__ if search else {}
order_by_list = order_by or [{'id': 'asc'}]
offset = (page_no - 1) * page_size
result = await {{ class_name }}CRUD(auth).page_{{ business_name }}_crud(
offset=offset,
limit=page_size,
order_by=order_by_list,
search=search_dict
)
return result
@classmethod
async def create_{{ business_name }}_service(cls, auth: AuthSchema, data: {{ class_name }}CreateSchema) -> Dict:
"""创建"""
# 检查唯一性约束
{% for column in columns %}
{% if column.is_unique == '1' %}
obj = await {{ class_name }}CRUD(auth).get({{ column.column_name }}=data.{{ column.column_name }})
if obj:
raise CustomException(msg='创建失败,{{ column.column_comment }}已存在')
{% endif %}
{% endfor %}
obj = await {{ class_name }}CRUD(auth).create_{{ business_name }}_crud(data=data)
return {{ class_name }}OutSchema.model_validate(obj).model_dump()
@classmethod
async def update_{{ business_name }}_service(cls, auth: AuthSchema, id: int, data: {{ class_name }}UpdateSchema) -> Dict:
"""更新"""
# 检查数据是否存在
obj = await {{ class_name }}CRUD(auth).get_by_id_{{ business_name }}_crud(id=id)
if not obj:
raise CustomException(msg='更新失败,该数据不存在')
# 检查唯一性约束
{% for column in columns %}
{% if column.is_unique == '1' %}
exist_obj = await {{ class_name }}CRUD(auth).get({{ column.column_name }}=data.{{ column.column_name }})
if exist_obj and exist_obj.id != id:
raise CustomException(msg='更新失败,{{ column.column_comment }}重复')
{% endif %}
{% endfor %}
obj = await {{ class_name }}CRUD(auth).update_{{ business_name }}_crud(id=id, data=data)
return {{ class_name }}OutSchema.model_validate(obj).model_dump()
@classmethod
async def delete_{{ business_name }}_service(cls, auth: AuthSchema, ids: List[int]) -> None:
"""删除"""
if len(ids) < 1:
raise CustomException(msg='删除失败,删除对象不能为空')
for id in ids:
obj = await {{ class_name }}CRUD(auth).get_by_id_{{ business_name }}_crud(id=id)
if not obj:
raise CustomException(msg=f'删除失败,ID为{id}的数据不存在')
await {{ class_name }}CRUD(auth).delete_{{ business_name }}_crud(ids=ids)
@classmethod
async def set_available_{{ business_name }}_service(cls, auth: AuthSchema, data: BatchSetAvailable) -> None:
"""批量设置状态"""
await {{ class_name }}CRUD(auth).set_available_{{ business_name }}_crud(ids=data.ids, status=data.status)
@classmethod
async def batch_export_{{ business_name }}_service(cls, obj_list: List[Dict[str, Any]]) -> bytes:
"""批量导出"""
mapping_dict = {
{% for column in columns %}
'{{ column.column_name }}': '{{ column.column_comment }}',
{% endfor %}
'creator': '创建者',
}
data = obj_list.copy()
for item in data:
# 状态转换
if 'status' in item:
item['status'] = '正常' if item.get('status') else '停用'
# 创建者转换
creator_info = item.get('creator')
if isinstance(creator_info, dict):
item['creator'] = creator_info.get('name', '未知')
elif creator_info is None:
item['creator'] = '未知'
return ExcelUtil.export_list2excel(list_data=data, mapping_dict=mapping_dict)
@classmethod
async def batch_import_{{ business_name }}_service(cls, auth: AuthSchema, file: UploadFile, update_support: bool = False) -> str:
"""批量导入"""
header_dict = {
{% for column in columns %}
'{{ column.column_comment }}': '{{ column.column_name }}',
{% endfor %}
}
try:
contents = await file.read()
df = pd.read_excel(io.BytesIO(contents))
await file.close()
if df.empty:
raise CustomException(msg="导入文件为空")
missing_headers = [header for header in header_dict.keys() if header not in df.columns]
if missing_headers:
raise CustomException(msg=f"导入文件缺少必要的列: {', '.join(missing_headers)}")
df.rename(columns=header_dict, inplace=True)
# 验证必填字段
{% for column in columns %}
{% if column.required == '1' %}
missing_rows = df[df['{{ column.column_name }}'].isnull()].index.tolist()
if missing_rows:
raise CustomException(msg="{{ column.column_comment }}不能为空,第{0}行".format([i+1 for i in missing_rows]))
{% endif %}
{% endfor %}
error_msgs = []
success_count = 0
count = 0
for index, row in df.iterrows():
count += 1
try:
data = {
{% for column in columns %}
"{{ column.column_name }}": row['{{ column.column_name }}'],
{% endfor %}
}
# 使用CreateSchema做校验后入库
create_schema = {{ class_name }}CreateSchema.model_validate(data)
# 检查唯一性约束
{% for column in columns %}
{% if column.is_unique == '1' %}
exists_obj = await {{ class_name }}CRUD(auth).get({{ column.column_name }}=create_schema.{{ column.column_name }})
if exists_obj:
if update_support:
await {{ class_name }}CRUD(auth).update(id=exists_obj.id, data=create_schema)
success_count += 1
else:
error_msgs.append(f"第{count}行: {{ column.column_comment }} {create_schema.{{ column.column_name }}} 已存在")
continue
{% endif %}
{% endfor %}
await {{ class_name }}CRUD(auth).create_crud(data=create_schema)
success_count += 1
except Exception as e:
error_msgs.append(f"第{count}行: {str(e)}")
continue
result = f"成功导入 {success_count} 条数据"
if error_msgs:
result += "\n错误信息:\n" + "\n".join(error_msgs)
return result
except Exception as e:
log.error(f"批量导入失败: {str(e)}")
raise CustomException(msg=f"导入失败: {str(e)}")
@classmethod
async def import_template_download_{{ business_name }}_service(cls) -> bytes:
"""下载导入模板"""
header_list = [
{% for column in columns %}
'{{ column.column_comment }}',
{% endfor %}
]
selector_header_list = []
option_list = []
# 添加下拉选项
{% for column in columns %}
{% if column.html_type == 'select' and column.dict_type %}
selector_header_list.append('{{ column.column_comment }}')
option_list.append({'{{ column.column_comment }}': []})
{% endif %}
{% endfor %}
return ExcelUtil.get_excel_template(
header_list=header_list,
selector_header_list=selector_header_list,
option_list=option_list
)
@@ -0,0 +1,133 @@
-- 统一的菜单 SQL(兼容 MySQL / PostgreSQL),对齐到 system_menu 表结构
{# 布尔值与保留字列名处理 #}
{% set b_true = 1 if db_type == 'mysql' else true %}
{% set b_false = 0 if db_type == 'mysql' else false %}
{% set order_col = '`order`' if db_type == 'mysql' else '"order"' %}
{# 公共字段列表(按实际库字段) #}
{# name, type, order, status, permission, icon, route_name, route_path, component_path, redirect, hidden, keep_alive, always_show, title, params, affix, parent_id, description, created_at, updated_at #}
{% if db_type == 'mysql' %}
-- 父菜单(类型=2:菜单)
INSERT INTO `system_menu` (name, type, {{ order_col }}, status, permission, icon, route_name, route_path, component_path, redirect, hidden, keep_alive, always_show, title, params, affix, parent_id, description, created_at, updated_at)
VALUES (
'{{ function_name }}',
2,
1,
{{ b_true }},
'{{ permission_prefix }}:query',
NULL,
'{{ business_name|snake_to_camel }}',
'/{{ module_name }}/{{ business_name }}',
'{{ module_name }}/{{ business_name }}/index',
NULL,
{{ b_false }},
{{ b_true }},
{{ b_false }},
'{{ function_name }}',
NULL,
{{ b_false }},
{{ parent_menu_id }},
'{{ function_name }}菜单',
now(),
now()
);
-- 获取父菜单IDMySQL
SELECT @parentId := LAST_INSERT_ID();
-- 按钮权限(类型=3:按钮/权限)
INSERT INTO `system_menu` (name, type, {{ order_col }}, status, permission, icon, route_name, route_path, component_path, redirect, hidden, keep_alive, always_show, title, params, affix, parent_id, description, created_at, updated_at)
VALUES ('{{ function_name }}查询', 3, 1, {{ b_true }}, '{{ permission_prefix }}:query', NULL, NULL, NULL, NULL, NULL, {{ b_false }}, {{ b_true }}, {{ b_false }}, '{{ function_name }}查询', NULL, {{ b_false }}, @parentId, NULL, NOW(), NOW());
INSERT INTO `system_menu` (name, type, {{ order_col }}, status, permission, icon, route_name, route_path, component_path, redirect, hidden, keep_alive, always_show, title, params, affix, parent_id, description, created_at, updated_at)
VALUES ('{{ function_name }}新增', 3, 2, {{ b_true }}, '{{ permission_prefix }}:create', NULL, NULL, NULL, NULL, NULL, {{ b_false }}, {{ b_true }}, {{ b_false }}, '{{ function_name }}新增', NULL, {{ b_false }}, @parentId, NULL, NOW(), NOW());
INSERT INTO `system_menu` (name, type, {{ order_col }}, status, permission, icon, route_name, route_path, component_path, redirect, hidden, keep_alive, always_show, title, params, affix, parent_id, description, created_at, updated_at)
VALUES ('{{ function_name }}修改', 3, 3, {{ b_true }}, '{{ permission_prefix }}:update', NULL, NULL, NULL, NULL, NULL, {{ b_false }}, {{ b_true }}, {{ b_false }}, '{{ function_name }}修改', NULL, {{ b_false }}, @parentId, NULL, NOW(), NOW());
INSERT INTO `system_menu` (name, type, {{ order_col }}, status, permission, icon, route_name, route_path, component_path, redirect, hidden, keep_alive, always_show, title, params, affix, parent_id, description, created_at, updated_at)
VALUES ('{{ function_name }}删除', 3, 4, {{ b_true }}, '{{ permission_prefix }}:delete', NULL, NULL, NULL, NULL, NULL, {{ b_false }}, {{ b_true }}, {{ b_false }}, '{{ function_name }}删除', NULL, {{ b_false }}, @parentId, NULL, NOW(), NOW());
INSERT INTO `system_menu` (name, type, {{ order_col }}, status, permission, icon, route_name, route_path, component_path, redirect, hidden, keep_alive, always_show, title, params, affix, parent_id, description, created_at, updated_at)
VALUES ('{{ function_name }}导出', 3, 5, {{ b_true }}, '{{ permission_prefix }}:export', NULL, NULL, NULL, NULL, NULL, {{ b_false }}, {{ b_true }}, {{ b_false }}, '{{ function_name }}导出', NULL, {{ b_false }}, @parentId, NULL, NOW(), NOW());
INSERT INTO `system_menu` (name, type, {{ order_col }}, status, permission, icon, route_name, route_path, component_path, redirect, hidden, keep_alive, always_show, title, params, affix, parent_id, description, created_at, updated_at)
VALUES ('{{ function_name }}导入', 3, 6, {{ b_true }}, '{{ permission_prefix }}:import', NULL, NULL, NULL, NULL, NULL, {{ b_false }}, {{ b_true }}, {{ b_false }}, '{{ function_name }}导入', NULL, {{ b_false }}, @parentId, NULL, NOW(), NOW());
INSERT INTO `system_menu` (name, type, {{ order_col }}, status, permission, icon, route_name, route_path, component_path, redirect, hidden, keep_alive, always_show, title, params, affix, parent_id, description, created_at, updated_at)
VALUES ('{{ function_name }}批量状态修改', 3, 7, {{ b_true }}, '{{ permission_prefix }}:patch', NULL, NULL, NULL, NULL, NULL, {{ b_false }}, {{ b_true }}, {{ b_false }}, '{{ function_name }}批量状态修改', NULL, {{ b_false }}, @parentId, NULL, NOW(), NOW());
INSERT INTO `system_menu` (name, type, {{ order_col }}, status, permission, icon, route_name, route_path, component_path, redirect, hidden, keep_alive, always_show, title, params, affix, parent_id, description, created_at, updated_at)
VALUES ('{{ function_name }}下载导入模板', 3, 8, {{ b_true }}, '{{ permission_prefix }}:download', NULL, NULL, NULL, NULL, NULL, {{ b_false }}, {{ b_true }}, {{ b_false }}, '{{ function_name }}下载导入模板', NULL, {{ b_false }}, @parentId, NULL, NOW(), NOW());
{% elif db_type == 'postgres' %}
-- 菜单 SQLPostgreSQL DO 块方案)
DO $$
DECLARE
parent_id INTEGER;
BEGIN
-- 插入父菜单并获取ID
INSERT INTO public.system_menu (
name, type, {{ order_col }}, status, permission, icon, route_name, route_path,
component_path, redirect, hidden, keep_alive, always_show, title,
params, affix, parent_id, description, created_at, updated_at
)
VALUES (
'{{ function_name }}',
2,
1,
{{ b_true }},
'{{ permission_prefix }}:query',
NULL,
'{{ business_name|snake_to_camel }}',
'/{{ module_name }}/{{ business_name }}',
'{{ module_name }}/{{ business_name }}/index',
NULL,
{{ b_false }},
{{ b_true }},
{{ b_false }},
'{{ function_name }}',
NULL,
{{ b_false }},
{{ parent_menu_id }},
'{{ function_name }}菜单',
NOW(),
NOW()
) RETURNING id INTO parent_id;
-- 插入所有子菜单按钮(单条 INSERT 语句,性能更好)
INSERT INTO public.system_menu (
name, type, {{ order_col }}, status, permission, icon, route_name, route_path,
component_path, redirect, hidden, keep_alive, always_show, title,
params, affix, parent_id, description, created_at, updated_at
) VALUES
('{{ function_name }}查询', 3, 1, {{ b_true }}, '{{ permission_prefix }}:query', NULL, NULL, NULL, NULL, NULL, {{ b_false }}, {{ b_true }}, {{ b_false }}, '{{ function_name }}查询', NULL, {{ b_false }}, parent_id, NULL, NOW(), NOW()),
('{{ function_name }}新增', 3, 2, {{ b_true }}, '{{ permission_prefix }}:create', NULL, NULL, NULL, NULL, NULL, {{ b_false }}, {{ b_true }}, {{ b_false }}, '{{ function_name }}新增', NULL, {{ b_false }}, parent_id, NULL, NOW(), NOW()),
('{{ function_name }}修改', 3, 3, {{ b_true }}, '{{ permission_prefix }}:update', NULL, NULL, NULL, NULL, NULL, {{ b_false }}, {{ b_true }}, {{ b_false }}, '{{ function_name }}修改', NULL, {{ b_false }}, parent_id, NULL, NOW(), NOW()),
('{{ function_name }}删除', 3, 4, {{ b_true }}, '{{ permission_prefix }}:delete', NULL, NULL, NULL, NULL, NULL, {{ b_false }}, {{ b_true }}, {{ b_false }}, '{{ function_name }}删除', NULL, {{ b_false }}, parent_id, NULL, NOW(), NOW()),
('{{ function_name }}导出', 3, 5, {{ b_true }}, '{{ permission_prefix }}:export', NULL, NULL, NULL, NULL, NULL, {{ b_false }}, {{ b_true }}, {{ b_false }}, '{{ function_name }}导出', NULL, {{ b_false }}, parent_id, NULL, NOW(), NOW()),
('{{ function_name }}导入', 3, 6, {{ b_true }}, '{{ permission_prefix }}:import', NULL, NULL, NULL, NULL, NULL, {{ b_false }}, {{ b_true }}, {{ b_false }}, '{{ function_name }}导入', NULL, {{ b_false }}, parent_id, NULL, NOW(), NOW()),
('{{ function_name }}批量状态修改', 3, 7, {{ b_true }}, '{{ permission_prefix }}:patch', NULL, NULL, NULL, NULL, NULL, {{ b_false }}, {{ b_true }}, {{ b_false }}, '{{ function_name }}批量状态修改', NULL, {{ b_false }}, parent_id, NULL, NOW(), NOW()),
('{{ function_name }}下载导入模板', 3, 8, {{ b_true }}, '{{ permission_prefix }}:download', NULL, NULL, NULL, NULL, NULL, {{ b_false }}, {{ b_true }}, {{ b_false }}, '{{ function_name }}下载导入模板', NULL, {{ b_false }}, parent_id, NULL, NOW(), NOW());
-- 可选:输出插入的父菜单ID(调试用)
RAISE NOTICE '{{ function_name }}菜单创建完成,父菜单ID: %', parent_id;
END $$;
{% else %}
-- 未识别的数据库类型,默认按 MySQL 处理
INSERT INTO `system_menu` (name, type, {{ order_col }}, status, permission, icon, route_name, route_path, component_path, redirect, hidden, keep_alive, always_show, title, params, affix, parent_id, description, created_at, updated_at)
VALUES ('{{ function_name }}', 2, 1, {{ b_true }}, '{{ permission_prefix }}:query', NULL, '{{ business_name|snake_to_camel }}', '/{{ module_name }}/{{ business_name }}', '{{ module_name }}/{{ business_name }}/index', NULL, {{ b_false }}, {{ b_true }}, {{ b_false }}, '{{ function_name }}', NULL, {{ b_false }}, {{ parent_menu_id }}, '{{ function_name }}菜单', now(), now());
SELECT @parentId := LAST_INSERT_ID();
INSERT INTO `system_menu` (name, type, {{ order_col }}, status, permission, icon, route_name, route_path, component_path, redirect, hidden, keep_alive, always_show, title, params, affix, parent_id, description, created_at, updated_at)
VALUES ('{{ function_name }}查询', 3, 1, {{ b_true }}, '{{ permission_prefix }}:query', NULL, NULL, NULL, NULL, NULL, {{ b_false }}, {{ b_true }}, {{ b_false }}, '{{ function_name }}查询', NULL, {{ b_false }}, @parentId, NULL, NOW(), NOW());
INSERT INTO `system_menu` (name, type, {{ order_col }}, status, permission, icon, route_name, route_path, component_path, redirect, hidden, keep_alive, always_show, title, params, affix, parent_id, description, created_at, updated_at)
VALUES ('{{ function_name }}新增', 3, 2, {{ b_true }}, '{{ permission_prefix }}:create', NULL, NULL, NULL, NULL, NULL, {{ b_false }}, {{ b_true }}, {{ b_false }}, '{{ function_name }}新增', NULL, {{ b_false }}, @parentId, NULL, NOW(), NOW());
INSERT INTO `system_menu` (name, type, {{ order_col }}, status, permission, icon, route_name, route_path, component_path, redirect, hidden, keep_alive, always_show, title, params, affix, parent_id, description, created_at, updated_at)
VALUES ('{{ function_name }}修改', 3, 3, {{ b_true }}, '{{ permission_prefix }}:update', NULL, NULL, NULL, NULL, NULL, {{ b_false }}, {{ b_true }}, {{ b_false }}, '{{ function_name }}修改', NULL, {{ b_false }}, @parentId, NULL, NOW(), NOW());
INSERT INTO `system_menu` (name, type, {{ order_col }}, status, permission, icon, route_name, route_path, component_path, redirect, hidden, keep_alive, always_show, title, params, affix, parent_id, description, created_at, updated_at)
VALUES ('{{ function_name }}删除', 3, 4, {{ b_true }}, '{{ permission_prefix }}:delete', NULL, NULL, NULL, NULL, NULL, {{ b_false }}, {{ b_true }}, {{ b_false }}, '{{ function_name }}删除', NULL, {{ b_false }}, @parentId, NULL, NOW(), NOW());
INSERT INTO `system_menu` (name, type, {{ order_col }}, status, permission, icon, route_name, route_path, component_path, redirect, hidden, keep_alive, always_show, title, params, affix, parent_id, description, created_at, updated_at)
VALUES ('{{ function_name }}导出', 3, 5, {{ b_true }}, '{{ permission_prefix }}:export', NULL, NULL, NULL, NULL, NULL, {{ b_false }}, {{ b_true }}, {{ b_false }}, '{{ function_name }}导出', NULL, {{ b_false }}, @parentId, NULL, NOW(), NOW());
INSERT INTO `system_menu` (name, type, {{ order_col }}, status, permission, icon, route_name, route_path, component_path, redirect, hidden, keep_alive, always_show, title, params, affix, parent_id, description, created_at, updated_at)
VALUES ('{{ function_name }}导入', 3, 6, {{ b_true }}, '{{ permission_prefix }}:import', NULL, NULL, NULL, NULL, NULL, {{ b_false }}, {{ b_true }}, {{ b_false }}, '{{ function_name }}导入', NULL, {{ b_false }}, @parentId, NULL, NOW(), NOW());
INSERT INTO `system_menu` (name, type, {{ order_col }}, status, permission, icon, route_name, route_path, component_path, redirect, hidden, keep_alive, always_show, title, params, affix, parent_id, description, created_at, updated_at)
VALUES ('{{ function_name }}批量状态修改', 3, 7, {{ b_true }}, '{{ permission_prefix }}:patch', NULL, NULL, NULL, NULL, NULL, {{ b_false }}, {{ b_true }}, {{ b_false }}, '{{ function_name }}批量状态修改', NULL, {{ b_false }}, @parentId, NULL, NOW(), NOW());
INSERT INTO `system_menu` (name, type, {{ order_col }}, status, permission, icon, route_name, route_path, component_path, redirect, hidden, keep_alive, always_show, title, params, affix, parent_id, description, created_at, updated_at)
VALUES ('{{ function_name }}下载导入模板', 3, 8, {{ b_true }}, '{{ permission_prefix }}:download', NULL, NULL, NULL, NULL, NULL, {{ b_false }}, {{ b_true }}, {{ b_false }}, '{{ function_name }}下载导入模板', NULL, {{ b_false }}, @parentId, NULL, NOW(), NOW());
{% endif %}
@@ -0,0 +1,132 @@
import request from "@/utils/request";
const API_PATH = "/{{ module_name }}/{{ business_name|lower }}";
// 参考 demo.ts 的风格,提供标准的 CRUD 与导入/导出 APITypeScript
const {{ class_name }}API = {
// 列表查询
list{{ class_name }}(query: {{ class_name }}PageQuery) {
return request<ApiResponse<PageResult<{{ class_name }}Table[]>>>({
url: `${API_PATH}/list`,
method: "get",
params: query,
});
},
// 详情查询
detail{{ class_name }}(id: number) {
return request<ApiResponse<{{ class_name }}Table>>({
url: `${API_PATH}/detail/${id}`,
method: "get",
});
},
// 新增
create{{ class_name }}(data: {{ class_name }}Form) {
return request<ApiResponse>({
url: `${API_PATH}/create`,
method: "post",
data,
});
},
// 修改(带主键)
update{{ class_name }}(id: number, data: {{ class_name }}Form) {
return request<ApiResponse>({
url: `${API_PATH}/update/${id}`,
method: "put",
data,
});
},
// 删除(支持批量)
delete{{ class_name }}(ids: number[]) {
return request<ApiResponse>({
url: `${API_PATH}/delete`,
method: "delete",
data: ids,
});
},
// 导出
export{{ class_name }}(query: {{ class_name }}PageQuery) {
return request<Blob>({
url: `${API_PATH}/export`,
method: "post",
data: query,
responseType: "blob",
});
},
// 下载导入模板
downloadTemplate{{ class_name }}() {
return request<Blob>({
url: `${API_PATH}/download/template`,
method: "post",
responseType: "blob",
});
},
// 导入
import{{ class_name }}(data: FormData) {
return request<ApiResponse>({
url: `${API_PATH}/import`,
method: "post",
data,
headers: { "Content-Type": "multipart/form-data" },
});
},
// 批量启用/停用
batchAvailable{{ class_name }}(body: { ids: number[]; status: boolean }) {
return request<ApiResponse>({
url: `${API_PATH}/available/setting`,
method: "patch",
data: body,
});
},
};
export default {{ class_name }}API;
// ------------------------------
// TS 类型声明
// ------------------------------
export interface {{ class_name }}PageQuery extends PageQuery {
{% for column in columns %}
{% if column.is_query == "1" and column.query_type != "BETWEEN" %}
{{ column.python_field }}?: {{
'boolean' if ('status' in (column.python_field|lower)) or (column.html_type == 'radio')
else 'number' if column.is_pk == '1'
else 'string'
}};
{% endif %}
{% endfor %}
// 时间范围查询(按示例统一字段名,如需按字段拆分可在页面层处理)
start_time?: string;
end_time?: string;
}
export interface {{ class_name }}Table {
{% for column in columns %}
{{ column.python_field }}?: {{
'boolean' if ('status' in (column.python_field|lower)) or (column.html_type == 'radio')
else 'number' if column.is_pk == '1'
else 'string'
}};
{% endfor %}
creator?: creatorType;
}
export interface {{ class_name }}Form {
id?: number;
{% for column in columns %}
{% if column.is_insert == "1" or column.is_edit == "1" %}
{{ column.python_field }}?: {{
'boolean' if ('status' in (column.python_field|lower)) or (column.html_type == 'radio')
else 'number' if column.is_pk == '1'
else 'string'
}};
{% endif %}
{% endfor %}
}
@@ -0,0 +1,646 @@
<template>
<div class="app-container">
<!-- 搜索区域 -->
<div v-show="visible" class="search-container">
<el-form ref="queryFormRef" :model="queryFormData" label-suffix=":" :inline="true" @submit.prevent="handleQuery">
{% for column in columns %}
{% if column.is_query == "1" %}
{% set dict_type = column.dict_type %}
{% set column_comment = column.column_comment if column.column_comment else '' %}
{% set parentheseIndex = column_comment.find("") %}
{% set comment = column_comment[:parentheseIndex] if parentheseIndex != -1 else column_comment %}
{% if column.html_type == "input" %}
<el-form-item label="{{ comment }}" prop="{{ column.python_field }}">
<el-input v-model="queryFormData.{{ column.python_field }}" placeholder="请输入{{ comment }}" clearable />
</el-form-item>
{% elif (column.html_type == "select" or column.html_type == "radio") and dict_type != "" %}
<el-form-item label="{{ comment }}" prop="{{ column.python_field }}">
<el-select v-model="queryFormData.{{ column.python_field }}" placeholder="请选择{{ comment }}" style="width: 180px" clearable>
<el-option v-for="dict in dictStore.getDictArray('{{ dict_type }}')" :key="dict.dict_value" :label="dict.dict_label" :value="dict.dict_value" />
</el-select>
</el-form-item>
{% elif (column.html_type == "select" or column.html_type == "radio") and dict_type %}
<el-form-item label="{{ comment }}" prop="{{ column.python_field }}">
<el-select v-model="queryFormData.{{ column.python_field }}" placeholder="请选择{{ comment }}" clearable>
<el-option label="请选择字典生成" value="" />
</el-select>
</el-form-item>
{% elif column.html_type == "datetime" and column.query_type != "BETWEEN" %}
<el-form-item label="{{ comment }}" prop="{{ column.python_field }}">
<el-date-picker v-model="queryFormData.{{ column.python_field }}" type="date" value-format="YYYY-MM-DD" clearable placeholder="请选择{{ comment }}" />
</el-form-item>
{% endif %}
{% endif %}
{% endfor %}
<!-- 可选:创建人选择与统一日期范围(展开后显示) -->
<el-form-item v-if="isExpand" prop="creator" label="创建人">
<UserTableSelect v-model="queryFormData.creator" @confirm-click="handleConfirm" @clear-click="handleQuery" />
</el-form-item>
<el-form-item v-if="isExpand" prop="start_time" label="创建时间">
<DatePicker v-model="dateRange" @update:model-value="handleDateRangeChange" />
</el-form-item>
<el-form-item>
<el-button v-hasPerm="['{{ module_name }}:{{ business_name }}:query']" type="primary" icon="search" @click="handleQuery">查询</el-button>
<el-button v-hasPerm="['{{ module_name }}:{{ business_name }}:query']" icon="refresh" @click="handleResetQuery">重置</el-button>
<template v-if="isExpandable">
<el-link class="ml-3" type="primary" underline="never" @click="isExpand = !isExpand">
{{ '{{' }} isExpand ? "收起" : "展开" {{ '}}' }}
<el-icon>
<template v-if="isExpand">
<ArrowUp />
</template>
<template v-else>
<ArrowDown />
</template>
</el-icon>
</el-link>
</template>
</el-form-item>
</el-form>
</div>
<!-- 内容区域 -->
<el-card shadow="hover" class="data-table">
<template #header>
<div class="card-header">
<span>
<el-tooltip content="{{ function_name }}列表">
<QuestionFilled class="w-4 h-4 mx-1" />
</el-tooltip>
{{ function_name }}列表
</span>
</div>
</template>
<!-- 功能区域 -->
<div class="data-table__toolbar">
<div class="data-table__toolbar--left">
<el-row :gutter="10">
<el-col :span="1.5">
<el-button v-hasPerm="['{{ module_name }}:{{ business_name }}:create']" type="success" icon="plus" @click="handleOpenDialog('create')">新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPerm="['{{ module_name }}:{{ business_name }}:delete']" type="danger" icon="delete" :disabled="selectIds.length === 0" @click="handleDelete(selectIds)">批量删除</el-button>
</el-col>
<el-col :span="1.5">
<el-dropdown v-hasPerm="['{{ module_name }}:{{ business_name }}:batch']" trigger="click">
<el-button type="default" :disabled="selectIds.length === 0" icon="ArrowDown">更多</el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item icon="Check" @click="handleMoreClick(true)">批量启用</el-dropdown-item>
<el-dropdown-item icon="CircleClose" @click="handleMoreClick(false)">批量停用</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</el-col>
</el-row>
</div>
<div class="data-table__toolbar--right">
<el-row :gutter="10">
<el-col :span="1.5">
<el-tooltip content="导入">
<el-button v-hasPerm="['{{ module_name }}:{{ business_name }}:import']" type="success" icon="upload" circle @click="handleOpenImportDialog" />
</el-tooltip>
</el-col>
<el-col :span="1.5">
<el-tooltip content="导出">
<el-button v-hasPerm="['{{ module_name }}:{{ business_name }}:export']" type="warning" icon="download" circle @click="handleOpenExportsModal" />
</el-tooltip>
</el-col>
<el-col :span="1.5">
<el-tooltip content="搜索显示/隐藏">
<el-button v-hasPerm="['*:*:*']" type="info" icon="search" circle @click="visible = !visible" />
</el-tooltip>
</el-col>
<el-col :span="1.5">
<el-tooltip content="刷新">
<el-button v-hasPerm="['{{ module_name }}:{{ business_name }}:refresh']" type="primary" icon="refresh" circle @click="handleRefresh" />
</el-tooltip>
</el-col>
<el-col :span="1.5">
<el-popover placement="bottom" trigger="click">
<template #reference>
<el-button type="danger" icon="operation" circle></el-button>
</template>
<el-scrollbar max-height="350px">
<template v-for="column in tableColumns" :key="column.prop">
<el-checkbox v-if="column.prop" v-model="column.show" :label="column.label" />
</template>
</el-scrollbar>
</el-popover>
</el-col>
</el-row>
</div>
</div>
<!-- 表格区域 -->
<el-table
ref="dataTableRef"
v-loading="loading"
:data="pageTableData"
highlight-current-row
class="data-table__content"
:height="450"
border
stripe
@selection-change="handleSelectionChange"
>
<template #empty>
<el-empty :image-size="80" description="暂无数据" />
</template>
<el-table-column v-if="tableColumns.find((col) => col.prop === 'selection')?.show" type="selection" min-width="55" align="center" />
<el-table-column v-if="tableColumns.find((col) => col.prop === 'index')?.show" fixed label="序号" min-width="60">
<template #default="scope">
{{ '{{' }} (queryFormData.page_no - 1) * queryFormData.page_size + scope.$index + 1 {{ '}}' }}
</template>
</el-table-column>
{% for column in columns %}
{% set python_field = column.python_field %}
{% set column_comment = column.column_comment if column.column_comment else '' %}
{% set parentheseIndex = column_comment.find("") %}
{% set comment = column_comment[:parentheseIndex] if parentheseIndex != -1 else column_comment %}
{% if column.is_list == "1" %}
<el-table-column v-if="tableColumns.find((col) => col.prop === '{{ python_field }}')?.show" label="{{ comment }}" prop="{{ python_field }}" min-width="140">
{% if python_field == "status" %}
<template #default="scope">
<el-tag :type="scope.row.status ? 'success' : 'info'">{{ '{{' }} scope.row.status ? '启用' : '停用' {{ '}}' }}</el-tag>
</template>
{% elif python_field == "creator" %}
<template #default="scope">
<el-tag>{{ '{{' }} scope.row.creator?.name {{ '}}' }}</el-tag>
</template>
{% endif %}
</el-table-column>
{% endif %}
{% endfor %}
<el-table-column v-if="tableColumns.find(col => col.prop === 'operation')?.show" fixed="right" label="操作" align="center" min-width="180">
<template #default="scope">
<el-button v-hasPerm="['{{ module_name }}:{{ business_name }}:detail']" type="info" size="small" link icon="document" @click="handleOpenDialog('detail', scope.row.id)">详情</el-button>
<el-button v-hasPerm="['{{ module_name }}:{{ business_name }}:update']" type="primary" size="small" link icon="edit" @click="handleOpenDialog('update', scope.row.id)">编辑</el-button>
<el-button v-hasPerm="['{{ module_name }}:{{ business_name }}:delete']" type="danger" size="small" link icon="delete" @click="handleDelete([scope.row.id])">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页区域 -->
<template #footer>
<pagination v-model:total="total" v-model:page="queryFormData.page_no" v-model:limit="queryFormData.page_size" @pagination="loadingData" />
</template>
</el-card>
<!-- 弹窗区域 -->
<el-dialog v-model="dialogVisible.visible" :title="dialogVisible.title" @close="handleCloseDialog">
<!-- 详情 -->
<template v-if="dialogVisible.type === 'detail'">
<el-descriptions :column="4" border>
{% for column in columns %}
{% set python_field = column.python_field %}
{% set column_comment = column.column_comment if column.column_comment else '' %}
{% set parentheseIndex = column_comment.find("") %}
{% set comment = column_comment[:parentheseIndex] if parentheseIndex != -1 else column_comment %}
<el-descriptions-item label="{{ comment }}" :span="2">
{{ '{' }}{{ '{' }} detailFormData.{{ python_field }} {{ '}' }}{{ '}' }}
</el-descriptions-item>
{% endfor %}
</el-descriptions>
</template>
<!-- 新增、编辑表单 -->
<template v-else>
<el-form ref="dataFormRef" :model="formData" :rules="rules" label-suffix=":" label-width="auto" label-position="right">
{% for column in columns %}
{% if column.is_insert == "1" or column.is_edit == "1" %}
{% set dict_type = column.dict_type %}
{% set column_comment = column.column_comment if column.column_comment else '' %}
{% set parentheseIndex = column_comment.find("") %}
{% set comment = column_comment[:parentheseIndex] if parentheseIndex != -1 else column_comment %}
{% set required = 'true' if column.is_nullable == '1' else 'false' %}
{% if column.python_field == "status" %}
<el-form-item label="状态" prop="status" :required="true">
<el-radio-group v-model="formData.status">
<el-radio :value="true">启用</el-radio>
<el-radio :value="false">停用</el-radio>
</el-radio-group>
</el-form-item>
{% elif column.html_type == "input" %}
<el-form-item label="{{ comment }}" prop="{{ column.python_field }}" :required="{{ required }}">
<el-input v-model="formData.{{ column.python_field }}" placeholder="请输入{{ comment }}" />
</el-form-item>
{% elif column.html_type == "textarea" %}
<el-form-item label="{{ comment }}" prop="{{ column.python_field }}" :required="{{ required }}">
<el-input v-model="formData.{{ column.python_field }}" type="textarea" placeholder="请输入{{ comment }}" rows="4" />
</el-form-item>
{% elif (column.html_type == "select" or column.html_type == "radio") and dict_type != "" %}
<el-form-item label="{{ comment }}" prop="{{ column.python_field }}" :required="{{ required }}">
<el-select v-model="formData.{{ column.python_field }}" placeholder="请选择{{ comment }}">
<el-option v-for="dict in dictStore.getDictArray('{{ dict_type }}')" :key="dict.dict_value" :label="dict.dict_label" :value="dict.dict_value" />
</el-select>
</el-form-item>
{% elif (column.html_type == "select" or column.html_type == "radio") and dict_type %}
<el-form-item label="{{ comment }}" prop="{{ column.python_field }}" :required="{{ required }}">
<el-select v-model="formData.{{ column.python_field }}" placeholder="请选择{{ comment }}">
<el-option label="请选择字典生成" value="" />
</el-select>
</el-form-item>
{% elif column.html_type == "date" %}
<el-form-item label="{{ comment }}" prop="{{ column.python_field }}" :required="{{ required }}">
<el-date-picker v-model="formData.{{ column.python_field }}" type="date" value-format="YYYY-MM-DD" placeholder="请选择{{ comment }}" />
</el-form-item>
{% elif column.html_type == "datetime" %}
<el-form-item label="{{ comment }}" prop="{{ column.python_field }}" :required="{{ required }}">
<el-date-picker v-model="formData.{{ column.python_field }}" type="datetime" value-format="YYYY-MM-DD HH:mm:ss" placeholder="请选择{{ comment }}" />
</el-form-item>
{% elif column.html_type == "checkbox" %}
<el-form-item label="{{ comment }}" prop="{{ column.python_field }}">
<el-checkbox v-model="formData.{{ column.python_field }}">{{ comment }}</el-checkbox>
</el-form-item>
{% elif column.html_type == "imageUpload" %}
<el-form-item label="{{ comment }}" prop="{{ column.python_field }}">
<SingleImageUpload v-model="formData.{{ column.python_field }}" />
</el-form-item>
{% endif %}
{% endif %}
{% endfor %}
</el-form>
</template>
<template #footer>
<div class="dialog-footer">
<el-button @click="handleCloseDialog">取消</el-button>
<el-button v-if="dialogVisible.type !== 'detail'" v-hasPerm="['{{ module_name }}:{{ business_name }}:submit']" type="primary" @click="handleSubmit">确定</el-button>
<el-button v-else v-hasPerm="['{{ module_name }}:{{ business_name }}:detail']" type="primary" @click="handleCloseDialog">确定</el-button>
</div>
</template>
</el-dialog>
<!-- 导入弹窗 -->
<ImportModal
v-model="importDialogVisible"
:content-config="curdContentConfig"
@upload="handleUpload"
/>
<!-- 导出弹窗 -->
<ExportModal
v-model="exportsDialogVisible"
:content-config="curdContentConfig"
:query-params="queryFormData"
:page-data="pageTableData"
:selection-data="selectionRows"
/>
</div>
</template>
<script setup lang="ts">
defineOptions({
name: "{{ class_name }}",
inheritAttrs: false,
});
import { ref, reactive, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { ResultEnum } from '@/enums/api/result.enum'
import { QuestionFilled, ArrowUp, ArrowDown, Check, CircleClose } from '@element-plus/icons-vue'
import { formatToDateTime } from '@/utils/dateUtil'
import {{ class_name }}API, { {{ class_name }}PageQuery, {{ class_name }}Table, {{ class_name }}Form } from '@/api/{{ module_name }}/{{ business_name }}'
import { useDictStore } from '@/store/index'
import SingleImageUpload from '@/components/Upload/SingleImageUpload.vue'
import ImportModal from '@/components/CURD/ImportModal.vue'
import ExportModal from '@/components/CURD/ExportModal.vue'
import DatePicker from '@/components/DatePicker/index.vue'
import type { IContentConfig } from '@/components/CURD/types'
const visible = ref(true)
const isExpand = ref(false)
const isExpandable = ref(true)
const queryFormRef = ref()
const dataFormRef = ref()
const total = ref(0)
const selectIds = ref<number[]>([])
const selectionRows = ref<{{ class_name }}Table[]>([]);
const loading = ref(false)
// 字典仓库与需要加载的字典类型
const dictStore = useDictStore()
const dictTypes = [
{% for column in columns %}
{% if column.dict_type %}
'{{ column.dict_type }}',
{% endif %}
{% endfor %}
]
// 分页表单
const pageTableData = ref<{{ class_name }}Table[]>([]);
// 表格列配置(根据列生成,可显隐)
const tableColumns = ref([
{ prop: 'selection', label: '选择框', show: true },
{ prop: 'index', label: '序号', show: true },
{% for column in columns %}
{% if column.is_list == "1" %}
{ prop: '{{ column.python_field }}', label: '{{ column.column_comment or column.python_field }}', show: true },
{% endif %}
{% endfor %}
{ prop: 'operation', label: '操作', show: true }
])
// 导出列(不含选择/序号/操作)
const exportColumns = [
{% for column in columns %}
{% if column.is_list == "1" %}
{ prop: '{{ column.python_field }}', label: '{{ column.column_comment or column.python_field }}' },
{% endif %}
{% endfor %}
]
// 导入/导出配置
const curdContentConfig = {
permPrefix: '{{ module_name }}:{{ business_name }}',
cols: exportColumns as any,
importTemplate: () => {{ class_name }}API.downloadTemplate{{ class_name }}(),
exportsAction: async (params: any) => {
const query: any = { ...params };
if (typeof query.status === 'string') {
query.status = query.status === 'true';
}
query.page_no = 1;
query.page_size = 9999;
const all: any[] = [];
while (true) {
const res = await {{ class_name }}API.list{{ class_name }}(query)
const items = res.data?.data?.items || []
const total = res.data?.data?.total || 0
all.push(...items)
if (all.length >= total || items.length === 0) break
query.page_no += 1
}
return all;
},
} as unknown as IContentConfig
// 弹窗状态
const dialogVisible = reactive({
title: '',
visible: false,
type: 'create', // 'create' | 'update' | 'detail'
})
// 编辑表单
const formData = reactive<{{ class_name }}Form>({
id: undefined,
{% for column in columns %}
{% if column.is_insert == "1" or column.is_edit == "1" %}
{{ column.python_field }}: undefined,
{% endif %}
{% endfor %}
})
// 定义初始表单数据常量
const initialFormData: {{ class_name }}Form = {
id: undefined,
{% for column in columns %}
{% if column.is_insert == "1" or column.is_edit == "1" %}
{{ column.python_field }}: {{ 'true' if column.python_field == 'status' else ('' if column.html_type == 'textarea' else 'undefined') }},
{% endif %}
{% endfor %}
}
// 重置表单
async function resetForm() {
if (dataFormRef.value) {
dataFormRef.value.resetFields();
dataFormRef.value.clearValidate();
}
// 完全重置 formData 为初始状态
Object.assign(formData, initialFormData);
}
// 表单验证规则(必填项按 is_nullable 生成)
const rules = reactive({
{% for column in columns %}
{% if column.is_insert == "1" or column.is_edit == "1" %}
{% set required = 'true' if column.is_nullable == '1' else 'false' %}
{{ column.python_field }}: [
{ required: {{ required }}, message: '请输入{{ column.column_comment or column.python_field }}', trigger: 'blur' },
],
{% endif %}
{% endfor %}
})
// 详情表单
const detailFormData = ref<{{ class_name }}Table>({});
// 统一日期范围
const dateRange = ref<[Date, Date] | []>([]);
function handleDateRangeChange(range: [Date, Date]) {
dateRange.value = range;
if (range && range.length === 2) {
queryFormData.start_time = formatToDateTime(range[0]);
queryFormData.end_time = formatToDateTime(range[1]);
} else {
queryFormData.start_time = undefined;
queryFormData.end_time = undefined;
}
}
// 查询参数
const queryFormData = reactive<{{ class_name }}PageQuery>({
page_no: 1,
page_size: 10,
{% for column in columns %}
{% if column.is_query == "1" and column.query_type != "BETWEEN" %}
{{ column.python_field }}: undefined,
{% endif %}
{% endfor %}
start_time: undefined,
end_time: undefined,
creator: undefined,
})
// 加载表格数据
async function loadingData() {
loading.value = true;
try {
const response = await {{ class_name }}API.list{{ class_name }}(queryFormData);
pageTableData.value = response.data.data.items;
total.value = response.data.data.total;
} catch (error) {
console.error(error);
} finally {
loading.value = false;
}
}
// 查询(重置页码后获取数据)
async function handleQuery() {
queryFormData.page_no = 1;
loadingData();
}
// 选择创建人后触发查询
function handleConfirm() {
handleQuery()
}
// 重置查询
async function handleResetQuery() {
queryFormRef.value.resetFields();
queryFormData.page_no = 1;
dateRange.value = [];
queryFormData.start_time = undefined;
queryFormData.end_time = undefined;
loadingData();
}
// 行复选框选中项变化
function handleSelectionChange(selection: any[]) {
selectIds.value = selection.map((item: any) => item.id)
selectionRows.value = selection
}
// 关闭弹窗
async function handleCloseDialog() {
dialogVisible.visible = false;
resetForm();
}
// 打开弹窗
async function handleOpenDialog(type: 'create' | 'update' | 'detail', id?: number) {
dialogVisible.type = type
if (id) {
const response = await {{ class_name }}API.detail{{ class_name }}(id);
if (type === 'detail') {
dialogVisible.title = '详情';
Object.assign(detailFormData.value, response.data.data);
} else if (type === 'update') {
dialogVisible.title = '修改';
Object.assign(formData, response.data.data);
}
} else {
dialogVisible.title = '新增{{ function_name }}';
formData.id = undefined;
}
dialogVisible.visible = true;
}
// 提交表单
async function handleSubmit() {
dataFormRef.value.validate(async (valid: any) => {
if (valid) {
loading.value = true
try {
const id = formData.id
if (id) {
await {{ class_name }}API.update{{ class_name }}(id, { id, ...formData });
dialogVisible.visible = false;
resetForm();
handleCloseDialog();
handleResetQuery();
} else {
await {{ class_name }}API.create{{ class_name }}(formData);
dialogVisible.visible = false;
resetForm();
handleCloseDialog();
handleResetQuery();
}
} catch (error) {
console.error(error)
} finally {
loading.value = false
}
}
})
}
// 删除、批量删除
async function handleDelete(ids: number[]) {
ElMessageBox.confirm('确认删除该项数据?', '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
})
.then(async () => {
try {
loading.value = true;
await {{ class_name }}API.delete{{ class_name }}(ids);
handleResetQuery()
} catch (error) {
console.error(error)
} finally {
loading.value = false
}
})
.catch(() => {
ElMessageBox.close()
})
}
// 批量启用/停用
async function handleMoreClick(status: boolean) {
if (selectIds.value.length) {
ElMessageBox.confirm(`确认${status ? '启用' : '停用'}该项数据?`, '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}).then(async () => {
try {
loading.value = true
await {{ class_name }}API.batchAvailable{{ class_name }}({ ids: selectIds.value, status });
handleResetQuery()
} catch (error) {
console.error(error)
} finally {
loading.value = false
}
}).catch(() => {
ElMessageBox.close()
})
}
}
// 导入弹窗显示状态
const importDialogVisible = ref(false)
// 导出弹窗显示状态
const exportsDialogVisible = ref(false)
// 打开导入弹窗
function handleOpenImportDialog() {
importDialogVisible.value = true
}
// 打开导出弹窗
function handleOpenExportsModal() {
exportsDialogVisible.value = true
}
// 处理上传
const handleUpload = async (formData: FormData) => {
try {
const response = await {{ class_name }}API.import{{ class_name }}(formData);
if (response.data.code === ResultEnum.SUCCESS) {
ElMessage.success(`${response.data.msg}${response.data.data}`)
importDialogVisible.value = false
await handleQuery()
}
} catch (error) {
console.error(error)
}
}
// 列表刷新
async function handleRefresh() {
await loadingData()
}
onMounted(async () => {
// 预加载字典数据
if (dictTypes.length > 0) {
await dictStore.getDict(dictTypes)
}
loadingData()
})
</script>
<style lang="scss" scoped></style>
@@ -0,0 +1,228 @@
# -*- coding: utf-8 -*-
import re
from typing import List
from app.common.constant import GenConstant
from app.utils.string_util import StringUtil
from app.api.v1.module_generator.gencode.schema import GenTableOutSchema, GenTableSchema, GenTableColumnSchema
class GenUtils:
"""代码生成器工具类"""
@classmethod
def init_table(cls, gen_table: GenTableSchema) -> None:
"""
初始化表信息
参数:
- gen_table (GenTableSchema): 业务表对象
返回:
- None
"""
# 只有当字段为None时才设置默认值
gen_table.class_name = cls.convert_class_name(gen_table.table_name or "")
gen_table.package_name = 'module_gencode'
gen_table.module_name = gen_table.package_name.split('.')[-1]
gen_table.business_name = gen_table.table_name
gen_table.function_name = re.sub(r'(?:表|测试)', '', gen_table.table_comment or "")
@classmethod
def init_column_field(cls, column: GenTableColumnSchema, table: GenTableOutSchema) -> None:
"""
初始化列属性字段
参数:
- column (GenTableColumnSchema): 业务表字段对象
- table (GenTableOutSchema): 业务表对象
返回:
- None
"""
data_type = cls.get_db_type(column.column_type or "")
column_name = column.column_name or ""
if not table.id:
raise ValueError("业务表ID不能为空")
column.table_id = table.id
column.python_field = cls.to_camel_case(column_name)
# 只有当python_type为None时才设置默认类型
column.python_type = StringUtil.get_mapping_value_by_key_ignore_case(GenConstant.DB_TO_PYTHON, data_type)
if column.column_length is None:
column.column_length = ''
if column.column_default is None:
column.column_default = ''
if column.html_type is None:
if cls.arrays_contains(GenConstant.COLUMNTYPE_STR, data_type) or cls.arrays_contains(
GenConstant.COLUMNTYPE_TEXT, data_type
):
# 字符串长度超过500设置为文本域
column_length = cls.get_column_length(column.column_type or "")
html_type = (
GenConstant.HTML_TEXTAREA
if column_length >= 500 or cls.arrays_contains(GenConstant.COLUMNTYPE_TEXT, data_type)
else GenConstant.HTML_INPUT
)
column.html_type = html_type
elif cls.arrays_contains(GenConstant.COLUMNTYPE_TIME, data_type):
column.html_type = GenConstant.HTML_DATETIME
elif cls.arrays_contains(GenConstant.COLUMNTYPE_NUMBER, data_type):
column.html_type = GenConstant.HTML_INPUT
elif column_name.lower().endswith("status"):
column.html_type = GenConstant.HTML_RADIO
elif column_name.lower().endswith("type") or column_name.lower().endswith("sex"):
column.html_type = GenConstant.HTML_SELECT
elif column_name.lower().endswith("image"):
column.html_type = GenConstant.HTML_IMAGE_UPLOAD
elif column_name.lower().endswith("file"):
column.html_type = GenConstant.HTML_FILE_UPLOAD
elif column_name.lower().endswith("content"):
column.html_type = GenConstant.HTML_EDITOR
else:
column.html_type = GenConstant.HTML_INPUT
# 只有当is_insert为None时才设置插入字段(默认所有字段都需要插入)
if column.is_insert:
column.is_insert = GenConstant.REQUIRE
else:
column.is_insert = False
# 只有当is_edit为None时才设置编辑字段
if not cls.arrays_contains(GenConstant.COLUMNNAME_NOT_EDIT, column_name) and not column.is_pk:
column.is_edit = GenConstant.REQUIRE
else:
column.is_edit = False
# 只有当is_list为None时才设置列表字段
if not cls.arrays_contains(GenConstant.COLUMNNAME_NOT_LIST, column_name) and not column.is_pk:
column.is_list = GenConstant.REQUIRE
else:
column.is_list = False
# 只有当is_query为None时才设置查询字段
if not cls.arrays_contains(GenConstant.COLUMNNAME_NOT_QUERY, column_name) and not column.is_pk:
column.is_query = GenConstant.REQUIRE
# 直接设置查询类型,因为我们已经确定这是一个查询字段
if column_name.lower().endswith('name') or data_type in ['varchar', 'char', 'text']:
column.query_type = GenConstant.QUERY_LIKE
else:
column.query_type = GenConstant.QUERY_EQ
else:
column.is_query = False
column.query_type = None
@classmethod
def arrays_contains(cls, arr, target_value) -> bool:
"""
检查目标值是否在数组中
注意从根本上解决问题现在确保传入的参数都是正确的类型
- arr 是列表类型且在GenConstant中定义
- target_value 不会是None
参数:
- arr: 数组类型
- target_value: 目标值
返回:
- bool: 如果目标值在数组中返回True否则返回False
"""
# 从根本上解决问题,不再需要复杂的防御性检查
# 因为现在我们确保传入的arr是GenConstant中定义的列表常量
# 并且target_value在调用前已经被处理过不会是None
# 简单直接地执行包含检查
target_str = str(target_value).lower()
return any(str(item).lower() == target_str for item in arr)
@classmethod
def convert_class_name(cls, table_name: str) -> str:
"""
表名转换成 Python 类名
参数:
- table_name (str): 业务表名
返回:
- str: Python 类名
"""
return StringUtil.convert_to_camel_case(table_name)
@classmethod
def replace_first(cls, input_string: str, search_list: List[str]) -> str:
"""
批量替换前缀
参数:
- input_string (str): 需要被替换的字符串
- search_list (List[str]): 可替换的字符串列表
返回:
- str: 替换后的字符串
"""
for search_string in search_list:
if input_string.startswith(search_string):
return input_string.replace(search_string, '', 1)
return input_string
@classmethod
def get_db_type(cls, column_type: str) -> str:
"""
获取数据库类型字段
参数:
- column_type (str): 字段类型
返回:
- str: 数据库类型
"""
if '(' in column_type:
return column_type.split('(')[0]
return column_type
@classmethod
def get_column_length(cls, column_type: str) -> int:
"""
获取字段长度
参数:
- column_type (str): 字段类型例如 'varchar(255)' 'decimal(10,2)'
返回:
- int: 字段长度优先取第一个长度值无法解析时返回0
"""
if '(' in column_type:
length = len(column_type.split('(')[1].split(')')[0])
return length
return 0
@classmethod
def split_column_type(cls, column_type: str) -> List[str]:
"""
拆分列类型
参数:
- column_type (str): 字段类型
返回:
- List[str]: 拆分结果
"""
if '(' in column_type and ')' in column_type:
return column_type.split('(')[1].split(')')[0].split(',')
return []
@classmethod
def to_camel_case(cls, text: str) -> str:
"""
将字符串转换为驼峰命名
param text: 需要转换的字符串
:return: 驼峰命名
"""
parts = text.split('_')
return parts[0] + ''.join(word.capitalize() for word in parts[1:])
@@ -0,0 +1,395 @@
# -*- coding:utf-8 -*-
from datetime import datetime
from jinja2.environment import Environment
from jinja2 import Environment, FileSystemLoader, select_autoescape, Template
from typing import List, Any, Set
from app.common.constant import GenConstant
from app.config.path_conf import TEMPLATE_DIR
from app.config.setting import settings
from app.utils.common_util import CamelCaseUtil, SnakeCaseUtil
from app.utils.string_util import StringUtil
from app.api.v1.module_generator.gencode.schema import GenTableOutSchema, GenTableColumnOutSchema
class Jinja2TemplateUtil:
"""
模板处理工具类
"""
# 项目路径
FRONTEND_PROJECT_PATH = 'frontend'
BACKEND_PROJECT_PATH = 'backend'
# 默认上级菜单,系统工具
DEFAULT_PARENT_MENU_ID = "3"
# 环境对象
_env = None
@classmethod
def get_env(cls):
"""
获取模板环境对象
参数:
-
返回:
- Environment: Jinja2 环境对象
"""
try:
if cls._env is None:
cls._env = Environment(
loader=FileSystemLoader(TEMPLATE_DIR),
autoescape=False, # 自动转义HTML
trim_blocks=True, # 删除多余的空行
lstrip_blocks=True, # 删除行首空格
keep_trailing_newline=True, # 保留行尾换行符
enable_async=True, # 开启异步支持
)
cls._env.filters.update(
{
'camel_to_snake': SnakeCaseUtil.camel_to_snake,
'snake_to_camel': CamelCaseUtil.snake_to_camel,
'get_sqlalchemy_type': cls.get_sqlalchemy_type,
}
)
return cls._env
except Exception as e:
raise RuntimeError(f'初始化Jinja2模板引擎失败: {e}')
@classmethod
def get_template(cls, template_path: str) -> Template:
"""
获取模板
参数:
- template_path (str): 模板路径
返回:
- Template: Jinja2 模板对象
异常:
- TemplateNotFound: 模板未找到时抛出
"""
return cls.get_env().get_template(template_path)
@classmethod
def prepare_context(cls, gen_table: GenTableOutSchema) -> dict[str, Any]:
"""
准备模板变量
参数:
- gen_table (GenTableOutSchema): 生成表的配置信息
返回:
- Dict[str, Any]: 模板上下文字典
"""
# 处理options为None的情况
# if not gen_table.options:
# raise ValueError('请先完善生成配置信息')
class_name = gen_table.class_name or ''
module_name = gen_table.module_name or ''
business_name = gen_table.business_name or ''
package_name = gen_table.package_name or ''
function_name = gen_table.function_name or ''
context = {
'table_name': gen_table.table_name or '',
'table_comment': gen_table.table_comment or '',
'function_name': function_name if StringUtil.is_not_empty(function_name) else '【请填写功能名称】',
'class_name': class_name,
'module_name': module_name,
'business_name': business_name,
'base_package': cls.get_package_prefix(package_name),
'package_name': package_name,
'datetime': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
'pk_column': gen_table.pk_column,
'model_import_list': cls.get_model_import_list(gen_table),
'schema_import_list': cls.get_schema_import_list(gen_table),
'permission_prefix': cls.get_permission_prefix(module_name, business_name),
'columns': gen_table.columns or [],
'table': gen_table,
'dicts': cls.get_dicts(gen_table),
'db_type': settings.DATABASE_TYPE,
'column_not_add_show': GenConstant.COLUMNNAME_NOT_ADD_SHOW,
'column_not_edit_show': GenConstant.COLUMNNAME_NOT_EDIT_SHOW,
'parent_menu_id': int(gen_table.parent_menu_id) if gen_table.parent_menu_id is not None else int(cls.DEFAULT_PARENT_MENU_ID),
}
return context
@classmethod
def get_template_list(cls):
"""
获取模板列表
参数:
-
返回:
- List[str]: 模板路径列表
"""
templates = [
'python/controller.py.j2',
'python/service.py.j2',
'python/crud.py.j2',
'python/schema.py.j2',
'python/param.py.j2',
'python/model.py.j2',
'sql/sql.sql.j2',
'ts/api.ts.j2',
'vue/index.vue.j2',
]
return templates
@classmethod
def get_file_name(cls, template: str, gen_table: GenTableOutSchema):
"""
根据模板生成文件名
参数:
- template (str): 模板路径字符串
- gen_table (GenTableOutSchema): 生成表的配置信息
返回:
- str: 模板生成的文件名
异常:
- ValueError: 当无法生成有效文件名时抛出
"""
module_name = gen_table.module_name or ''
business_name = gen_table.business_name or ''
# 验证必要的参数
if not module_name or not business_name:
raise ValueError(f"无法为模板 {template} 生成文件名:模块名或业务名未设置")
# 映射表方式简化
template_mapping = {
'controller.py.j2': f'{cls.BACKEND_PROJECT_PATH}/app/api/v1/{module_name}/{business_name}/controller.py',
'service.py.j2': f'{cls.BACKEND_PROJECT_PATH}/app/api/v1/{module_name}/{business_name}/service.py',
'crud.py.j2': f'{cls.BACKEND_PROJECT_PATH}/app/api/v1/{module_name}/{business_name}/crud.py',
'model.py.j2': f'{cls.BACKEND_PROJECT_PATH}/app/api/v1/{module_name}/{business_name}/model.py',
'param.py.j2': f'{cls.BACKEND_PROJECT_PATH}/app/api/v1/{module_name}/{business_name}/param.py',
'schema.py.j2': f'{cls.BACKEND_PROJECT_PATH}/app/api/v1/{module_name}/{business_name}/schema.py',
'sql.sql.j2': f'{cls.BACKEND_PROJECT_PATH}/sql/menu/{module_name}/{business_name}.sql',
'api.ts.j2': f'{cls.FRONTEND_PROJECT_PATH}/src/api/{module_name}/{business_name}.ts',
'index.vue.j2': f'{cls.FRONTEND_PROJECT_PATH}/src/views/{module_name}/{business_name}/index.vue'
}
# 查找匹配的模板路径
for key, path in template_mapping.items():
if key in template:
return path
# 默认处理
template_name = template.split('/')[-1].replace('.j2', '')
return f'{cls.BACKEND_PROJECT_PATH}/generated/{template_name}'
@classmethod
def get_package_prefix(cls, package_name: str) -> str:
"""
获取包前缀
参数:
- package_name (str): 包名
返回:
- str: 包前缀
"""
# 修复:当包名中不存在'.'时,直接返回原包名
return package_name[: package_name.rfind('.')] if '.' in package_name else package_name
@classmethod
def get_schema_import_list(cls, gen_table: GenTableOutSchema):
"""
获取schema模板导入包列表
:param gen_table: 生成表的配置信息
:return: 导入包列表
"""
columns = gen_table.columns or []
import_list = set()
for column in columns:
if column.python_type in GenConstant.TYPE_DATE:
import_list.add(f'from datetime import {column.python_type}')
elif column.python_type == GenConstant.TYPE_DECIMAL:
import_list.add('from decimal import Decimal')
if gen_table.sub:
if gen_table.sub_table and gen_table.sub_table.columns:
sub_columns = gen_table.sub_table.columns or []
for sub_column in sub_columns:
if sub_column.python_type in GenConstant.TYPE_DATE:
import_list.add(f'from datetime import {sub_column.python_type}')
elif sub_column.python_type == GenConstant.TYPE_DECIMAL:
import_list.add('from decimal import Decimal')
return cls.merge_same_imports(list(import_list), 'from datetime import')
@classmethod
def get_model_import_list(cls, gen_table: GenTableOutSchema):
"""
获取do模板导入包列表
:param gen_table: 生成表的配置信息
:return: 导入包列表
"""
columns = gen_table.columns or []
import_list = set()
for column in columns:
if column.column_type:
data_type = cls.get_db_type(column.column_type)
if data_type in GenConstant.COLUMNTYPE_GEOMETRY:
import_list.add('from geoalchemy2 import Geometry')
import_list.add(
f'from sqlalchemy import {StringUtil.get_mapping_value_by_key_ignore_case(GenConstant.DB_TO_SQLALCHEMY, data_type)}'
)
if gen_table.sub:
import_list.add('from sqlalchemy import ForeignKey')
if gen_table.sub_table and gen_table.sub_table.columns:
sub_columns = gen_table.sub_table.columns or []
for sub_column in sub_columns:
if sub_column.column_type:
data_type = cls.get_db_type(sub_column.column_type)
import_list.add(
f'from sqlalchemy import {StringUtil.get_mapping_value_by_key_ignore_case(GenConstant.DB_TO_SQLALCHEMY, data_type)}'
)
return cls.merge_same_imports(list(import_list), 'from sqlalchemy import')
@classmethod
def get_db_type(cls, column_type: str) -> str:
"""
获取数据库字段类型
参数:
- column_type (str): 字段类型字符串
返回:
- str: 数据库类型去除长度等修饰
"""
if '(' in column_type:
return column_type.split('(')[0]
return column_type
@classmethod
def merge_same_imports(cls, imports: List[str], import_start: str) -> List[str]:
"""
合并相同的导入语句
参数:
- imports (List[str]): 导入语句列表
- import_start (str): 导入语句的起始字符串
返回:
- List[str]: 合并后的导入语句列表
"""
merged_imports = []
_imports = []
for import_stmt in imports:
if import_stmt.startswith(import_start):
imported_items = import_stmt.split('import')[1].strip()
_imports.extend(imported_items.split(', '))
else:
merged_imports.append(import_stmt)
if _imports:
merged_datetime_import = f'{import_start} {", ".join(_imports)}'
merged_imports.append(merged_datetime_import)
return merged_imports
@classmethod
def get_dicts(cls, gen_table: GenTableOutSchema):
"""
获取字典列表
参数:
- gen_table (GenTableOutSchema): 生成表的配置信息
返回:
- str: 以逗号分隔的字典类型字符串
"""
columns = gen_table.columns or []
dicts = set()
cls.add_dicts(dicts, columns)
# 处理sub_table为None的情况
if gen_table.sub_table is not None:
# 处理sub_table.columns为None的情况
sub_columns = gen_table.sub_table.columns or []
cls.add_dicts(dicts, sub_columns)
return ', '.join(dicts)
@classmethod
def add_dicts(cls, dicts: Set[str], columns: List[GenTableColumnOutSchema]):
"""
添加字典类型到集合
参数:
- dicts (Set[str]): 字典类型集合
- columns (List[GenTableColumnOutSchema]): 字段列表
返回:
- Set[str]: 更新后的字典类型集合
"""
for column in columns:
super_column = column.super_column if column.super_column is not None else '0'
dict_type = column.dict_type or ''
html_type = column.html_type or ''
if (
not super_column
and StringUtil.is_not_empty(dict_type)
and StringUtil.equals_any_ignore_case(
html_type, [GenConstant.HTML_SELECT, GenConstant.HTML_RADIO, GenConstant.HTML_CHECKBOX]
)
):
dicts.add(f"'{dict_type}'")
@classmethod
def get_permission_prefix(cls, module_name: str | None, business_name: str | None) -> str:
"""
获取权限前缀
参数:
- module_name (str | None): 模块名
- business_name (str | None): 业务名
返回:
- str: 权限前缀字符串
"""
return f'{module_name}:{business_name}'
@classmethod
def get_sqlalchemy_type(cls, column):
"""
获取 SQLAlchemy 类型
参数:
- column_type (Any): 列类型或包含 `column_type` 属性的对象
返回:
- str: SQLAlchemy 类型字符串
"""
if '(' in column:
column_type_list = column.split('(')
if column_type_list[0] in GenConstant.COLUMNTYPE_STR:
sqlalchemy_type = (
StringUtil.get_mapping_value_by_key_ignore_case(
GenConstant.DB_TO_SQLALCHEMY, column_type_list[0]
)
+ '('
+ column_type_list[1]
)
else:
sqlalchemy_type = StringUtil.get_mapping_value_by_key_ignore_case(
GenConstant.DB_TO_SQLALCHEMY, column_type_list[0]
)
else:
sqlalchemy_type = StringUtil.get_mapping_value_by_key_ignore_case(
GenConstant.DB_TO_SQLALCHEMY, column
)
return sqlalchemy_type
+8 -8
View File
@@ -8,7 +8,7 @@ from app.common.response import SuccessResponse
from app.core.exceptions import CustomException
from app.core.router_class import OperationLogRoute
from app.core.dependencies import AuthPermission, redis_getter
from app.core.logger import logger
from app.core.logger import log
from .service import CacheService
@@ -32,7 +32,7 @@ async def get_monitor_cache_info_controller(
- JSONResponse: 包含缓存监控统计信息的JSON响应
"""
result = await CacheService.get_cache_monitor_statistical_info_service(redis=redis)
logger.info('获取缓存监控信息成功')
log.info('获取缓存监控信息成功')
return SuccessResponse(data=result, msg='获取缓存监控信息成功')
@@ -50,7 +50,7 @@ async def get_monitor_cache_name_controller() -> JSONResponse:
- JSONResponse: 包含缓存名称列表的JSON响应
"""
result = await CacheService.get_cache_monitor_cache_name_service()
logger.info('获取缓存名称列表成功')
log.info('获取缓存名称列表成功')
return SuccessResponse(data=result, msg='获取缓存名称列表成功')
@@ -74,7 +74,7 @@ async def get_monitor_cache_key_controller(
- JSONResponse: 包含缓存键名列表的JSON响应
"""
result = await CacheService.get_cache_monitor_cache_key_service(redis=redis, cache_name=cache_name)
logger.info(f'获取缓存{cache_name}的键名列表成功')
log.info(f'获取缓存{cache_name}的键名列表成功')
return SuccessResponse(data=result, msg=f'获取缓存{cache_name}的键名列表成功')
@@ -100,7 +100,7 @@ async def get_monitor_cache_value_controller(
- JSONResponse: 包含缓存值的JSON响应
"""
result = await CacheService.get_cache_monitor_cache_value_service(redis=redis, cache_name=cache_name, cache_key=cache_key)
logger.info(f'获取缓存{cache_name}:{cache_key}的值成功')
log.info(f'获取缓存{cache_name}:{cache_key}的值成功')
return SuccessResponse(data=result, msg=f'获取缓存{cache_name}:{cache_key}的值成功')
@@ -126,7 +126,7 @@ async def clear_monitor_cache_name_controller(
result = await CacheService.clear_cache_monitor_cache_name_service(redis=redis, cache_name=cache_name)
if not result:
raise CustomException(msg='清除缓存失败', data=result)
logger.info(f'清除缓存{cache_name}成功')
log.info(f'清除缓存{cache_name}成功')
return SuccessResponse(msg=f'{cache_name}对应键值清除成功', data=result)
@@ -152,7 +152,7 @@ async def clear_monitor_cache_key_controller(
result = await CacheService.clear_cache_monitor_cache_key_service(redis=redis, cache_key=cache_key)
if not result:
raise CustomException(msg='清除缓存失败', data=result)
logger.info(f'清除缓存键{cache_key}成功')
log.info(f'清除缓存键{cache_key}成功')
return SuccessResponse(msg=f'{cache_key}清除成功', data=result)
@@ -174,5 +174,5 @@ async def clear_monitor_cache_all_controller(
result = await CacheService.clear_cache_monitor_all_service(redis=redis)
if not result:
raise CustomException(msg='清除缓存失败', data=result)
logger.info('清除所有缓存成功')
log.info('清除所有缓存成功')
return SuccessResponse(msg='所有缓存清除成功', data=result)
@@ -9,7 +9,7 @@ from app.common.response import SuccessResponse,ErrorResponse
from app.core.dependencies import AuthPermission, redis_getter
from app.core.base_params import PaginationQueryParam
from app.core.router_class import OperationLogRoute
from app.core.logger import logger
from app.core.logger import log
from .param import OnlineQueryParam
from .service import OnlineService
@@ -42,7 +42,7 @@ async def get_online_list_controller(
"""
result_dict_list = await OnlineService.get_online_list_service(redis=redis, search=search)
result_dict = await PaginationService.paginate(data_list= result_dict_list, page_no= paging_query.page_no, page_size = paging_query.page_size)
logger.info('获取成功')
log.info('获取成功')
return SuccessResponse(data=result_dict,msg='获取成功')
@@ -69,10 +69,10 @@ async def delete_online_controller(
"""
is_ok = await OnlineService.delete_online_service(redis=redis, session_id=session_id)
if is_ok:
logger.info("强制下线成功")
log.info("强制下线成功")
return SuccessResponse(msg="强制下线成功")
else:
logger.info("强制下线失败")
log.info("强制下线失败")
return ErrorResponse(msg="强制下线失败")
@OnlineRouter.delete(
@@ -95,8 +95,8 @@ async def clear_online_controller(
"""
is_ok = await OnlineService.clear_online_service(redis=redis)
if is_ok:
logger.info("清除所有在线用户成功")
log.info("清除所有在线用户成功")
return SuccessResponse(msg="清除所有在线用户成功")
else:
logger.info("清除所有在线用户失败")
log.info("清除所有在线用户失败")
return ErrorResponse(msg="清除所有在线用户失败")
@@ -7,7 +7,7 @@ from redis.asyncio.client import Redis
from app.common.enums import RedisInitKeyConfig
from app.core.redis_crud import RedisCURD
from app.core.security import decode_access_token
from app.core.logger import logger
from app.core.logger import log
from .param import OnlineQueryParam
@@ -40,7 +40,7 @@ class OnlineService:
if cls._match_search_conditions(session_info, search):
online_users.append(session_info)
except Exception as e:
logger.error(f"解析在线用户数据失败: {e}")
log.error(f"解析在线用户数据失败: {e}")
continue
# 按照 login_time 倒序排序
online_users.sort(key=lambda x: x.get('login_time', ''), reverse=True)
@@ -65,7 +65,7 @@ class OnlineService:
await RedisCURD(redis).delete(f"{RedisInitKeyConfig.REFRESH_TOKEN.key}:{session_id}")
logger.info(f"强制下线用户会话: {session_id}")
log.info(f"强制下线用户会话: {session_id}")
return True
@classmethod
@@ -83,7 +83,7 @@ class OnlineService:
await RedisCURD(redis).clear(f"{RedisInitKeyConfig.ACCESS_TOKEN.key}:*")
await RedisCURD(redis).clear(f"{RedisInitKeyConfig.REFRESH_TOKEN.key}:*")
logger.info(f"清除所有在线用户会话成功")
log.info(f"清除所有在线用户会话成功")
return True
@@ -10,7 +10,7 @@ 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
from app.core.logger import log
from .param import ResourceSearchQueryParam
from .service import ResourceService
@@ -58,7 +58,7 @@ async def get_directory_list_controller(
page_size=page.page_size
)
logger.info(f"获取目录列表成功: {getattr(search, 'name', None) or ''}")
log.info(f"获取目录列表成功: {getattr(search, 'name', None) or ''}")
return SuccessResponse(data=result_dict, msg="获取目录列表成功")
@@ -88,7 +88,7 @@ async def upload_file_controller(
target_path=target_path,
base_url=str(request.base_url)
)
logger.info(f"上传文件成功: {result_dict['filename']}")
log.info(f"上传文件成功: {result_dict['filename']}")
return SuccessResponse(data=result_dict, msg="上传文件成功")
@@ -121,7 +121,7 @@ async def download_file_controller(
import os
filename = os.path.basename(file_path)
logger.info(f"下载文件成功: {filename}")
log.info(f"下载文件成功: {filename}")
return FileResponse(
path=file_path,
filename=filename,
@@ -148,7 +148,7 @@ async def delete_files_controller(
- JSONResponse: 包含删除结果的JSON响应
"""
await ResourceService.delete_file_service(paths=paths)
logger.info(f"删除文件成功: {paths}")
log.info(f"删除文件成功: {paths}")
return SuccessResponse(msg="删除文件成功")
@@ -171,7 +171,7 @@ async def move_file_controller(
- JSONResponse: 包含移动结果的JSON响应
"""
await ResourceService.move_file_service(data=data)
logger.info(f"移动文件成功: {data.source_path} -> {data.target_path}")
log.info(f"移动文件成功: {data.source_path} -> {data.target_path}")
return SuccessResponse(msg="移动文件成功")
@@ -194,7 +194,7 @@ async def copy_file_controller(
- JSONResponse: 包含复制结果的JSON响应
"""
await ResourceService.copy_file_service(data=data)
logger.info(f"复制文件成功: {data.source_path} -> {data.target_path}")
log.info(f"复制文件成功: {data.source_path} -> {data.target_path}")
return SuccessResponse(msg="复制文件成功")
@@ -217,7 +217,7 @@ async def rename_file_controller(
- JSONResponse: 包含重命名结果的JSON响应
"""
await ResourceService.rename_file_service(data=data)
logger.info(f"重命名文件成功: {data.old_path} -> {data.new_name}")
log.info(f"重命名文件成功: {data.old_path} -> {data.new_name}")
return SuccessResponse(msg="重命名文件成功")
@@ -240,7 +240,7 @@ async def create_directory_controller(
- JSONResponse: 包含创建目录结果的JSON响应
"""
await ResourceService.create_directory_service(data=data)
logger.info(f"创建目录成功: {data.parent_path}/{data.dir_name}")
log.info(f"创建目录成功: {data.parent_path}/{data.dir_name}")
return SuccessResponse(msg="创建目录成功")
@@ -271,7 +271,7 @@ async def export_resource_list_controller(
)
export_result = await ResourceService.export_resource_service(data_list=result_dict_list)
logger.info("导出资源列表成功")
log.info("导出资源列表成功")
return StreamResponse(
data=bytes2file_response(export_result),
media_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
@@ -9,7 +9,7 @@ from urllib.parse import urlparse
from fastapi import UploadFile
from app.core.exceptions import CustomException
from app.core.logger import logger
from app.core.logger import log
from app.utils.excel_util import ExcelUtil
from app.config.setting import settings
@@ -216,7 +216,7 @@ class ResourceService:
'is_hidden': is_hidden
}
except Exception as e:
logger.error(f'获取文件信息失败: {str(e)}')
log.error(f'获取文件信息失败: {str(e)}')
return {}
@classmethod
@@ -285,7 +285,7 @@ class ResourceService:
except CustomException:
raise
except Exception as e:
logger.error(f'获取目录列表失败: {str(e)}')
log.error(f'获取目录列表失败: {str(e)}')
raise CustomException(msg=f'获取目录列表失败: {str(e)}')
@classmethod
@@ -349,7 +349,7 @@ class ResourceService:
return sorted_resources
except Exception as e:
logger.error(f'搜索资源失败: {str(e)}')
log.error(f'搜索资源失败: {str(e)}')
raise CustomException(msg=f'搜索资源失败: {str(e)}')
@classmethod
@@ -528,7 +528,7 @@ class ResourceService:
# 生成文件URL
file_url = cls._generate_http_url(file_path, base_url)
logger.info(f"文件上传成功: {filename}")
log.info(f"文件上传成功: {filename}")
return ResourceUploadSchema(
filename=filename,
@@ -538,7 +538,7 @@ class ResourceService:
).model_dump(mode='json')
except Exception as e:
logger.error(f"文件上传失败: {str(e)}")
log.error(f"文件上传失败: {str(e)}")
raise CustomException(msg=f"文件上传失败: {str(e)}")
@classmethod
@@ -563,13 +563,13 @@ class ResourceService:
raise CustomException(msg='路径不是文件')
# 返回本地文件路径给 FileResponse 使用
logger.info(f"定位文件路径: {safe_path}")
log.info(f"定位文件路径: {safe_path}")
return safe_path
except CustomException:
raise
except Exception as e:
logger.error(f"下载文件失败: {str(e)}")
log.error(f"下载文件失败: {str(e)}")
raise CustomException(msg=f"下载文件失败: {str(e)}")
@classmethod
@@ -591,18 +591,18 @@ class ResourceService:
safe_path = cls._get_safe_path(path)
if not os.path.exists(safe_path):
logger.warning(f"路径不存在,跳过: {path}")
log.error(f"路径不存在,跳过: {path}")
continue
if os.path.isfile(safe_path):
os.remove(safe_path)
logger.info(f"删除文件成功: {safe_path}")
log.info(f"删除文件成功: {safe_path}")
elif os.path.isdir(safe_path):
shutil.rmtree(safe_path)
logger.info(f"删除目录成功: {safe_path}")
log.info(f"删除目录成功: {safe_path}")
except Exception as e:
logger.error(f"删除失败 {path}: {str(e)}")
log.error(f"删除失败 {path}: {str(e)}")
raise CustomException(msg=f"删除失败 {path}: {str(e)}")
@classmethod
@@ -633,14 +633,14 @@ class ResourceService:
if os.path.isfile(safe_path):
os.remove(safe_path)
success_paths.append(path)
logger.info(f"删除文件成功: {safe_path}")
log.info(f"删除文件成功: {safe_path}")
elif os.path.isdir(safe_path):
shutil.rmtree(safe_path)
success_paths.append(path)
logger.info(f"删除目录成功: {safe_path}")
log.info(f"删除目录成功: {safe_path}")
except Exception as e:
logger.error(f"删除失败 {path}: {str(e)}")
log.error(f"删除失败 {path}: {str(e)}")
failed_paths.append(path)
return {
@@ -683,12 +683,12 @@ class ResourceService:
# 移动文件
shutil.move(source_path, target_path)
logger.info(f"移动成功: {source_path} -> {target_path}")
log.info(f"移动成功: {source_path} -> {target_path}")
except CustomException:
raise
except Exception as e:
logger.error(f"移动失败: {str(e)}")
log.error(f"移动失败: {str(e)}")
raise CustomException(msg=f"移动失败: {str(e)}")
@classmethod
@@ -723,12 +723,12 @@ class ResourceService:
else:
shutil.copytree(source_path, target_path, dirs_exist_ok=data.overwrite)
logger.info(f"复制成功: {source_path} -> {target_path}")
log.info(f"复制成功: {source_path} -> {target_path}")
except CustomException:
raise
except Exception as e:
logger.error(f"复制失败: {str(e)}")
log.error(f"复制失败: {str(e)}")
raise CustomException(msg=f"复制失败: {str(e)}")
@classmethod
@@ -757,12 +757,12 @@ class ResourceService:
# 重命名
os.rename(old_path, new_path)
logger.info(f"重命名成功: {old_path} -> {new_path}")
log.info(f"重命名成功: {old_path} -> {new_path}")
except CustomException:
raise
except Exception as e:
logger.error(f"重命名失败: {str(e)}")
log.error(f"重命名失败: {str(e)}")
raise CustomException(msg=f"重命名失败: {str(e)}")
@classmethod
@@ -797,12 +797,12 @@ class ResourceService:
# 创建目录
os.makedirs(new_dir_path)
logger.info(f"创建目录成功: {new_dir_path}")
log.info(f"创建目录成功: {new_dir_path}")
except CustomException:
raise
except Exception as e:
logger.error(f"创建目录失败: {str(e)}")
log.error(f"创建目录失败: {str(e)}")
raise CustomException(msg=f"创建目录失败: {str(e)}")
@classmethod
@@ -7,7 +7,7 @@ from fastapi.responses import JSONResponse
from app.common.response import SuccessResponse
from app.core.dependencies import AuthPermission
from app.core.router_class import OperationLogRoute
from app.core.logger import logger
from app.core.logger import log
from .service import ServerService
@@ -28,6 +28,6 @@ async def get_monitor_server_info_controller() -> JSONResponse:
- JSONResponse: 包含服务器监控信息的JSON响应
"""
result_dict = await ServerService.get_server_monitor_info_service()
logger.info(f'获取服务器监控信息成功: {result_dict}')
log.info(f'获取服务器监控信息成功: {result_dict}')
return SuccessResponse(data=result_dict, msg='获取服务器监控信息成功')
@@ -10,7 +10,7 @@ from redis.asyncio.client import Redis
from app.common.response import ErrorResponse, SuccessResponse
from app.core.router_class import OperationLogRoute
from app.core.security import CustomOAuth2PasswordRequestForm
from app.core.logger import logger
from app.core.logger import log
from app.config.setting import settings
from app.core.dependencies import (
db_getter,
@@ -56,7 +56,7 @@ async def login_for_access_token_controller(
"""
login_token = await LoginService.authenticate_user_service(request=request, redis=redis, login_form=login_form, db=db)
logger.info(f"用户{login_form.username}登录成功")
log.info(f"用户{login_form.username}登录成功")
# 如果是文档请求,则不记录日志:http://localhost:8000/api/v1/docs
if settings.DOCS_URL in request.headers.get("referer", ""):
@@ -87,7 +87,7 @@ async def get_new_token_controller(
# 解析当前的访问Token以获取用户名
new_token = await LoginService.refresh_token_service(db=db, request=request, redis=redis, refresh_token=payload)
token_dict = new_token.model_dump()
logger.info(f"刷新token成功: {token_dict}")
log.info(f"刷新token成功: {token_dict}")
return SuccessResponse(data=token_dict, msg="刷新成功")
@@ -109,7 +109,7 @@ async def get_captcha_for_login_controller(
"""
# 获取验证码
captcha = await CaptchaService.get_captcha_service(redis=redis)
logger.info(f"获取验证码成功")
log.info(f"获取验证码成功")
return SuccessResponse(data=captcha, msg="获取验证码成功")
@@ -132,6 +132,6 @@ async def logout_controller(
- CustomException: 退出登录失败时抛出异常
"""
if await LoginService.logout_service(redis=redis, token=payload):
logger.info('退出成功')
log.info('退出成功')
return SuccessResponse(msg='退出成功')
return ErrorResponse(msg='退出失败')
@@ -16,7 +16,7 @@ from app.utils.ip_local_util import IpLocalUtil
from app.utils.hash_bcrpy_util import PwdUtil
from app.core.redis_crud import RedisCURD
from app.core.exceptions import CustomException
from app.core.logger import logger
from app.core.logger import log
from app.config.setting import settings
from app.core.security import (
CustomOAuth2PasswordRequestForm,
@@ -280,7 +280,7 @@ class LoginService:
await RedisCURD(redis).delete(f"{RedisInitKeyConfig.ACCESS_TOKEN.key}:{session_id}")
await RedisCURD(redis).delete(f"{RedisInitKeyConfig.REFRESH_TOKEN.key}:{session_id}")
logger.info(f"用户退出登录成功,会话编号:{session_id}")
log.info(f"用户退出登录成功,会话编号:{session_id}")
return True
@@ -317,7 +317,7 @@ class CaptchaService:
expire=settings.CAPTCHA_EXPIRE_SECONDS
)
logger.info(f"生成验证码成功,验证码:{captcha_value}")
log.info(f"生成验证码成功,验证码:{captcha_value}")
# 返回验证码信息
return CaptchaOutSchema(
@@ -350,15 +350,15 @@ class CaptchaService:
captcha_value = await RedisCURD(redis).get(redis_key)
if not captcha_value:
logger.warning('验证码已过期或不存在')
log.error('验证码已过期或不存在')
raise CustomException(msg="验证码已过期")
# 验证码不区分大小写比对
if captcha.lower() != captcha_value.lower():
logger.warning(f'验证码错误,用户输入:{captcha},正确值:{captcha_value}')
log.error(f'验证码错误,用户输入:{captcha},正确值:{captcha_value}')
raise CustomException(msg="验证码错误")
# 验证成功后删除验证码,避免重复使用
await RedisCURD(redis).delete(redis_key)
logger.info(f'验证码校验成功,key:{key}')
log.info(f'验证码校验成功,key:{key}')
return True
@@ -7,7 +7,7 @@ from app.common.response import SuccessResponse
from app.core.router_class import OperationLogRoute
from app.core.dependencies import AuthPermission
from app.core.base_schema import BatchSetAvailable
from app.core.logger import logger
from app.core.logger import log
from ..auth.schema import AuthSchema
from .param import DeptQueryParam
@@ -41,7 +41,7 @@ async def get_dept_tree_controller(
"""
order_by = [{"order": "asc"}]
result_dict_list = await DeptService.get_dept_tree_service(search=search, auth=auth, order_by=order_by)
logger.info(f"查询部门树成功")
log.info(f"查询部门树成功")
return SuccessResponse(data=result_dict_list, msg="查询部门树成功")
@@ -64,7 +64,7 @@ async def get_obj_detail_controller(
- CustomException: 查询部门详情失败时抛出异常
"""
result_dict = await DeptService.get_dept_detail_service(id=id, auth=auth)
logger.info(f"查询部门详情成功 {id}")
log.info(f"查询部门详情成功 {id}")
return SuccessResponse(data=result_dict, msg="查询部门详情成功")
@@ -87,7 +87,7 @@ async def create_obj_controller(
- CustomException: 创建部门失败时抛出异常
"""
result_dict = await DeptService.create_dept_service(data=data, auth=auth)
logger.info(f"创建部门成功: {result_dict}")
log.info(f"创建部门成功: {result_dict}")
return SuccessResponse(data=result_dict, msg="创建部门成功")
@@ -112,7 +112,7 @@ async def update_obj_controller(
- CustomException: 修改部门失败时抛出异常
"""
result_dict = await DeptService.update_dept_service(auth=auth, id=id, data=data)
logger.info(f"修改部门成功: {result_dict}")
log.info(f"修改部门成功: {result_dict}")
return SuccessResponse(data=result_dict, msg="修改部门成功")
@@ -135,7 +135,7 @@ async def delete_obj_controller(
- CustomException: 删除部门失败时抛出异常
"""
await DeptService.delete_dept_service(ids=ids, auth=auth)
logger.info(f"删除部门成功: {ids}")
log.info(f"删除部门成功: {ids}")
return SuccessResponse(msg="删除部门成功")
@@ -158,5 +158,5 @@ async def batch_set_available_obj_controller(
- CustomException: 批量修改部门状态失败时抛出异常
"""
await DeptService.batch_set_available_service(data=data, auth=auth)
logger.info(f"批量修改部门状态成功: {data.ids}")
log.info(f"批量修改部门状态成功: {data.ids}")
return SuccessResponse(msg="批量修改部门状态成功")
@@ -10,7 +10,7 @@ from app.core.base_params import PaginationQueryParam
from app.core.base_schema import BatchSetAvailable
from app.core.dependencies import AuthPermission, redis_getter
from app.core.router_class import OperationLogRoute
from app.core.logger import logger
from app.core.logger import log
from app.common.request import PaginationService
from app.utils.common_util import bytes2file_response
@@ -46,7 +46,7 @@ async def get_type_detail_controller(
- CustomException: 获取字典类型详情失败时抛出异常
"""
result_dict = await DictTypeService.get_obj_detail_service(id=id, auth=auth)
logger.info(f"获取字典类型详情成功 {id}")
log.info(f"获取字典类型详情成功 {id}")
return SuccessResponse(data=result_dict, msg="获取字典类型详情成功")
@DictRouter.get("/type/list", summary="查询字典类型", description="查询字典类型")
@@ -71,7 +71,7 @@ async def get_type_list_controller(
"""
result_dict_list = await DictTypeService.get_obj_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(f"查询字典类型列表成功")
log.info(f"查询字典类型列表成功")
return SuccessResponse(data=result_dict, msg="查询字典类型列表成功")
@DictRouter.get("/type/optionselect", summary="获取全部字典类型", description="获取全部字典类型")
@@ -91,7 +91,7 @@ async def get_type_loptionselect_controller(
- CustomException: 获取字典类型列表失败时抛出异常
"""
result_dict_list = await DictTypeService.get_obj_list_service(auth=auth)
logger.info(f"获取字典类型列表成功")
log.info(f"获取字典类型列表成功")
return SuccessResponse(data=result_dict_list, msg="获取字典类型列表成功")
@DictRouter.post("/type/create", summary="创建字典类型", description="创建字典类型")
@@ -115,7 +115,7 @@ async def create_type_controller(
- CustomException: 创建字典类型失败时抛出异常
"""
result_dict = await DictTypeService.create_obj_service(auth=auth, redis=redis, data=data)
logger.info(f"创建字典类型成功: {result_dict}")
log.info(f"创建字典类型成功: {result_dict}")
return SuccessResponse(data=result_dict, msg="创建字典类型成功")
@DictRouter.put("/type/update/{id}", summary="修改字典类型", description="修改字典类型")
@@ -141,7 +141,7 @@ async def update_type_controller(
- CustomException: 修改字典类型失败时抛出异常
"""
result_dict = await DictTypeService.update_obj_service(auth=auth, redis=redis, id=id, data=data)
logger.info(f"修改字典类型成功: {result_dict}")
log.info(f"修改字典类型成功: {result_dict}")
return SuccessResponse(data=result_dict, msg="修改字典类型成功")
@DictRouter.delete("/type/delete", summary="删除字典类型", description="删除字典类型")
@@ -165,7 +165,7 @@ async def delete_type_controller(
- CustomException: 删除字典类型失败时抛出异常
"""
await DictTypeService.delete_obj_service(auth=auth, redis=redis, ids=ids)
logger.info(f"删除字典类型成功: {ids}")
log.info(f"删除字典类型成功: {ids}")
return SuccessResponse(msg="删除字典类型成功")
@DictRouter.patch("/type/available/setting", summary="批量修改字典类型状态", description="批量修改字典类型状态")
@@ -187,7 +187,7 @@ async def batch_set_available_dict_type_controller(
- CustomException: 批量修改字典类型状态失败时抛出异常
"""
await DictTypeService.set_obj_available_service(auth=auth, data=data)
logger.info(f"批量修改字典类型状态成功: {data.ids}")
log.info(f"批量修改字典类型状态成功: {data.ids}")
return SuccessResponse(msg="批量修改字典类型状态成功")
@DictRouter.post('/type/export', summary="导出字典类型", description="导出字典类型")
@@ -211,7 +211,7 @@ async def export_type_list_controller(
# 获取全量数据
result_dict_list = await DictTypeService.get_obj_list_service(search=search, auth=auth)
export_result = await DictTypeService.export_obj_service(data_list=result_dict_list)
logger.info('导出字典类型成功')
log.info('导出字典类型成功')
return StreamResponse(
data=bytes2file_response(export_result),
@@ -240,7 +240,7 @@ async def get_data_detail_controller(
- CustomException: 获取字典数据详情失败时抛出异常
"""
result_dict = await DictDataService.get_obj_detail_service(id=id, auth=auth)
logger.info(f"获取字典数据详情成功 {id}")
log.info(f"获取字典数据详情成功 {id}")
return SuccessResponse(data=result_dict, msg="获取字典数据详情成功")
@DictRouter.get("/data/list", summary="查询字典数据", description="查询字典数据")
@@ -268,7 +268,7 @@ async def get_data_list_controller(
order_by = page.order_by
result_dict_list = await DictDataService.get_obj_list_service(auth=auth, search=search, order_by=order_by)
result_dict = await PaginationService.paginate(data_list= result_dict_list, page_no= page.page_no, page_size = page.page_size)
logger.info(f"查询字典数据列表成功")
log.info(f"查询字典数据列表成功")
return SuccessResponse(data=result_dict, msg="查询字典数据列表成功")
@DictRouter.post("/data/create", summary="创建字典数据", description="创建字典数据")
@@ -292,7 +292,7 @@ async def create_data_controller(
- CustomException: 创建字典数据失败时抛出异常
"""
result_dict = await DictDataService.create_obj_service(auth=auth, redis=redis, data=data)
logger.info(f"创建字典数据成功: {result_dict}")
log.info(f"创建字典数据成功: {result_dict}")
return SuccessResponse(data=result_dict, msg="创建字典数据成功")
@DictRouter.put("/data/update/{id}", summary="修改字典数据", description="修改字典数据")
@@ -318,7 +318,7 @@ async def update_data_controller(
- CustomException: 修改字典数据失败时抛出异常
"""
result_dict = await DictDataService.update_obj_service(auth=auth, redis=redis, id=id, data=data)
logger.info(f"修改字典数据成功: {result_dict}")
log.info(f"修改字典数据成功: {result_dict}")
return SuccessResponse(data=result_dict, msg="修改字典数据成功")
@DictRouter.delete("/data/delete", summary="删除字典数据", description="删除字典数据")
@@ -342,7 +342,7 @@ async def delete_data_controller(
- CustomException: 删除字典数据失败时抛出异常
"""
await DictDataService.delete_obj_service(auth=auth, redis=redis, ids=ids)
logger.info(f"删除字典数据成功: {ids}")
log.info(f"删除字典数据成功: {ids}")
return SuccessResponse(msg="删除字典数据成功")
@DictRouter.patch("/data/available/setting", summary="批量修改字典数据状态", description="批量修改字典数据状态")
@@ -364,7 +364,7 @@ async def batch_set_available_dict_data_controller(
- CustomException: 批量修改字典数据状态失败时抛出异常
"""
await DictDataService.set_obj_available_service(auth=auth, data=data)
logger.info(f"批量修改字典数据状态成功: {data.ids}")
log.info(f"批量修改字典数据状态成功: {data.ids}")
return SuccessResponse(msg="批量修改字典数据状态成功")
@DictRouter.post('/data/export', summary="导出字典数据", description="导出字典数据")
@@ -389,7 +389,7 @@ async def export_data_list_controller(
"""
result_dict_list = await DictDataService.get_obj_list_service(auth=auth, search=search, order_by=page.order_by)
export_result = await DictDataService.export_obj_service(data_list=result_dict_list)
logger.info('导出字典数据成功')
log.info('导出字典数据成功')
return StreamResponse(
data=bytes2file_response(export_result),
@@ -420,7 +420,7 @@ async def get_init_dict_data_controller(
dict_data_query_result = await DictDataService.get_init_dict_service(
redis=redis, dict_type=dict_type
)
logger.info(f"获取初始化字典数据成功:{dict_data_query_result}")
log.info(f"获取初始化字典数据成功:{dict_data_query_result}")
# 确保数据是字符串类型再进行 JSON 解析
if isinstance(dict_data_query_result, bytes):
@@ -10,7 +10,7 @@ from app.core.database import async_db_session
from app.core.base_schema import BatchSetAvailable
from app.core.redis_crud import RedisCURD
from app.core.exceptions import CustomException
from app.core.logger import logger
from app.core.logger import log
from app.api.v1.module_system.auth.schema import AuthSchema
from .schema import DictDataCreateSchema,DictDataOutSchema,DictDataUpdateSchema,DictTypeCreateSchema,DictTypeOutSchema,DictTypeUpdateSchema
@@ -81,9 +81,9 @@ class DictTypeService:
key=redis_key,
value="",
)
logger.info(f"创建字典类型成功: {new_obj_dict}")
log.info(f"创建字典类型成功: {new_obj_dict}")
except Exception as e:
logger.error(f"创建字典类型失败: {e}")
log.error(f"创建字典类型失败: {e}")
raise CustomException(msg=f"创建字典类型失败 {e}")
return new_obj_dict
@@ -146,9 +146,9 @@ class DictTypeService:
key=redis_key,
value=value,
)
logger.info(f"更新字典类型成功并刷新缓存: {new_obj_dict}")
log.info(f"更新字典类型成功并刷新缓存: {new_obj_dict}")
except Exception as e:
logger.error(f"更新字典类型缓存失败: {e}")
log.error(f"更新字典类型缓存失败: {e}")
raise CustomException(msg=f"更新字典类型缓存失败 {e}")
return new_obj_dict
@@ -181,9 +181,9 @@ class DictTypeService:
redis_key = f"{RedisInitKeyConfig.SYSTEM_DICT.key}:{exist_obj.dict_type}"
try:
await RedisCURD(redis).delete(redis_key)
logger.info(f"删除字典类型成功: {id}")
log.info(f"删除字典类型成功: {id}")
except Exception as e:
logger.error(f"删除字典类型失败: {e}")
log.error(f"删除字典类型失败: {e}")
raise CustomException(msg=f"删除字典类型失败")
await DictTypeCRUD(auth).delete_obj_crud(ids=ids)
@@ -286,14 +286,14 @@ class DictDataService:
auth = AuthSchema(db=session)
obj_list = await DictTypeCRUD(auth).get_obj_list_crud()
if not obj_list:
logger.warning("❗️ 未找到任何字典类型数据")
log.error("❗️ 未找到任何字典类型数据")
return
for obj in obj_list:
dict_type = obj.dict_type
dict_data_list = await DictDataCRUD(auth).get_obj_list_crud(search={'dict_type': dict_type})
if not dict_data_list:
logger.warning(f"❗️ 字典类型 {dict_type} 未找到对应的字典数据")
log.error(f"❗️ 字典类型 {dict_type} 未找到对应的字典数据")
continue
dict_data = [DictDataOutSchema.model_validate(row).model_dump() for row in dict_data_list if row]
@@ -307,7 +307,7 @@ class DictDataService:
value=value,
)
except Exception as e:
logger.error(f"❌️ 初始化字典数据失败: {e}")
log.error(f"❌️ 初始化字典数据失败: {e}")
raise CustomException(msg=f"初始化字典数据失败 {e}")
@classmethod
@@ -357,9 +357,9 @@ class DictDataService:
key=redis_key,
value=value,
)
logger.info(f"创建字典数据写入缓存成功: {obj}")
log.info(f"创建字典数据写入缓存成功: {obj}")
except Exception as e:
logger.error(f"创建字典数据写入缓存失败: {e}")
log.error(f"创建字典数据写入缓存失败: {e}")
raise CustomException(msg=f"创建字典数据失败 {e}")
return DictDataOutSchema.model_validate(obj).model_dump()
@@ -399,7 +399,7 @@ class DictDataService:
value=value,
)
except Exception as e:
logger.error(f"更新字典数据类型变更时刷新旧缓存失败: {e}")
log.error(f"更新字典数据类型变更时刷新旧缓存失败: {e}")
obj = await DictDataCRUD(auth).update_obj_crud(id=id, data=data)
redis_key = f"{RedisInitKeyConfig.SYSTEM_DICT.key}:{data.dict_type}"
@@ -413,9 +413,9 @@ class DictDataService:
key=redis_key,
value=value,
)
logger.info(f"更新字典数据写入缓存成功: {obj}")
log.info(f"更新字典数据写入缓存成功: {obj}")
except Exception as e:
logger.error(f"更新字典数据写入缓存失败: {e}")
log.error(f"更新字典数据写入缓存失败: {e}")
raise CustomException(msg=f"更新字典数据失败 {e}")
return DictDataOutSchema.model_validate(obj).model_dump()
@@ -449,9 +449,9 @@ class DictDataService:
try:
# 删除Redis缓存
await RedisCURD(redis).delete(redis_key)
logger.info(f"删除字典数据成功: {id}")
log.info(f"删除字典数据成功: {id}")
except Exception as e:
logger.error(f"删除字典数据失败: {e}")
log.error(f"删除字典数据失败: {e}")
raise CustomException(msg=f"删除字典数据失败 {e}")
await DictDataCRUD(auth).delete_obj_crud(ids=ids)
@@ -9,7 +9,7 @@ from app.utils.common_util import bytes2file_response
from app.core.router_class import OperationLogRoute
from app.core.dependencies import AuthPermission
from app.core.base_params import PaginationQueryParam
from app.core.logger import logger
from app.core.logger import log
from ..auth.schema import AuthSchema
from .param import OperationLogQueryParam
@@ -41,7 +41,7 @@ async def get_obj_list_controller(
order_by = page.order_by
result_dict_list = await OperationLogService.get_log_list_service(search=search, auth=auth, order_by=order_by)
result_dict = await PaginationService.paginate(data_list= result_dict_list, page_no= page.page_no, page_size = page.page_size)
logger.info(f"查询日志成功")
log.info(f"查询日志成功")
return SuccessResponse(data=result_dict, msg="查询日志成功")
@@ -61,7 +61,7 @@ async def get_obj_detail_controller(
- JSONResponse: 包含日志详情的 JSON 响应模型
"""
result_dict = await OperationLogService.get_log_detail_service(id=id, auth=auth)
logger.info(f"查询日志成功 {id}")
log.info(f"查询日志成功 {id}")
return SuccessResponse(data=result_dict, msg="获取日志详情成功")
@@ -81,7 +81,7 @@ async def delete_obj_log_controller(
- JSONResponse: 包含删除结果的 JSON 响应模型
"""
await OperationLogService.delete_log_service(ids=ids, auth=auth)
logger.info(f"删除日志成功 {ids}")
log.info(f"删除日志成功 {ids}")
return SuccessResponse(msg="删除日志成功")
@@ -102,7 +102,7 @@ async def export_obj_list_controller(
"""
operation_log_list = await OperationLogService.get_log_list_service(search=search, auth=auth)
operation_log_export_result = await OperationLogService.export_log_list_service(operation_log_list=operation_log_list)
logger.info('导出日志成功')
log.info('导出日志成功')
return StreamResponse(
data=bytes2file_response(operation_log_export_result),
@@ -7,7 +7,7 @@ from app.common.response import SuccessResponse
from app.core.dependencies import AuthPermission
from app.core.base_schema import BatchSetAvailable
from app.core.router_class import OperationLogRoute
from app.core.logger import logger
from app.core.logger import log
from ..auth.schema import AuthSchema
from .param import MenuQueryParam
@@ -36,7 +36,7 @@ async def get_menu_tree_controller(
"""
order_by = [{"order": "asc"}]
result_dict_list = await MenuService.get_menu_tree_service(search=search, auth=auth, order_by=order_by)
logger.info(f"查询菜单树成功")
log.info(f"查询菜单树成功")
return SuccessResponse(data=result_dict_list, msg="查询菜单树成功")
@@ -55,7 +55,7 @@ async def get_obj_detail_controller(
- JSONResponse: 包含菜单详情的 JSON 响应
"""
result_dict = await MenuService.get_menu_detail_service(id=id, auth=auth)
logger.info(f"查询菜单情成功 {id}")
log.info(f"查询菜单情成功 {id}")
return SuccessResponse(data=result_dict, msg="获取菜单成功")
@@ -74,7 +74,7 @@ async def create_obj_controller(
- JSONResponse: 包含创建菜单的 JSON 响应
"""
result_dict = await MenuService.create_menu_service(data=data, auth=auth)
logger.info(f"创建菜单成功: {result_dict}")
log.info(f"创建菜单成功: {result_dict}")
return SuccessResponse(data=result_dict, msg="创建菜单成功")
@@ -95,7 +95,7 @@ async def update_obj_controller(
- JSONResponse: 包含修改菜单的 JSON 响应
"""
result_dict = await MenuService.update_menu_service(id=id, data=data, auth=auth)
logger.info(f"修改菜单成功: {result_dict}")
log.info(f"修改菜单成功: {result_dict}")
return SuccessResponse(data=result_dict, msg="修改菜单成功")
@@ -114,7 +114,7 @@ async def delete_obj_controller(
- JSONResponse: 包含删除菜单的 JSON 响应
"""
await MenuService.delete_menu_service(ids=ids, auth=auth)
logger.info(f"删除菜单成功: {ids}")
log.info(f"删除菜单成功: {ids}")
return SuccessResponse(msg="删除菜单成功")
@@ -133,5 +133,5 @@ async def batch_set_available_obj_controller(
- JSONResponse: 批量修改菜单状态的 JSON 响应
"""
await MenuService.set_menu_available_service(data=data, auth=auth)
logger.info(f"批量修改菜单状态成功: {data.ids}")
log.info(f"批量修改菜单状态成功: {data.ids}")
return SuccessResponse(msg="批量修改菜单状态成功")
@@ -29,7 +29,7 @@ class MenuModel(ModelMixin):
__loader_options__ = ["roles"]
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True, comment='主键ID')
name: Mapped[str] = mapped_column(String(50), nullable=False, comment='菜单名称', unique=True)
name: Mapped[str] = mapped_column(String(50), nullable=False, comment='菜单名称')
type: Mapped[int] = mapped_column(Integer, nullable=False, default=2, comment='菜单类型(1:目录 2:菜单 3:按钮/权限 4:链接)')
order: Mapped[int] = mapped_column(Integer, nullable=False, default=999, comment='显示排序')
status: Mapped[bool] = mapped_column(Boolean(), default=True, nullable=False, comment="是否启用(True:启用 False:禁用)")
@@ -8,7 +8,7 @@ from app.core.base_params import PaginationQueryParam
from app.core.dependencies import AuthPermission, get_current_user
from app.core.router_class import OperationLogRoute
from app.core.base_schema import BatchSetAvailable
from app.core.logger import logger
from app.core.logger import log
from app.common.request import PaginationService
from app.utils.common_util import bytes2file_response
@@ -39,7 +39,7 @@ async def get_obj_detail_controller(
- JSONResponse: 包含公告详情的响应模型
"""
result_dict = await NoticeService.get_notice_detail_service(id=id, auth=auth)
logger.info(f"获取公告详情成功 {id}")
log.info(f"获取公告详情成功 {id}")
return SuccessResponse(data=result_dict, msg="获取公告详情成功")
@NoticeRouter.get("/list", summary="查询公告", description="查询公告")
@@ -61,7 +61,7 @@ async def get_obj_list_controller(
"""
result_dict_list = await NoticeService.get_notice_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(f"查询公告列表成功")
log.info(f"查询公告列表成功")
return SuccessResponse(data=result_dict, msg="查询公告列表成功")
@NoticeRouter.post("/create", summary="创建公告", description="创建公告")
@@ -80,7 +80,7 @@ async def create_obj_controller(
- JSONResponse: 包含创建公告结果的响应模型
"""
result_dict = await NoticeService.create_notice_service(auth=auth, data=data)
logger.info(f"创建公告成功: {result_dict}")
log.info(f"创建公告成功: {result_dict}")
return SuccessResponse(data=result_dict, msg="创建公告成功")
@NoticeRouter.put("/update/{id}", summary="修改公告", description="修改公告")
@@ -101,7 +101,7 @@ async def update_obj_controller(
- JSONResponse: 包含修改公告结果的响应模型
"""
result_dict = await NoticeService.update_notice_service(auth=auth, id=id, data=data)
logger.info(f"修改公告成功: {result_dict}")
log.info(f"修改公告成功: {result_dict}")
return SuccessResponse(data=result_dict, msg="修改公告成功")
@NoticeRouter.delete("/delete", summary="删除公告", description="删除公告")
@@ -120,7 +120,7 @@ async def delete_obj_controller(
- JSONResponse: 包含删除公告结果的响应模型
"""
await NoticeService.delete_notice_service(auth=auth, ids=ids)
logger.info(f"删除公告成功: {ids}")
log.info(f"删除公告成功: {ids}")
return SuccessResponse(msg="删除公告成功")
@NoticeRouter.patch("/available/setting", summary="批量修改公告状态", description="批量修改公告状态")
@@ -139,7 +139,7 @@ async def batch_set_available_obj_controller(
- JSONResponse: 包含批量修改公告状态结果的响应模型
"""
await NoticeService.set_notice_available_service(auth=auth, data=data)
logger.info(f"批量修改公告状态成功: {data.ids}")
log.info(f"批量修改公告状态成功: {data.ids}")
return SuccessResponse(msg="批量修改公告状态成功")
@NoticeRouter.post('/export', summary="导出公告", description="导出公告")
@@ -159,7 +159,7 @@ async def export_obj_list_controller(
"""
result_dict_list = await NoticeService.get_notice_list_service(search=search, auth=auth)
export_result = await NoticeService.export_notice_service(notice_list=result_dict_list)
logger.info('导出公告成功')
log.info('导出公告成功')
return StreamResponse(
data=bytes2file_response(export_result),
@@ -185,5 +185,5 @@ async def get_obj_list_available_controller(
"""
result_dict_list = await NoticeService.get_notice_list_available_service(auth=auth)
result_dict = await PaginationService.paginate(data_list= result_dict_list)
logger.info(f"查询已启用公告列表成功")
log.info(f"查询已启用公告列表成功")
return SuccessResponse(data=result_dict, msg="查询已启用公告列表成功")
@@ -10,7 +10,7 @@ from app.utils.common_util import bytes2file_response
from app.core.base_params import PaginationQueryParam
from app.core.dependencies import AuthPermission, redis_getter
from app.core.router_class import OperationLogRoute
from app.core.logger import logger
from app.core.logger import log
from ..auth.schema import AuthSchema
from .param import ParamsQueryParam
@@ -36,7 +36,7 @@ async def get_type_detail_controller(
- JSONResponse: 包含参数详情的 JSON 响应
"""
result_dict = await ParamsService.get_obj_detail_service(id=id, auth=auth)
logger.info(f"获取参数详情成功 {id}")
log.info(f"获取参数详情成功 {id}")
return SuccessResponse(data=result_dict, msg="获取参数详情成功")
@@ -56,7 +56,7 @@ async def get_obj_by_key_controller(
- JSONResponse: 包含参数详情的 JSON 响应
"""
result_dict = await ParamsService.get_obj_by_key_service(config_key=config_key, auth=auth)
logger.info(f"根据配置键获取参数详情成功 {config_key}")
log.info(f"根据配置键获取参数详情成功 {config_key}")
return SuccessResponse(data=result_dict, msg="根据配置键获取参数详情成功")
@@ -76,7 +76,7 @@ async def get_config_value_by_key_controller(
- JSONResponse: 包含参数值的 JSON 响应
"""
result_value = await ParamsService.get_config_value_by_key_service(config_key=config_key, auth=auth)
logger.info(f"根据配置键获取参数值成功 {config_key}")
log.info(f"根据配置键获取参数值成功 {config_key}")
return SuccessResponse(data=result_value, msg="根据配置键获取参数值成功")
@@ -99,7 +99,7 @@ async def get_obj_list_controller(
"""
result_dict_list = await ParamsService.get_obj_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(f"获取参数列表成功")
log.info(f"获取参数列表成功")
return SuccessResponse(data=result_dict, msg="查询参数列表成功")
@@ -121,7 +121,7 @@ async def create_obj_controller(
- JSONResponse: 包含创建参数结果的 JSON 响应
"""
result_dict = await ParamsService.create_obj_service(auth=auth, redis=redis, data=data)
logger.info(f"创建参数成功: {result_dict}")
log.info(f"创建参数成功: {result_dict}")
return SuccessResponse(data=result_dict, msg="创建参数成功")
@@ -145,7 +145,7 @@ async def update_objs_controller(
- JSONResponse: 包含修改参数结果的 JSON 响应
"""
result_dict = await ParamsService.update_obj_service(auth=auth, redis=redis, id=id, data=data)
logger.info(f"更新参数成功 {result_dict}")
log.info(f"更新参数成功 {result_dict}")
return SuccessResponse(data=result_dict, msg="更新参数成功")
@@ -167,7 +167,7 @@ async def delete_obj_controller(
- JSONResponse: 包含删除参数结果的 JSON 响应
"""
await ParamsService.delete_obj_service(auth=auth, redis=redis, ids=ids)
logger.info(f"删除参数成功: {ids}")
log.info(f"删除参数成功: {ids}")
return SuccessResponse(msg="删除参数成功")
@@ -188,7 +188,7 @@ async def export_obj_list_controller(
"""
result_dict_list = await ParamsService.get_obj_list_service(search=search, auth=auth)
export_result = await ParamsService.export_obj_service(data_list=result_dict_list)
logger.info('导出参数成功')
log.info('导出参数成功')
return StreamResponse(
data=bytes2file_response(export_result),
@@ -215,7 +215,7 @@ async def upload_file_controller(
- JSONResponse: 包含上传文件结果的 JSON 响应
"""
result_str = await ParamsService.upload_service(base_url=str(request.base_url), file=file)
logger.info(f"上传文件: {result_str}")
log.info(f"上传文件: {result_str}")
return SuccessResponse(data=result_str, msg='上传文件成功')
@@ -233,5 +233,5 @@ async def get_init_obj_controller(
- JSONResponse: 获取初始化缓存参数的 JSON 响应
"""
result_dict = await ParamsService.get_init_config_service(redis=redis)
logger.info(f"获取初始化缓存参数成功 {result_dict}")
log.info(f"获取初始化缓存参数成功 {result_dict}")
return SuccessResponse(data=result_dict, msg="获取初始化缓存参数成功")
@@ -14,7 +14,7 @@ from app.utils.excel_util import ExcelUtil
from app.utils.upload_util import UploadUtil
from app.core.base_schema import UploadResponseSchema
from app.core.exceptions import CustomException
from app.core.logger import logger
from app.core.logger import log
from ..auth.schema import AuthSchema
from .param import ParamsQueryParam
@@ -123,10 +123,10 @@ class ParamsService:
value="",
)
if not result:
logger.error(f"同步配置到缓存失败: {new_obj_dict}")
log.error(f"同步配置到缓存失败: {new_obj_dict}")
raise CustomException(msg="同步配置到缓存失败")
except Exception as e:
logger.error(f"创建字典类型失败: {e}")
log.error(f"创建字典类型失败: {e}")
raise CustomException(msg=f"创建字典类型失败 {e}")
return new_obj_dict
@@ -165,10 +165,10 @@ class ParamsService:
value=value,
)
if not result:
logger.error(f"同步配置到缓存失败: {new_obj_dict}")
log.error(f"同步配置到缓存失败: {new_obj_dict}")
raise CustomException(msg="同步配置到缓存失败")
except Exception as e:
logger.error(f"更新系统配置失败: {e}")
log.error(f"更新系统配置失败: {e}")
raise CustomException(msg="更新系统配置失败")
return new_obj_dict
@@ -207,9 +207,9 @@ class ParamsService:
redis_key = f"{RedisInitKeyConfig.SYSTEM_CONFIG.key}:{exist_obj.config_key}"
try:
await RedisCURD(redis).delete(redis_key)
logger.info(f"删除系统配置成功: {id}")
log.info(f"删除系统配置成功: {id}")
except Exception as e:
logger.error(f"删除系统配置失败: {e}")
log.error(f"删除系统配置失败: {e}")
raise CustomException(msg="删除字典类型失败")
@classmethod
@@ -294,10 +294,10 @@ class ParamsService:
value=value,
)
if not result:
logger.error(f"❌️ 初始化系统配置失败: {config_obj_dict}")
log.error(f"❌️ 初始化系统配置失败: {config_obj_dict}")
raise CustomException(msg="初始化系统配置失败")
except Exception as e:
logger.error(f"❌️ 初始化系统配置失败: {e}")
log.error(f"❌️ 初始化系统配置失败: {e}")
raise CustomException(msg="初始化系统配置失败")
@classmethod
@@ -321,7 +321,7 @@ class ParamsService:
new_config = json.loads(config)
configs.append(new_config)
except Exception as e:
logger.error(f"解析系统配置数据失败: {e}")
log.error(f"解析系统配置数据失败: {e}")
continue
return configs
@@ -362,7 +362,7 @@ class ParamsService:
demo_config = json.loads(config_values[0])
config_result["demo_enable"] = demo_config.get("config_value", False) if isinstance(demo_config, dict) else False
except json.JSONDecodeError:
logger.error(f"解析演示模式配置失败")
log.error(f"解析演示模式配置失败")
# 解析IP白名单配置
if config_values[1]:
@@ -372,7 +372,7 @@ class ParamsService:
# 确保是列表类型
config_result["ip_white_list"] = json.loads(ip_white_config.get("config_value", []))
except json.JSONDecodeError:
logger.error(f"解析IP白名单配置失败")
log.error(f"解析IP白名单配置失败")
# 解析IP黑名单
# 解析API路径白名单
if config_values[2]:
@@ -381,7 +381,7 @@ class ParamsService:
# 确保是列表类型
config_result["white_api_list_path"] = json.loads(white_api_config.get("config_value", []))
except json.JSONDecodeError:
logger.error(f"解析API白名单配置失败")
log.error(f"解析API白名单配置失败")
# 解析IP黑名单
if config_values[3]:
@@ -390,5 +390,5 @@ class ParamsService:
# 确保是列表类型
config_result["ip_black_list"] = json.loads(black_ip_config.get("config_value", []))
except json.JSONDecodeError:
logger.error(f"解析IP黑名单配置失败")
log.error(f"解析IP黑名单配置失败")
return config_result
@@ -10,7 +10,7 @@ from app.core.base_params import PaginationQueryParam
from app.core.router_class import OperationLogRoute
from app.core.dependencies import AuthPermission
from app.core.base_schema import BatchSetAvailable
from app.core.logger import logger
from app.core.logger import log
from ..auth.schema import AuthSchema
from .service import PositionService
@@ -46,7 +46,7 @@ async def get_obj_list_controller(
order_by = page.order_by
result_dict_list = await PositionService.get_position_list_service(search=search, auth=auth, order_by=order_by)
result_dict = await PaginationService.paginate(data_list= result_dict_list, page_no= page.page_no, page_size = page.page_size)
logger.info(f"查询岗位列表成功")
log.info(f"查询岗位列表成功")
return SuccessResponse(data=result_dict, msg="查询岗位列表成功")
@@ -66,7 +66,7 @@ async def get_obj_detail_controller(
- JSONResponse: 岗位详情对象
"""
result_dict = await PositionService.get_position_detail_service(id=id, auth=auth)
logger.info(f"查询岗位详情成功 {id}")
log.info(f"查询岗位详情成功 {id}")
return SuccessResponse(data=result_dict, msg="获取岗位详情成功")
@@ -86,7 +86,7 @@ async def create_obj_controller(
- JSONResponse: 岗位详情对象
"""
result_dict = await PositionService.create_position_service(data=data, auth=auth)
logger.info(f"创建岗位成功: {result_dict}")
log.info(f"创建岗位成功: {result_dict}")
return SuccessResponse(data=result_dict, msg="创建岗位成功")
@@ -108,7 +108,7 @@ async def update_obj_controller(
- JSONResponse: 岗位详情对象
"""
result_dict = await PositionService.update_position_service(id=id, data=data, auth=auth)
logger.info(f"修改岗位成功: {result_dict}")
log.info(f"修改岗位成功: {result_dict}")
return SuccessResponse(data=result_dict, msg="修改岗位成功")
@@ -128,7 +128,7 @@ async def delete_obj_controller(
- JSONResponse: 成功消息
"""
await PositionService.delete_position_service(ids=ids, auth=auth)
logger.info(f"删除岗位成功: {ids}")
log.info(f"删除岗位成功: {ids}")
return SuccessResponse(msg="删除岗位成功")
@@ -148,7 +148,7 @@ async def batch_set_available_obj_controller(
- JSONResponse: 成功消息
"""
await PositionService.set_position_available_service(data=data, auth=auth)
logger.info(f"批量修改岗位状态成功: {data.ids}")
log.info(f"批量修改岗位状态成功: {data.ids}")
return SuccessResponse(msg="批量修改岗位状态成功")
@@ -169,7 +169,7 @@ async def export_obj_list_controller(
"""
position_query_result = await PositionService.get_position_list_service(search=search, auth=auth)
position_export_result = await PositionService.export_position_list_service(position_list=position_query_result)
logger.info('导出岗位成功')
log.info('导出岗位成功')
return StreamResponse(
data=bytes2file_response(position_export_result),
@@ -10,7 +10,7 @@ from app.core.router_class import OperationLogRoute
from app.core.base_params import PaginationQueryParam
from app.core.dependencies import AuthPermission
from app.core.base_schema import BatchSetAvailable
from app.core.logger import logger
from app.core.logger import log
from ..auth.schema import AuthSchema
from .service import RoleService
@@ -47,7 +47,7 @@ async def get_obj_list_controller(
order_by = page.order_by
result_dict_list = await RoleService.get_role_list_service(search=search, auth=auth, order_by=order_by)
result_dict = await PaginationService.paginate(data_list= result_dict_list, page_no= page.page_no, page_size = page.page_size)
logger.info(f"查询角色成功")
log.info(f"查询角色成功")
return SuccessResponse(data=result_dict, msg="查询角色成功")
@@ -67,7 +67,7 @@ async def get_obj_detail_controller(
- JSONResponse: 角色详情JSON响应
"""
result_dict = await RoleService.get_role_detail_service(id=id, auth=auth)
logger.info(f"获取角色详情成功 {id}")
log.info(f"获取角色详情成功 {id}")
return SuccessResponse(data=result_dict, msg="获取角色详情成功")
@@ -87,7 +87,7 @@ async def create_obj_controller(
- JSONResponse: 创建角色JSON响应
"""
result_dict = await RoleService.create_role_service(data=data, auth=auth)
logger.info(f"创建角色成功: {result_dict}")
log.info(f"创建角色成功: {result_dict}")
return SuccessResponse(data=result_dict, msg="创建角色成功")
@@ -109,7 +109,7 @@ async def update_obj_controller(
- JSONResponse: 修改角色JSON响应
"""
result_dict = await RoleService.update_role_service(id=id, data=data, auth=auth)
logger.info(f"修改角色成功: {result_dict}")
log.info(f"修改角色成功: {result_dict}")
return SuccessResponse(data=result_dict, msg="修改角色成功")
@@ -129,7 +129,7 @@ async def delete_obj_controller(
- JSONResponse: 删除角色JSON响应
"""
await RoleService.delete_role_service(ids=ids, auth=auth)
logger.info(f"删除角色成功: {ids}")
log.info(f"删除角色成功: {ids}")
return SuccessResponse(msg="删除角色成功")
@@ -149,7 +149,7 @@ async def batch_set_available_obj_controller(
- JSONResponse: 批量修改角色状态JSON响应
"""
await RoleService.set_role_available_service(data=data, auth=auth)
logger.info(f"批量修改角色状态成功: {data.ids}")
log.info(f"批量修改角色状态成功: {data.ids}")
return SuccessResponse(msg="批量修改角色状态成功")
@@ -169,7 +169,7 @@ async def set_role_permission_controller(
- JSONResponse: 角色授权JSON响应
"""
await RoleService.set_role_permission_service(data=data, auth=auth)
logger.info(f"设置角色权限成功: {data}")
log.info(f"设置角色权限成功: {data}")
return SuccessResponse(msg="授权角色成功")
@@ -190,7 +190,7 @@ async def export_obj_list_controller(
"""
role_query_result = await RoleService.get_role_list_service(search=search, auth=auth)
role_export_result = await RoleService.export_role_list_service(role_list=role_query_result)
logger.info('导出角色成功')
log.info('导出角色成功')
return StreamResponse(
data=bytes2file_response(role_export_result),
@@ -10,7 +10,7 @@ from app.core.base_params import PaginationQueryParam
from app.core.dependencies import AuthPermission
from app.core.router_class import OperationLogRoute
from app.core.base_schema import BatchSetAvailable
from app.core.logger import logger
from app.core.logger import log
from app.api.v1.module_system.auth.schema import AuthSchema
from .param import TenantQueryParam
@@ -39,7 +39,7 @@ async def get_obj_detail_controller(
- JSONResponse: 包含租户详情的JSON响应
"""
result_dict = await TenantService.detail_service(id=id, auth=auth)
logger.info(f"获取租户详情成功 {id}")
log.info(f"获取租户详情成功 {id}")
return SuccessResponse(data=result_dict, msg="获取租户详情成功")
@TenantRouter.get("/list", summary="查询租户列表", description="查询租户列表")
@@ -67,7 +67,7 @@ async def get_obj_list_controller(
search=search,
order_by=page.order_by
)
logger.info("查询租户列表成功")
log.info("查询租户列表成功")
return SuccessResponse(data=result_dict, msg="查询租户列表成功")
@TenantRouter.post("/create", summary="创建租户", description="创建租户")
@@ -86,7 +86,7 @@ async def create_obj_controller(
- JSONResponse: 包含创建租户详情的JSON响应
"""
result_dict = await TenantService.create_service(auth=auth, data=data)
logger.info(f"创建租户成功: {result_dict.get('name')}")
log.info(f"创建租户成功: {result_dict.get('name')}")
return SuccessResponse(data=result_dict, msg="创建租户成功")
@TenantRouter.put("/update/{id}", summary="修改租户", description="修改租户")
@@ -107,7 +107,7 @@ async def update_obj_controller(
- JSONResponse: 包含修改租户详情的JSON响应
"""
result_dict = await TenantService.update_service(auth=auth, id=id, data=data)
logger.info(f"修改租户成功: {result_dict.get('name')}")
log.info(f"修改租户成功: {result_dict.get('name')}")
return SuccessResponse(data=result_dict, msg="修改租户成功")
@TenantRouter.delete("/delete", summary="删除租户", description="删除租户")
@@ -126,7 +126,7 @@ async def delete_obj_controller(
- JSONResponse: 包含删除租户详情的JSON响应
"""
await TenantService.delete_service(auth=auth, ids=ids)
logger.info(f"删除租户成功: {ids}")
log.info(f"删除租户成功: {ids}")
return SuccessResponse(msg="删除租户成功")
@TenantRouter.patch("/available/setting", summary="批量修改租户状态", description="批量修改租户状态")
@@ -145,7 +145,7 @@ async def batch_set_available_obj_controller(
- JSONResponse: 包含批量修改租户状态详情的JSON响应
"""
await TenantService.set_available_service(auth=auth, data=data)
logger.info(f"批量修改租户状态成功: {data.ids}")
log.info(f"批量修改租户状态成功: {data.ids}")
return SuccessResponse(msg="批量修改租户状态成功")
@TenantRouter.post('/export', summary="导出租户", description="导出租户")
@@ -165,7 +165,7 @@ async def export_obj_list_controller(
"""
result_dict_list = await TenantService.list_service(search=search, auth=auth)
export_result = await TenantService.batch_export_service(obj_list=result_dict_list)
logger.info('导出租户成功')
log.info('导出租户成功')
return StreamResponse(
data=bytes2file_response(export_result),
@@ -191,7 +191,7 @@ async def import_obj_list_controller(
- JSONResponse: 包含导入租户详情的JSON响应
"""
batch_import_result = await TenantService.batch_import_service(file=file, auth=auth, update_support=True)
logger.info(f"导入租户成功: {batch_import_result}")
log.info(f"导入租户成功: {batch_import_result}")
return SuccessResponse(data=batch_import_result, msg="导入租户成功")
@TenantRouter.post('/download/template', summary="获取租户导入模板", description="获取租户导入模板", dependencies=[Depends(AuthPermission(["module_system:tenant:download"]))])
@@ -203,7 +203,7 @@ async def export_obj_template_controller() -> StreamingResponse:
- StreamingResponse: 包含租户导入模板的Excel文件流响应
"""
example_import_template_result = await TenantService.import_template_download_service()
logger.info('获取租户导入模板成功')
log.info('获取租户导入模板成功')
return StreamResponse(
data=bytes2file_response(example_import_template_result),
@@ -8,7 +8,7 @@ import pandas as pd
from app.core.base_schema import BatchSetAvailable
from app.core.exceptions import CustomException
from app.utils.excel_util import ExcelUtil
from app.core.logger import logger
from app.core.logger import log
from app.api.v1.module_system.auth.schema import AuthSchema
from .schema import TenantCreateSchema, TenantUpdateSchema, TenantOutSchema
@@ -286,7 +286,7 @@ class TenantService:
return result
except Exception as e:
logger.error(f"批量导入用户失败: {str(e)}")
log.error(f"批量导入用户失败: {str(e)}")
raise CustomException(msg=f"导入失败: {str(e)}")
@classmethod
@@ -12,7 +12,7 @@ from app.core.router_class import OperationLogRoute
from app.core.dependencies import db_getter, get_current_user, AuthPermission
from app.core.base_params import PaginationQueryParam
from app.core.base_schema import BatchSetAvailable
from app.core.logger import logger
from app.core.logger import log
from ..auth.schema import AuthSchema
from .service import UserService
@@ -45,7 +45,7 @@ async def get_current_user_info_controller(
- JSONResponse: 当前用户信息JSON响应
"""
result_dict = await UserService.get_current_user_info_service(auth=auth)
logger.info(f"获取当前用户信息成功")
log.info(f"获取当前用户信息成功")
return SuccessResponse(data=result_dict, msg='获取当前用户信息成功')
@@ -65,7 +65,7 @@ async def user_avatar_upload_controller(
- JSONResponse: 上传头像JSON响应
"""
result_str = await UserService.upload_avatar_service(base_url=str(request.base_url), file=file)
logger.info(f"上传头像成功: {result_str}")
log.info(f"上传头像成功: {result_str}")
return SuccessResponse(data=result_str, msg='上传头像成功')
@@ -85,7 +85,7 @@ async def update_current_user_info_controller(
- JSONResponse: 更新当前用户基本信息JSON响应
"""
result_dict = await UserService.update_current_user_info_service(data=data, auth=auth)
logger.info(f"更新当前用户基本信息成功: {result_dict}")
log.info(f"更新当前用户基本信息成功: {result_dict}")
return SuccessResponse(data=result_dict, msg='更新当前用户基本信息成功')
@@ -105,7 +105,7 @@ async def change_current_user_password_controller(
- JSONResponse: 修改密码JSON响应
"""
result_dict = await UserService.change_user_password_service(data=data, auth=auth)
logger.info(f"修改密码成功: {result_dict}")
log.info(f"修改密码成功: {result_dict}")
return SuccessResponse(data=result_dict, msg='修改密码成功, 请重新登录')
@UserRouter.put("/reset/password", summary="重置密码", description="重置密码")
@@ -124,7 +124,7 @@ async def reset_password_controller(
- JSONResponse: 重置密码JSON响应
"""
result_dict = await UserService.reset_user_password_service(data=data, auth=auth)
logger.info(f"重置密码成功: {result_dict}")
log.info(f"重置密码成功: {result_dict}")
return SuccessResponse(data=result_dict, msg='重置密码成功')
@UserRouter.post('/register', summary="注册用户", description="注册用户")
@@ -144,7 +144,7 @@ async def register_user_controller(
"""
auth = AuthSchema(db=db)
user_register_result = await UserService.register_user_service(data=data, auth=auth)
logger.info(f"{data.username} 注册用户成功: {user_register_result}")
log.info(f"{data.username} 注册用户成功: {user_register_result}")
return SuccessResponse(data=user_register_result, msg='注册用户成功')
@@ -165,7 +165,7 @@ async def forget_password_controller(
"""
auth = AuthSchema(db=db)
user_forget_password_result = await UserService.forget_password_service(data=data, auth=auth)
logger.info(f"{data.username} 重置密码成功: {user_forget_password_result}")
log.info(f"{data.username} 重置密码成功: {user_forget_password_result}")
return SuccessResponse(data=user_forget_password_result, msg='重置密码成功')
@@ -188,7 +188,7 @@ async def get_obj_list_controller(
"""
result_dict_list = await UserService.get_user_list_service(search=search, auth=auth, 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(f"查询用户成功")
log.info(f"查询用户成功")
return SuccessResponse(data=result_dict, msg="查询用户成功")
@@ -208,7 +208,7 @@ async def get_obj_detail_controller(
- JSONResponse: 用户详情JSON响应
"""
result_dict = await UserService.get_detail_by_id_service(id=id, auth=auth)
logger.info(f"获取用户详情成功 {id}")
log.info(f"获取用户详情成功 {id}")
return SuccessResponse(data=result_dict, msg='获取用户详情成功')
@@ -232,7 +232,7 @@ async def create_obj_controller(
- JSONResponse: 创建用户JSON响应
"""
result_dict = await UserService.create_user_service(data=data, auth=auth)
logger.info(f"创建用户成功: {result_dict}")
log.info(f"创建用户成功: {result_dict}")
return SuccessResponse(data=result_dict, msg="创建用户成功")
@@ -254,7 +254,7 @@ async def update_obj_controller(
- JSONResponse: 修改用户JSON响应
"""
result_dict = await UserService.update_user_service(id=id, data=data, auth=auth)
logger.info(f"修改用户成功: {result_dict}")
log.info(f"修改用户成功: {result_dict}")
return SuccessResponse(data=result_dict, msg="修改用户成功")
@@ -274,7 +274,7 @@ async def delete_obj_controller(
- JSONResponse: 删除用户JSON响应
"""
await UserService.delete_user_service(ids=ids, auth=auth)
logger.info(f"删除用户成功: {ids}")
log.info(f"删除用户成功: {ids}")
return SuccessResponse(msg="删除用户成功")
@@ -294,7 +294,7 @@ async def batch_set_available_obj_controller(
- JSONResponse: 批量修改用户状态JSON响应
"""
await UserService.set_user_available_service(data=data, auth=auth)
logger.info(f"批量修改用户状态成功: {data.ids}")
log.info(f"批量修改用户状态成功: {data.ids}")
return SuccessResponse(msg="批量修改用户状态成功")
@@ -307,7 +307,7 @@ async def export_obj_template_controller()-> StreamingResponse:
- StreamingResponse: 用户导入模板流响应
"""
user_import_template_result = await UserService.get_import_template_user_service()
logger.info('获取用户导入模板成功')
log.info('获取用户导入模板成功')
return StreamResponse(
data=bytes2file_response(user_import_template_result),
@@ -338,7 +338,7 @@ async def export_obj_list_controller(
"""
user_list = await UserService.get_user_list_service(auth=auth, search=search, order_by=page.order_by)
user_export_result = await UserService.export_user_list_service(user_list)
logger.info('导出用户成功')
log.info('导出用户成功')
return StreamResponse(
data=bytes2file_response(user_export_result),
@@ -365,5 +365,5 @@ async def import_obj_list_controller(
- JSONResponse: 导入用户JSON响应
"""
batch_import_result = await UserService.batch_import_user_service(file=file, auth=auth, update_support=True)
logger.info(f"导入用户成功: {batch_import_result}")
log.info(f"导入用户成功: {batch_import_result}")
return SuccessResponse(data=batch_import_result, msg="导入用户成功")
@@ -8,7 +8,7 @@ import pandas as pd
from app.core.exceptions import CustomException
from app.utils.hash_bcrpy_util import PwdUtil
from app.core.base_schema import BatchSetAvailable, UploadResponseSchema
from app.core.logger import logger
from app.core.logger import log
from app.utils.common_util import traversal_to_tree
from app.utils.excel_util import ExcelUtil
from app.utils.upload_util import UploadUtil
@@ -570,7 +570,7 @@ class UserService:
return result
except Exception as e:
logger.error(f"批量导入用户失败: {str(e)}")
log.error(f"批量导入用户失败: {str(e)}")
raise CustomException(msg=f"导入失败: {str(e)}")
@classmethod