mirror of
https://github.com/fastapiadmin/FastapiAdmin.git
synced 2026-09-21 12:52:26 +00:00
refactor(module_generator): 重构代码生成模块结构
重构代码生成模块,删除冗余文件并优化结构: 1. 删除旧的form、page、table等相关文件 2. 新增gencode模块基础结构 3. 移动jinja2工具类和模板引擎初始化器到utils目录 4. 优化数据库模型关系定义 5. 更新nginx配置支持WebSocket 6. 添加正则验证和加密工具类 7. 修复菜单和部门模型的循环引用问题 8. 优化初始化脚本和配置加载逻辑 调整初始化流程,使用统一会话管理: 1. 修改数据库初始化使用AsyncSessionLocal 2. 优化定时任务初始化逻辑 3. 统一配置和字典服务初始化方式 4. 修复模型关系定义导致的循环导入问题 其他改进: 1. 更新requirements.txt添加依赖 2. 调整IP白名单配置 3. 优化数据库连接日志输出 4. 修复模型继承关系问题
This commit is contained in:
@@ -1,15 +0,0 @@
|
||||
|
||||
class GenConfig:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
# 作者
|
||||
author: str = 'Richard'
|
||||
# 默认生成包路径 system 需改成自己的模块名称 如 system monitor tool
|
||||
packageName: str = ''
|
||||
# 自动去除表前缀,默认是false
|
||||
autoRemovePre: bool = False
|
||||
# 表前缀(生成类名不会包含表前缀,多个用逗号分隔)
|
||||
tablePrefix: str = 'sys_'
|
||||
# 是否允许生成文件覆盖到本地(自定义路径),默认不允许
|
||||
allowOverwrite: bool = False
|
||||
@@ -1,211 +0,0 @@
|
||||
class GenConstants:
|
||||
"""代码生成通用常量"""
|
||||
|
||||
# 单表(增删改查)
|
||||
TPL_CRUD = "crud"
|
||||
|
||||
# 树表(增删改查)
|
||||
TPL_TREE = "tree"
|
||||
|
||||
# 主子表(增删改查)
|
||||
TPL_SUB = "sub"
|
||||
|
||||
# 树编码字段
|
||||
TREE_CODE = "treeCode"
|
||||
|
||||
# 树父编码字段
|
||||
TREE_PARENT_CODE = "treeParentCode"
|
||||
|
||||
# 树名称字段
|
||||
TREE_NAME = "treeName"
|
||||
|
||||
# 上级菜单ID字段
|
||||
PARENT_MENU_ID = "parentMenuId"
|
||||
|
||||
# 上级菜单名称字段
|
||||
PARENT_MENU_NAME = "parentMenuName"
|
||||
|
||||
# 数据库字符串类型
|
||||
TYPE_STRING = ["char", "varchar", "nvarchar", "varchar2"]
|
||||
|
||||
# 数据库文本类型
|
||||
TYPE_TEXT = ["tinytext", "text", "mediumtext", "longtext"]
|
||||
|
||||
# 数据库时间类型
|
||||
TYPE_DATE_TIME = ["datetime", "time", "date", "timestamp" ]
|
||||
|
||||
# 数据库数字类型
|
||||
TYPE_NUMBER = ["tinyint", "smallint", "mediumint", "int", "number", "integer", "bigint", "float", "float", "double", "decimal"]
|
||||
|
||||
# 页面不需要编辑字段
|
||||
COLUMN_NAME_NOT_EDIT = ["id", "create_by", "dept_id", "create_time", "del_flag", "update_time"]
|
||||
|
||||
# 页面不需要显示的列表字段
|
||||
COLUMN_NAME_NOT_LIST = ["id", "create_by", "dept_id", "create_time", "del_flag", "update_by"]
|
||||
|
||||
# 页面不需要查询字段
|
||||
COLUMN_NAME_NOT_QUERY = ["id", "create_by", "dept_id", "create_time", "del_flag", "update_by", "update_time", "remark"]
|
||||
|
||||
DAO_COLUMN_NOT_EDIT = ["create_by", "dept_id", "create_time", "del_flag", "update_time"]
|
||||
|
||||
# Entity基类字段
|
||||
BASE_ENTITY = ['id', 'create_time', 'update_time', "create_by", "dept_id", 'del_flag']
|
||||
|
||||
# Tree基类字段
|
||||
TREE_ENTITY = ["parentName", "parentId", "orderNum", "ancestors"]
|
||||
|
||||
# 文本框
|
||||
HTML_INPUT = "input"
|
||||
|
||||
# 文本域
|
||||
HTML_TEXTAREA = "textarea"
|
||||
|
||||
# 下拉框
|
||||
HTML_SELECT = "select"
|
||||
|
||||
# 单选框
|
||||
HTML_RADIO = "radio"
|
||||
|
||||
# 复选框
|
||||
HTML_CHECKBOX = "checkbox"
|
||||
|
||||
# 日期控件
|
||||
HTML_DATETIME = "datetime"
|
||||
|
||||
# 图片上传控件
|
||||
HTML_IMAGE_UPLOAD = "imageUpload"
|
||||
|
||||
# 文件上传控件
|
||||
HTML_FILE_UPLOAD = "fileUpload"
|
||||
|
||||
# 富文本控件
|
||||
HTML_EDITOR = "editor"
|
||||
|
||||
# 模糊查询
|
||||
QUERY_LIKE = "LIKE"
|
||||
|
||||
# 相等查询
|
||||
QUERY_EQ = "EQ"
|
||||
|
||||
# 需要
|
||||
REQUIRE = "1"
|
||||
|
||||
# MySQL -> SQLAlchemy 类型映射
|
||||
MYSQL_TO_SQLALCHEMY = {
|
||||
# Numeric Types
|
||||
"TINYINT": "SmallInteger",
|
||||
"SMALLINT": "SmallInteger",
|
||||
"MEDIUMINT": "Integer",
|
||||
"INT": "Integer",
|
||||
"INTEGER": "Integer",
|
||||
"BIGINT": "BigInteger",
|
||||
"FLOAT": "Float",
|
||||
"DOUBLE": "Float",
|
||||
"DECIMAL": "Numeric",
|
||||
"NUMERIC": "Numeric",
|
||||
|
||||
# String Types
|
||||
"CHAR": "String",
|
||||
"VARCHAR": "String",
|
||||
"TEXT": "Text",
|
||||
"TINYTEXT": "Text",
|
||||
"MEDIUMTEXT": "Text",
|
||||
"LONGTEXT": "Text",
|
||||
"BLOB": "LargeBinary",
|
||||
"TINYBLOB": "LargeBinary",
|
||||
"MEDIUMBLOB": "LargeBinary",
|
||||
"LONGBLOB": "LargeBinary",
|
||||
|
||||
# Date and Time Types
|
||||
"DATE": "Date",
|
||||
"DATETIME": "DateTime",
|
||||
"TIMESTAMP": "DateTime",
|
||||
"TIME": "Time",
|
||||
"YEAR": "Integer", # MySQL YEAR type is commonly represented as Integer in SQLAlchemy
|
||||
|
||||
# Binary Types
|
||||
"BINARY": "Binary",
|
||||
"VARBINARY": "Binary",
|
||||
|
||||
# Enum and Set Types
|
||||
"ENUM": "Enum",
|
||||
"SET": "Enum", # Set can be represented using Enum type in SQLAlchemy
|
||||
|
||||
# JSON Types
|
||||
"JSON": "JSON", # SQLAlchemy supports JSON type from 1.3.0 version
|
||||
|
||||
# Spatial Types (less common in typical usage)
|
||||
"GEOMETRY": "String", # Can be represented as String or Binary
|
||||
"POINT": "String", # Represented as String in SQLAlchemy
|
||||
"LINESTRING": "String", # Represented as String in SQLAlchemy
|
||||
"POLYGON": "String", # Represented as String in SQLAlchemy
|
||||
|
||||
# Other Types
|
||||
"BIT": "Boolean",
|
||||
"BOOL": "Boolean",
|
||||
"UUID": "String", # UUIDs in SQLAlchemy can be represented as String
|
||||
"BINARY": "Binary", # MySQL BINARY type corresponds to SQLAlchemy's Binary
|
||||
}
|
||||
|
||||
MYSQL_TO_PYTHON = {
|
||||
# 字符串类型
|
||||
"VARCHAR": "str",
|
||||
"CHAR": "str",
|
||||
"TEXT": "str",
|
||||
"TINYTEXT": "str",
|
||||
"MEDIUMTEXT": "str",
|
||||
"LONGTEXT": "str",
|
||||
# 数值类型
|
||||
"INT": "int",
|
||||
"TINYINT": "int",
|
||||
"SMALLINT": "int",
|
||||
"MEDIUMINT": "int",
|
||||
"BIGINT": "int",
|
||||
"FLOAT": "float",
|
||||
"DOUBLE": "float",
|
||||
"DECIMAL": "float",
|
||||
"NUMERIC": "float",
|
||||
"BIT": "bool", # 位字段,0 或 1
|
||||
# 日期和时间类型
|
||||
"DATETIME": "datetime",
|
||||
"TIMESTAMP": "datetime",
|
||||
"DATE": "datetime.date",
|
||||
"TIME": "datetime.time",
|
||||
"YEAR": "int", # 存储年份
|
||||
"TINYINT UNSIGNED": "int", # 无符号小整数类型
|
||||
# 布尔类型
|
||||
"BOOLEAN": "bool",
|
||||
"BOOL": "bool", # 布尔类型,通常与 BOOLEAN 相同
|
||||
# JSON 数据类型
|
||||
"JSON": "dict", # JSON 数据存储为字典
|
||||
# 二进制类型
|
||||
"BLOB": "bytes",
|
||||
"TINYBLOB": "bytes",
|
||||
"MEDIUMBLOB": "bytes",
|
||||
"LONGBLOB": "bytes",
|
||||
# 枚举和集合类型
|
||||
"ENUM": "str", # 枚举类型作为字符串
|
||||
"SET": "list", # 集合类型作为列表
|
||||
# 时间单位类型
|
||||
"DATE": "datetime.date", # 仅日期
|
||||
"TIME": "datetime.time", # 仅时间
|
||||
# 大文本类型
|
||||
"LONGTEXT": "str",
|
||||
"MEDIUMTEXT": "str",
|
||||
"TINYTEXT": "str",
|
||||
# UUID
|
||||
"UUID": "str", # UUID 一般作为字符串
|
||||
# 用于二进制数据
|
||||
"BINARY": "bytes", # 固定长度的二进制数据
|
||||
"VARBINARY": "bytes", # 可变长度的二进制数据
|
||||
# 其他数据类型
|
||||
"GEOMETRY": "bytes", # 空间数据类型,通常存储为字节流
|
||||
"POINT": "bytes", # 点数据类型
|
||||
"LINESTRING": "bytes", # 线数据类型
|
||||
"POLYGON": "bytes", # 多边形数据类型
|
||||
"MULTIPOINT": "bytes", # 多点数据类型
|
||||
"MULTILINESTRING": "bytes", # 多线数据类型
|
||||
"MULTIPOLYGON": "bytes", # 多多边形数据类型
|
||||
"GEOMETRYCOLLECTION": "bytes", # 几何集合类型
|
||||
}
|
||||
|
||||
@@ -1,145 +0,0 @@
|
||||
from typing import List
|
||||
from fastapi import APIRouter, Depends, Query
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from starlette.requests import Request
|
||||
from config.get_db import get_db
|
||||
from module_admin.aspect.data_scope import GetDataScope
|
||||
from module_admin.aspect.interface_auth import CheckUserInterfaceAuth
|
||||
from module_admin.service.login_service import LoginService
|
||||
from module_gen.entity.vo.gen_table_vo import GenTableModel, GenTablePageModel, GenTableIdsModel
|
||||
from module_gen.service.gen_table_service import GenTableService
|
||||
from utils.response_util import ResponseUtil
|
||||
|
||||
gen1Controller = APIRouter(prefix="/tool/gen", tags=["代码生成"], dependencies=[Depends(LoginService.get_current_user)])
|
||||
|
||||
"""代码生成操作处理"""
|
||||
@gen1Controller.get('/list', dependencies=[Depends(CheckUserInterfaceAuth('tool:gen:list'))])
|
||||
async def gen_list(request: Request,
|
||||
gen_table: GenTablePageModel = Depends(GenTablePageModel.as_query),
|
||||
query_db: AsyncSession = Depends(get_db),
|
||||
data_scope_sql: str = Depends(GetDataScope('SysDept'))):
|
||||
"""查询代码生成列表"""
|
||||
table_list = await GenTableService.select_gen_table_list(gen_table, query_db, data_scope_sql)
|
||||
return ResponseUtil.success(model_content=table_list)
|
||||
|
||||
@gen1Controller.get('/db/list', dependencies=[Depends(CheckUserInterfaceAuth('tool:gen:list'))])
|
||||
async def gen_db_list(request: Request,
|
||||
gen_table: GenTablePageModel = Depends(GenTablePageModel.as_query),
|
||||
query_db: AsyncSession = Depends(get_db),
|
||||
data_scope_sql: str = Depends(GetDataScope('SysDept'))):
|
||||
"""查询数据库列表"""
|
||||
db_list = await GenTableService.select_db_table_list(gen_table, query_db, data_scope_sql)
|
||||
return ResponseUtil.success(model_content=db_list)
|
||||
|
||||
@gen1Controller.post('/importTable', dependencies=[Depends(CheckUserInterfaceAuth('tool:gen:import'))])
|
||||
async def import_table(request: Request, tables: str = Query(None),
|
||||
query_db: AsyncSession = Depends(get_db),
|
||||
data_scope_sql: str = Depends(GetDataScope('SysDept'))):
|
||||
"""导入表结构"""
|
||||
tables_array = tables.split(',') if tables else []
|
||||
operate_log = "导入表" + ",".join(tables_array)
|
||||
await GenTableService.import_gen_table(tables_array, query_db)
|
||||
return ResponseUtil.success(operate_log)
|
||||
|
||||
@gen1Controller.get('/getById/{tableId}', dependencies=[Depends(CheckUserInterfaceAuth('tool:gen:query'))])
|
||||
async def get_info(request: Request, tableId: int,
|
||||
query_db: AsyncSession = Depends(get_db),
|
||||
data_scope_sql: str = Depends(GetDataScope('SysDept'))):
|
||||
"""查询表详细信息"""
|
||||
table_info = await GenTableService.select_gen_table_by_id(tableId, query_db, data_scope_sql)
|
||||
all_gen_tables = await GenTableService.select_all_gen_table_list(query_db, data_scope_sql)
|
||||
result = {
|
||||
"info": table_info,
|
||||
"rows": table_info.columns,
|
||||
"tables": all_gen_tables
|
||||
}
|
||||
return ResponseUtil.success(data=result)
|
||||
|
||||
@gen1Controller.get('/tableInfo/{tableName}')
|
||||
async def get_table_info(request: Request, tableName: str,
|
||||
query_db: AsyncSession = Depends(get_db),
|
||||
data_scope_sql: str = Depends(GetDataScope('SysDept'))):
|
||||
"""获取表详细信息"""
|
||||
table_info = GenTableService.select_gen_table_by_name(tableName, query_db)
|
||||
return ResponseUtil.success(data=table_info)
|
||||
|
||||
@gen1Controller.put('', dependencies=[Depends(CheckUserInterfaceAuth('tool:gen:edit'))])
|
||||
async def update_save(request: Request, gen_table: GenTableModel,
|
||||
query_db: AsyncSession = Depends(get_db),
|
||||
data_scope_sql: str = Depends(GetDataScope('SysDept'))):
|
||||
"""修改保存代码生成业务"""
|
||||
await GenTableService.validate_edit(gen_table)
|
||||
await GenTableService.update_gen_table(query_db, gen_table)
|
||||
return ResponseUtil.success()
|
||||
|
||||
@gen1Controller.delete('/{tableIds}', dependencies=[Depends(CheckUserInterfaceAuth('tool:gen:remove'))])
|
||||
async def delete(request: Request, tableIds: str,
|
||||
query_db: AsyncSession = Depends(get_db),
|
||||
data_scope_sql: str = Depends(GetDataScope('SysDept'))):
|
||||
"""删除代码生成"""
|
||||
tableIdsArray = tableIds.split(',') if tableIds else []
|
||||
await GenTableService.delete_gen_table_by_ids(query_db, tableIdsArray)
|
||||
return ResponseUtil.success()
|
||||
|
||||
@gen1Controller.get('/preview/{tableId}', dependencies=[Depends(CheckUserInterfaceAuth('tool:gen:preview'))])
|
||||
async def preview(request: Request, tableId: int,
|
||||
query_db: AsyncSession = Depends(get_db),
|
||||
data_scope_sql: str = Depends(GetDataScope('SysDept'))):
|
||||
"""预览代码"""
|
||||
result, table = await GenTableService.preview_code(query_db, tableId, data_scope_sql)
|
||||
return ResponseUtil.success(data=result)
|
||||
|
||||
# @gen1Controller.get('/download/{tableName}', dependencies=[Depends(CheckUserInterfaceAuth('tool:gen:code'))])
|
||||
# async def download(request: Request, table_name: str,
|
||||
# query_db: AsyncSession = Depends(get_db),
|
||||
# data_scope_sql: str = Depends(GetDataScope('SysDept'))):
|
||||
# """生成代码(下载方式)"""
|
||||
# # 查询表信息
|
||||
# table_info = await GenTableService.select_gen_table_by_name(table_name, query_db)
|
||||
# #生成代码
|
||||
# byte_data = await GenTableService.download_code(table_info)
|
||||
#
|
||||
# # 生成zip文件
|
||||
# return create_zip_file(byte_data, f"{table_info.table_comment}.zip")
|
||||
|
||||
# @gen1Controller.get('/genCode/{tableName}', dependencies=[Depends(CheckUserInterfaceAuth('tool:gen:code'))])
|
||||
# async def generate_code(request: Request, table_name: str,
|
||||
# query_db: AsyncSession = Depends(get_db),
|
||||
# data_scope_sql: str = Depends(GetDataScope('SysDept'))):
|
||||
# """生成代码(自定义路径)"""
|
||||
# # 生成代码
|
||||
# await GenTableService.generate_code(table_name, query_db)
|
||||
# return ResponseUtil.success()
|
||||
|
||||
@gen1Controller.get('/synchDb/{tableName}', dependencies=[Depends(CheckUserInterfaceAuth('tool:gen:edit'))])
|
||||
async def sync_db(request: Request, tableName: str,
|
||||
query_db: AsyncSession = Depends(get_db),
|
||||
data_scope_sql: str = Depends(GetDataScope('SysDept'))):
|
||||
"""同步数据库"""
|
||||
await GenTableService.sync_db(query_db, tableName, data_scope_sql)
|
||||
return ResponseUtil.success()
|
||||
|
||||
|
||||
|
||||
@gen1Controller.get('/batchGenCode', dependencies=[Depends(CheckUserInterfaceAuth('tool:gen:code'))])
|
||||
async def batch_generate_code(request: Request, ids_model: GenTableIdsModel = Depends(GenTableIdsModel.as_query),
|
||||
query_db: AsyncSession = Depends(get_db),
|
||||
data_scope_sql: str = Depends(GetDataScope('SysDept'))):
|
||||
"""批量生成代码"""
|
||||
# 查询表信息
|
||||
table_id_array = ids_model.tb_ids.split(',') if ids_model.tb_ids else []
|
||||
# 生成zip包
|
||||
byte_data = await GenTableService.batch_generate_code(query_db, data_scope_sql, table_id_array)
|
||||
return ResponseUtil.streaming(data=byte_data)
|
||||
|
||||
|
||||
@gen1Controller.post('/createTable', dependencies=[Depends(CheckUserInterfaceAuth('tool:gen:import'))])
|
||||
async def import_table(request: Request, sql: str = Query(None),
|
||||
query_db: AsyncSession = Depends(get_db),
|
||||
data_scope_sql: str = Depends(GetDataScope('SysDept'))):
|
||||
"""创建表结构"""
|
||||
success = await GenTableService.create_table(query_db, sql)
|
||||
if success:
|
||||
return ResponseUtil.success()
|
||||
else:
|
||||
return ResponseUtil.failure(msg="创建失败,请检查语法是否符合mysql标准,并检查后端日志")
|
||||
@@ -1,192 +0,0 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
|
||||
from datetime import datetime, time
|
||||
from typing import List
|
||||
|
||||
from sqlalchemy import and_, delete, desc, func, or_, select, update, text, case, asc
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from module_gen.entity.do.gen_table_column_do import GenTableColumn
|
||||
from module_gen.entity.vo.gen_table_column_vo import GenTableColumnPageModel, GenTableColumnModel
|
||||
from utils.page_util import PageUtil, PageResponseModel
|
||||
|
||||
|
||||
class GenTableColumnDao:
|
||||
|
||||
@classmethod
|
||||
async def get_by_id(cls, db: AsyncSession, gen_table_column_id: int) -> GenTableColumn:
|
||||
"""根据主键获取单条记录"""
|
||||
gen_table_column = (((await db.execute(
|
||||
select(GenTableColumn)
|
||||
.where(GenTableColumn.column_id == gen_table_column_id)))
|
||||
.scalars())
|
||||
.first())
|
||||
return gen_table_column
|
||||
|
||||
|
||||
@classmethod
|
||||
async def get_list_by_table_id(cls, db: AsyncSession, table_id: int) -> list[GenTableColumn]:
|
||||
"""根据主键获取单条记录"""
|
||||
gen_table_columns = (((await db.execute(
|
||||
select(GenTableColumn)
|
||||
.where(GenTableColumn.table_id == table_id)))
|
||||
.scalars())
|
||||
.all())
|
||||
return list(gen_table_columns)
|
||||
"""
|
||||
查询
|
||||
"""
|
||||
@classmethod
|
||||
async def get_gen_table_column_list(cls, db: AsyncSession,
|
||||
query_object: GenTableColumnPageModel,
|
||||
data_scope_sql: str = None,
|
||||
is_page: bool = False) -> PageResponseModel|list:
|
||||
|
||||
query = (
|
||||
select(GenTableColumn)
|
||||
.where(
|
||||
|
||||
GenTableColumn.column_id == query_object.column_id if query_object.column_id else True,
|
||||
|
||||
GenTableColumn.table_id == query_object.table_id if query_object.table_id else True,
|
||||
|
||||
GenTableColumn.column_name.like(f"%{query_object.column_name}%") if query_object.column_name else True,
|
||||
|
||||
GenTableColumn.column_comment.like(f"%{query_object.column_comment}%") if query_object.column_comment else True,
|
||||
|
||||
GenTableColumn.column_type.like(f"%{query_object.column_type}%") if query_object.column_type else True,
|
||||
|
||||
GenTableColumn.python_type.like(f"%{query_object.python_type}%") if query_object.python_type else True,
|
||||
|
||||
GenTableColumn.python_field.like(f"%{query_object.python_field}%") if query_object.python_field else True,
|
||||
|
||||
GenTableColumn.is_pk.like(f"%{query_object.is_pk}%") if query_object.is_pk else True,
|
||||
|
||||
GenTableColumn.is_increment.like(f"%{query_object.is_increment}%") if query_object.is_increment else True,
|
||||
|
||||
GenTableColumn.is_required.like(f"%{query_object.is_required}%") if query_object.is_required else True,
|
||||
|
||||
GenTableColumn.is_insert.like(f"%{query_object.is_insert}%") if query_object.is_insert else True,
|
||||
|
||||
GenTableColumn.is_edit.like(f"%{query_object.is_edit}%") if query_object.is_edit else True,
|
||||
|
||||
GenTableColumn.is_list.like(f"%{query_object.is_list}%") if query_object.is_list else True,
|
||||
|
||||
GenTableColumn.is_query.like(f"%{query_object.is_query}%") if query_object.is_query else True,
|
||||
|
||||
GenTableColumn.query_type.like(f"%{query_object.query_type}%") if query_object.query_type else True,
|
||||
|
||||
GenTableColumn.html_type.like(f"%{query_object.html_type}%") if query_object.html_type else True,
|
||||
|
||||
GenTableColumn.dict_type.like(f"%{query_object.dict_type}%") if query_object.dict_type else True,
|
||||
|
||||
GenTableColumn.sort == query_object.sort if query_object.sort else True,
|
||||
|
||||
GenTableColumn.create_by.like(f"%{query_object.create_by}%") if query_object.create_by else True,
|
||||
|
||||
GenTableColumn.create_time == query_object.create_time if query_object.create_time else True,
|
||||
|
||||
GenTableColumn.update_by.like(f"%{query_object.update_by}%") if query_object.update_by else True,
|
||||
|
||||
GenTableColumn.update_time == query_object.update_time if query_object.update_time else True,
|
||||
|
||||
eval(data_scope_sql) if data_scope_sql else True,
|
||||
)
|
||||
.order_by(asc(GenTableColumn.column_name))
|
||||
.distinct()
|
||||
)
|
||||
gen_table_column_list = await PageUtil.paginate(db, query, query_object.page_num, query_object.page_size, is_page)
|
||||
return gen_table_column_list
|
||||
|
||||
|
||||
@classmethod
|
||||
async def add_gen_table_column(cls, db: AsyncSession, add_model: GenTableColumnModel) -> GenTableColumn:
|
||||
"""
|
||||
增加
|
||||
"""
|
||||
gen_table_column = GenTableColumn(**add_model.model_dump(exclude_unset=True))
|
||||
db.add(gen_table_column)
|
||||
await db.flush()
|
||||
return gen_table_column
|
||||
|
||||
@classmethod
|
||||
async def edit_gen_table_column(cls, db: AsyncSession, edit_model: GenTableColumnModel, auto_commit: bool = True, exclude_unset=False):
|
||||
"""
|
||||
修改
|
||||
"""
|
||||
edit_dict_data = edit_model.model_dump(exclude_unset=exclude_unset)
|
||||
await db.execute(update(GenTableColumn), [edit_dict_data])
|
||||
await db.flush()
|
||||
if auto_commit:
|
||||
await db.commit()
|
||||
return edit_model
|
||||
|
||||
@classmethod
|
||||
async def del_gen_table_column(cls, db: AsyncSession, del_model: GenTableColumnModel, soft_del: bool = True, auto_commit: bool = True):
|
||||
"""
|
||||
删除
|
||||
"""
|
||||
if soft_del:
|
||||
await db.execute(update(GenTableColumn).where(GenTableColumn.column_id == del_model.column_id).values(del_flag='2'))
|
||||
else:
|
||||
await db.execute(delete(GenTableColumn).where(GenTableColumn.column_id == del_model.column_id))
|
||||
await db.flush()
|
||||
if auto_commit:
|
||||
await db.commit()
|
||||
|
||||
|
||||
@classmethod
|
||||
async def del_gen_table_column_by_table_ids(cls, db: AsyncSession, table_ids: List[int], soft_del: bool = True, auto_commit: bool = True):
|
||||
"""
|
||||
删除
|
||||
"""
|
||||
if soft_del:
|
||||
await db.execute(update(GenTableColumn).where(GenTableColumn.table_id.in_(table_ids)).values(del_flag='2'))
|
||||
else:
|
||||
await db.execute(delete(GenTableColumn).where(GenTableColumn.table_id.in_(table_ids)))
|
||||
await db.flush()
|
||||
if auto_commit:
|
||||
await db.commit()
|
||||
|
||||
@classmethod
|
||||
async def select_db_table_columns_by_name(cls, session: AsyncSession, table_name: str)-> List[GenTableColumnModel]:
|
||||
"""
|
||||
查询指定表的列信息。
|
||||
:param session: AsyncSession 数据库会话
|
||||
:param table_name: 表名
|
||||
:return: 查询结果映射到 GenTableColumnResult 对象的列表
|
||||
"""
|
||||
# 检查表名是否为空
|
||||
if not table_name:
|
||||
raise ValueError("Table name cannot be empty.")
|
||||
|
||||
# 基础查询
|
||||
query = text("""
|
||||
SELECT column_name,
|
||||
(CASE WHEN (is_nullable = 'no' AND column_key != 'PRI') THEN '1' ELSE '0' END) AS is_required,
|
||||
(CASE WHEN column_key = 'PRI' THEN '1' ELSE '0' END) AS is_pk,
|
||||
ordinal_position AS sort,
|
||||
column_comment,
|
||||
(CASE WHEN extra = 'auto_increment' THEN '1' ELSE '0' END) AS is_increment,
|
||||
column_type
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema = (SELECT DATABASE())
|
||||
AND table_name = :table_name
|
||||
ORDER BY ordinal_position
|
||||
""")
|
||||
|
||||
# 执行查询并传递参数
|
||||
result = await session.execute(query, {"table_name": table_name})
|
||||
rows = result.fetchall()
|
||||
# 将结果映射到 GenTableColumnResult 对象
|
||||
return [
|
||||
GenTableColumnModel(
|
||||
columnName=row[0],
|
||||
isRequired=row[1],
|
||||
isPk=row[2],
|
||||
sort=row[3],
|
||||
columnComment=row[4],
|
||||
isIncrement=row[5],
|
||||
columnType=row[6]
|
||||
)
|
||||
for row in rows
|
||||
]
|
||||
@@ -1,240 +0,0 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
|
||||
from datetime import datetime, time
|
||||
from typing import List
|
||||
|
||||
from sqlalchemy import and_, delete, desc, func, or_, select, update, MetaData, text, not_, Table, Column, String, \
|
||||
DateTime
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy.orm import selectinload
|
||||
|
||||
from module_gen.entity.do.gen_table_do import GenTable
|
||||
from module_gen.entity.vo.gen_table_vo import GenTablePageModel, GenTableModel
|
||||
from utils.common_util import CamelCaseUtil
|
||||
from utils.page_util import PageUtil, PageResponseModel
|
||||
|
||||
|
||||
class GenTableDao:
|
||||
|
||||
@classmethod
|
||||
async def get_by_id(cls, db: AsyncSession, gen_table_id: int) -> GenTable:
|
||||
"""根据主键获取单条记录"""
|
||||
gen_table = (((await db.execute(
|
||||
select(GenTable)
|
||||
.where(GenTable.table_id == gen_table_id)))
|
||||
.scalars())
|
||||
.first())
|
||||
return gen_table
|
||||
|
||||
@classmethod
|
||||
async def get_by_table_name(cls, db: AsyncSession, table_name: str) -> GenTable:
|
||||
"""根据名称获取单条记录"""
|
||||
gen_table = (((await db.execute(
|
||||
select(GenTable)
|
||||
.options(selectinload(GenTable.columns))
|
||||
.where(GenTable.table_name == table_name)))
|
||||
.scalars())
|
||||
.first())
|
||||
return gen_table
|
||||
|
||||
"""
|
||||
查询
|
||||
"""
|
||||
@classmethod
|
||||
async def get_gen_table_list(cls, db: AsyncSession,
|
||||
query_object: GenTablePageModel,
|
||||
data_scope_sql: str,
|
||||
is_page: bool = False) -> PageResponseModel:
|
||||
|
||||
query = (
|
||||
select(GenTable)
|
||||
.options(selectinload(GenTable.columns))
|
||||
.where(
|
||||
|
||||
GenTable.table_id == query_object.table_id if query_object.table_id else True,
|
||||
|
||||
GenTable.table_name.like(f"%{query_object.table_name}%") if query_object.table_name else True,
|
||||
|
||||
GenTable.table_comment.like(f"%{query_object.table_comment}%") if query_object.table_comment else True,
|
||||
|
||||
GenTable.sub_table_name.like(f"%{query_object.sub_table_name}%") if query_object.sub_table_name else True,
|
||||
|
||||
GenTable.sub_table_fk_name.like(f"%{query_object.sub_table_fk_name}%") if query_object.sub_table_fk_name else True,
|
||||
|
||||
GenTable.class_name.like(f"%{query_object.class_name}%") if query_object.class_name else True,
|
||||
|
||||
GenTable.tpl_category.like(f"%{query_object.tpl_category}%") if query_object.tpl_category else True,
|
||||
|
||||
GenTable.tpl_web_type.like(f"%{query_object.tpl_web_type}%") if query_object.tpl_web_type else True,
|
||||
|
||||
GenTable.package_name.like(f"%{query_object.package_name}%") if query_object.package_name else True,
|
||||
|
||||
GenTable.module_name.like(f"%{query_object.module_name}%") if query_object.module_name else True,
|
||||
|
||||
GenTable.business_name.like(f"%{query_object.business_name}%") if query_object.business_name else True,
|
||||
|
||||
GenTable.function_name.like(f"%{query_object.function_name}%") if query_object.function_name else True,
|
||||
|
||||
GenTable.function_author.like(f"%{query_object.function_author}%") if query_object.function_author else True,
|
||||
|
||||
GenTable.gen_type.like(f"%{query_object.gen_type}%") if query_object.gen_type else True,
|
||||
|
||||
GenTable.gen_path.like(f"%{query_object.gen_path}%") if query_object.gen_path else True,
|
||||
|
||||
GenTable.options.like(f"%{query_object.options}%") if query_object.options else True,
|
||||
|
||||
GenTable.create_by.like(f"%{query_object.create_by}%") if query_object.create_by else True,
|
||||
|
||||
GenTable.create_time == query_object.create_time if query_object.create_time else True,
|
||||
|
||||
GenTable.update_by.like(f"%{query_object.update_by}%") if query_object.update_by else True,
|
||||
|
||||
GenTable.update_time == query_object.update_time if query_object.update_time else True,
|
||||
|
||||
GenTable.remark.like(f"%{query_object.remark}%") if query_object.remark else True,
|
||||
|
||||
eval(data_scope_sql),
|
||||
)
|
||||
.order_by(desc(GenTable.create_time))
|
||||
.distinct()
|
||||
)
|
||||
gen_table_list = await PageUtil.paginate(db, query, query_object.page_num, query_object.page_size, is_page)
|
||||
return gen_table_list
|
||||
|
||||
|
||||
@classmethod
|
||||
async def add_gen_table(cls, db: AsyncSession, add_model: GenTableModel) -> GenTable:
|
||||
"""
|
||||
增加
|
||||
"""
|
||||
gen_table = GenTable(**add_model.model_dump(exclude_unset=True, exclude={'sub', 'tree', 'crud'}))
|
||||
db.add(gen_table)
|
||||
await db.flush()
|
||||
return gen_table
|
||||
|
||||
@classmethod
|
||||
async def edit_gen_table(cls, db: AsyncSession, edit_model: GenTableModel, auto_commit: bool = True):
|
||||
"""
|
||||
修改
|
||||
"""
|
||||
edit_dict_data = edit_model.model_dump(exclude_unset=True)
|
||||
await db.execute(update(GenTable), [edit_dict_data])
|
||||
await db.flush()
|
||||
if auto_commit:
|
||||
await db.commit()
|
||||
return edit_model
|
||||
|
||||
@classmethod
|
||||
async def del_gen_table(cls, db: AsyncSession, del_model: GenTableModel, soft_del: bool = True, auto_commit: bool = True):
|
||||
"""
|
||||
删除
|
||||
"""
|
||||
if soft_del:
|
||||
await db.execute(update(GenTable).where(GenTable.table_id == del_model.id).values(del_flag='2'))
|
||||
else:
|
||||
await db.execute(delete(GenTable).where(GenTable.table_id == del_model.id))
|
||||
await db.flush()
|
||||
if auto_commit:
|
||||
await db.commit()
|
||||
|
||||
@classmethod
|
||||
async def del_gen_table_by_ids(cls, db: AsyncSession, ids: List[int], soft_del: bool = True, auto_commit: bool = True):
|
||||
"""
|
||||
批量删除
|
||||
"""
|
||||
if soft_del:
|
||||
await db.execute(update(GenTable).where(GenTable.table_id.in_(ids)).values(del_flag='2'))
|
||||
else:
|
||||
await db.execute(delete(GenTable).where(GenTable.table_id.in_(ids)))
|
||||
await db.flush()
|
||||
if auto_commit:
|
||||
await db.commit()
|
||||
|
||||
# 定义查询方法
|
||||
@classmethod
|
||||
async def select_db_table_list(cls, session: AsyncSession, gen_table: GenTablePageModel, is_page = False) -> PageResponseModel:
|
||||
"""
|
||||
查询数据库中的表信息,根据 GenTable 入参动态添加过滤条件。
|
||||
"""
|
||||
"""查询数据库表列表"""
|
||||
query = (
|
||||
select(
|
||||
text("table_name"),
|
||||
text("table_comment"),
|
||||
text("create_time"),
|
||||
text("update_time"),
|
||||
)
|
||||
.select_from(text("information_schema.tables"))
|
||||
.where(
|
||||
and_(
|
||||
text("table_schema = (select database())"),
|
||||
text("table_name NOT LIKE 'qrtz\\_%'"),
|
||||
text("table_name NOT LIKE 'gen\\_%'"),
|
||||
text("table_name NOT IN (select table_name from gen_table)"),
|
||||
)
|
||||
)
|
||||
)
|
||||
# 动态条件构造
|
||||
if gen_table.table_name:
|
||||
query = query.where(
|
||||
text("lower(table_name) like lower(:table_name)")
|
||||
)
|
||||
if gen_table.table_comment:
|
||||
query = query.where(
|
||||
text("lower(table_comment) like lower(:table_comment)")
|
||||
)
|
||||
# 排序
|
||||
query = query.order_by(text("create_time DESC"))
|
||||
# 参数绑定
|
||||
params = {}
|
||||
if gen_table.table_name:
|
||||
params["table_name"] = f"%{gen_table.table_name}%"
|
||||
if gen_table.table_comment:
|
||||
params["table_comment"] = f"%{gen_table.table_comment}%"
|
||||
|
||||
rows = await PageUtil.paginate(session, query.params(**params), gen_table.page_num, gen_table.page_size, is_page)
|
||||
return rows
|
||||
|
||||
|
||||
@classmethod
|
||||
async def select_db_table_list_by_names(cls, session: AsyncSession, table_names: List[str]):
|
||||
"""根据表名称查询数据库表信息"""
|
||||
table_str = ",".join([f"'{item}'" for item in table_names])
|
||||
if not table_names:
|
||||
return []
|
||||
query = select(
|
||||
text("table_name"),
|
||||
text("table_comment"),
|
||||
text("create_time"),
|
||||
text("update_time"),
|
||||
).select_from(text('information_schema.tables')).where(
|
||||
and_(
|
||||
text("table_name NOT LIKE 'qrtz\\_%'"),
|
||||
text("table_name NOT LIKE 'gen\\_%'"),
|
||||
text("table_name NOT IN (select table_name from gen_table)"),
|
||||
text(f"table_name IN ({ table_str })"),
|
||||
text("table_schema = (select database())")
|
||||
)
|
||||
)
|
||||
|
||||
result = await session.execute(query)
|
||||
rows = result.fetchall()
|
||||
# return CamelCaseUtil.transform_result(rows)
|
||||
return [
|
||||
GenTableModel(tableName=row[0], tableComment=row[1], createTime=row[2], updateTime=row[3]) for row in rows
|
||||
]
|
||||
|
||||
@classmethod
|
||||
async def create_table(cls, session: AsyncSession, sql: str) -> bool:
|
||||
""" 创建表 """
|
||||
try:
|
||||
await session.execute(text(sql))
|
||||
# 提交事务
|
||||
await session.commit()
|
||||
await session.flush()
|
||||
return True
|
||||
except Exception as e:
|
||||
# 如果发生异常,回滚事务
|
||||
await session.rollback()
|
||||
print(f"创建表时发生错误: {e}")
|
||||
return False
|
||||
@@ -1,61 +0,0 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
import datetime
|
||||
|
||||
from sqlalchemy import Column, ForeignKey, BigInteger, DateTime, Integer, String, text
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
from config.database import BaseMixin, Base
|
||||
|
||||
|
||||
class GenTableColumn(Base):
|
||||
__tablename__ = "gen_table_column"
|
||||
__table_args__ = ({'comment': '代码生成器-表字段'})
|
||||
|
||||
column_id = Column(BigInteger, primary_key=True, nullable=False, autoincrement=True, comment='编号')
|
||||
|
||||
table_id = Column(Integer, ForeignKey('gen_table.table_id'), nullable=True, comment='归属表编号')
|
||||
|
||||
column_name = Column(String(length=200), comment='列名称')
|
||||
|
||||
column_comment = Column(String(length=500), comment='列描述')
|
||||
|
||||
column_type = Column(String(length=100), comment='列类型')
|
||||
|
||||
python_type = Column(String(length=500), comment='python类型')
|
||||
|
||||
python_field = Column(String(length=200), comment='python字段名')
|
||||
|
||||
is_pk = Column(String(length=1), comment='是否主键(1是)')
|
||||
|
||||
is_increment = Column(String(length=1), comment='是否自增(1是)')
|
||||
|
||||
is_required = Column(String(length=1), comment='是否必填(1是)')
|
||||
|
||||
is_insert = Column(String(length=1), comment='是否为插入字段(1是)')
|
||||
|
||||
is_edit = Column(String(length=1), comment='是否编辑字段(1是)')
|
||||
|
||||
is_list = Column(String(length=1), comment='是否列表字段(1是)')
|
||||
|
||||
is_query = Column(String(length=1), comment='是否查询字段(1是)')
|
||||
|
||||
query_type = Column(String(length=200), default='EQ', comment='查询方式(等于、不等于、大于、小于、范围)')
|
||||
|
||||
html_type = Column(String(length=200), comment='显示类型(文本框、文本域、下拉框、复选框、单选框、日期控件)')
|
||||
|
||||
dict_type = Column(String(length=200), default='', comment='字典类型')
|
||||
|
||||
sort = Column(Integer, comment='排序')
|
||||
|
||||
create_by = Column(String(length=64), default='', comment='创建者')
|
||||
|
||||
update_by = Column(String(length=64), default='', comment='更新者')
|
||||
|
||||
del_flag = Column(String(1), nullable=False, default='0', server_default=text("'0'"), comment='删除标志(0代表存在 2代表删除)')
|
||||
|
||||
create_time = Column(DateTime, nullable=False, default=datetime.datetime.now, comment='创建时间')
|
||||
|
||||
update_time = Column(DateTime, nullable=False, default=datetime.datetime.now, onupdate=datetime.datetime.now, index=True, comment='更新时间')
|
||||
|
||||
tables = relationship('GenTable', back_populates='columns')
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
import datetime
|
||||
|
||||
from sqlalchemy import Column, ForeignKey, BigInteger, DateTime, String, text
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
from config.database import Base
|
||||
|
||||
|
||||
class GenTable(Base):
|
||||
__tablename__ = "gen_table"
|
||||
|
||||
table_id = Column(BigInteger, primary_key=True, nullable=False, autoincrement=True, comment='编号')
|
||||
|
||||
table_name = Column(String(length=200), default='', comment='表名称')
|
||||
|
||||
table_comment = Column(String(length=500), default='', comment='表描述')
|
||||
|
||||
sub_table_name = Column(String(length=64), comment='关联子表的表名')
|
||||
|
||||
sub_table_fk_name = Column(String(length=64), comment='子表关联的外键名')
|
||||
|
||||
class_name = Column(String(length=100), default='', comment='实体类名称')
|
||||
|
||||
tpl_category = Column(String(length=200), default='crud', comment='使用的模板(crud单表操作 tree树表操作)')
|
||||
|
||||
tpl_web_type = Column(String(length=30), default='', comment='前端模板类型(element-ui模版 element-plus模版)')
|
||||
|
||||
package_name = Column(String(length=100), comment='生成包路径')
|
||||
|
||||
module_name = Column(String(length=30), comment='生成模块名')
|
||||
|
||||
business_name = Column(String(length=30), comment='生成业务名')
|
||||
|
||||
function_name = Column(String(length=50), comment='生成功能名')
|
||||
|
||||
function_author = Column(String(length=50), comment='生成功能作者')
|
||||
|
||||
gen_type = Column(String(length=1), default='0', comment='生成代码方式(0zip压缩包 1自定义路径)')
|
||||
|
||||
gen_path = Column(String(length=200), default='/', comment='生成路径(不填默认项目路径)')
|
||||
|
||||
options = Column(String(length=1000), comment='其它生成选项')
|
||||
|
||||
create_by = Column(String(length=64), default='', comment='创建者')
|
||||
|
||||
update_by = Column(String(length=64), default='', comment='更新者')
|
||||
|
||||
remark = Column(String(length=500), comment='备注')
|
||||
|
||||
del_flag = Column(String(1), nullable=False, default='0', server_default=text("'0'"), comment='删除标志(0代表存在 2代表删除)')
|
||||
|
||||
create_time = Column(DateTime, nullable=False, default=datetime.datetime.now, comment='创建时间')
|
||||
|
||||
update_time = Column(DateTime, nullable=False, default=datetime.datetime.now, onupdate=datetime.datetime.now, index=True, comment='更新时间')
|
||||
|
||||
columns = relationship('GenTableColumn', order_by='GenTableColumn.sort', back_populates='tables')
|
||||
@@ -1,67 +0,0 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
from datetime import datetime
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
from pydantic.alias_generators import to_camel
|
||||
from typing import List, Literal, Optional, Union
|
||||
from module_admin.annotation.pydantic_annotation import as_query
|
||||
|
||||
|
||||
class GenTableColumnModel(BaseModel):
|
||||
"""
|
||||
表对应pydantic模型
|
||||
"""
|
||||
model_config = ConfigDict(alias_generator=to_camel, from_attributes=True)
|
||||
|
||||
column_id: Optional[int] = Field(default=None, description='编号')
|
||||
|
||||
table_id: Optional[int] = Field(default=None, description='归属表编号')
|
||||
|
||||
column_name: Optional[str] = Field(default=None, description='列名称')
|
||||
|
||||
column_comment: Optional[str] = Field(default=None, description='列描述')
|
||||
|
||||
column_type: Optional[str] = Field(default=None, description='列类型')
|
||||
|
||||
python_type: Optional[str] = Field(default=None, description='python类型')
|
||||
|
||||
python_field: Optional[str] = Field(default=None, description='python字段名')
|
||||
|
||||
is_pk: Optional[str] = Field(default=None, description='是否主键(1是)')
|
||||
|
||||
is_increment: Optional[str] = Field(default=None, description='是否自增(1是)')
|
||||
|
||||
is_required: Optional[str] = Field(default=None, description='是否必填(1是)')
|
||||
|
||||
is_insert: Optional[str] = Field(default=None, description='是否为插入字段(1是)')
|
||||
|
||||
is_edit: Optional[str] = Field(default=None, description='是否编辑字段(1是)')
|
||||
|
||||
is_list: Optional[str] = Field(default=None, description='是否列表字段(1是)')
|
||||
|
||||
is_query: Optional[str] = Field(default=None, description='是否查询字段(1是)')
|
||||
|
||||
query_type: Optional[str] = Field(default=None, description='查询方式(等于、不等于、大于、小于、范围)')
|
||||
|
||||
html_type: Optional[str] = Field(default=None, description='显示类型(文本框、文本域、下拉框、复选框、单选框、日期控件)')
|
||||
|
||||
dict_type: Optional[str] = Field(default=None, description='字典类型')
|
||||
|
||||
sort: Optional[int] = Field(default=None, description='排序')
|
||||
|
||||
create_by: Optional[str] = Field(default=None, description='创建者')
|
||||
|
||||
create_time: Optional[datetime] = Field(default=None, description='创建时间')
|
||||
|
||||
update_by: Optional[str] = Field(default=None, description='更新者')
|
||||
|
||||
update_time: Optional[datetime] = Field(default=None, description='更新时间')
|
||||
|
||||
|
||||
|
||||
@as_query
|
||||
class GenTableColumnPageModel(GenTableColumnModel):
|
||||
"""
|
||||
分页查询模型
|
||||
"""
|
||||
page_num: int = Field(default=1, description='当前页码')
|
||||
page_size: int = Field(default=10, description='每页记录数')
|
||||
@@ -1,17 +0,0 @@
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
from pydantic.alias_generators import to_camel
|
||||
|
||||
|
||||
class GenTableOptionModel(BaseModel):
|
||||
|
||||
model_config = ConfigDict(alias_generator=to_camel, from_attributes=True)
|
||||
|
||||
parent_menu_id: Optional[int] = Field(default=None, description='所属父级分类')
|
||||
|
||||
tree_code: Optional[str] = Field(default=None, description='tree_code')
|
||||
|
||||
tree_name: Optional[str] = Field(default=None, description='tree_name')
|
||||
|
||||
tree_parent_code: Optional[str] = Field(default=None, description='tree_parent_code')
|
||||
@@ -1,100 +0,0 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
from datetime import datetime
|
||||
from pydantic import BaseModel, ConfigDict, Field, model_validator
|
||||
from pydantic.alias_generators import to_camel
|
||||
from typing import List, Literal, Optional, Union, Dict, Any, Set
|
||||
from module_admin.annotation.pydantic_annotation import as_query
|
||||
from module_gen.constants.gen_constants import GenConstants
|
||||
from module_gen.entity.vo.gen_table_column_vo import GenTableColumnModel
|
||||
|
||||
|
||||
class GenTableBaseModel(BaseModel):
|
||||
"""
|
||||
表对应pydantic模型
|
||||
"""
|
||||
model_config = ConfigDict(alias_generator=to_camel, from_attributes=True)
|
||||
|
||||
table_id: Optional[int] = Field(default=None, description='编号')
|
||||
|
||||
table_name: Optional[str] = Field(default=None, description='表名称')
|
||||
|
||||
table_comment: Optional[str] = Field(default=None, description='表描述')
|
||||
|
||||
sub_table_name: Optional[str] = Field(default=None, description='关联子表的表名')
|
||||
|
||||
sub_table_fk_name: Optional[str] = Field(default=None, description='子表关联的外键名')
|
||||
|
||||
class_name: Optional[str] = Field(default=None, description='实体类名称')
|
||||
|
||||
tpl_category: Optional[str] = Field(default=None, description='使用的模板(crud单表操作 tree树表操作)')
|
||||
|
||||
tpl_web_type: Optional[str] = Field(default=None, description='前端模板类型(element-ui模版 element-plus模版)')
|
||||
|
||||
package_name: Optional[str] = Field(default=None, description='生成包路径')
|
||||
|
||||
module_name: Optional[str] = Field(default=None, description='生成模块名')
|
||||
|
||||
business_name: Optional[str] = Field(default=None, description='生成业务名')
|
||||
|
||||
function_name: Optional[str] = Field(default=None, description='生成功能名')
|
||||
|
||||
function_author: Optional[str] = Field(default=None, description='生成功能作者')
|
||||
|
||||
gen_type: Optional[str] = Field(default=None, description='生成代码方式(0zip压缩包 1自定义路径)')
|
||||
|
||||
gen_path: Optional[str] = Field(default=None, description='生成路径(不填默认项目路径)')
|
||||
|
||||
options: Optional[str] = Field(default=None, description='其它生成选项')
|
||||
|
||||
create_by: Optional[str] = Field(default=None, description='创建者')
|
||||
|
||||
create_time: Optional[datetime] = Field(default=None, description='创建时间')
|
||||
|
||||
update_by: Optional[str] = Field(default=None, description='更新者')
|
||||
|
||||
update_time: Optional[datetime] = Field(default=None, description='更新时间')
|
||||
|
||||
remark: Optional[str] = Field(default=None, description='备注')
|
||||
|
||||
params: Optional[Any] = Field(default=None, description='前端传递过来的表附加信息,转换成json字符串后放到options')
|
||||
|
||||
|
||||
|
||||
|
||||
class GenTableModel(GenTableBaseModel):
|
||||
"""
|
||||
代码生成业务表模型
|
||||
"""
|
||||
|
||||
pk_column: Optional['GenTableColumnModel'] = Field(default=None, description='主键信息')
|
||||
sub_table: Optional['GenTableModel'] = Field(default=None, description='子表信息')
|
||||
columns: Optional[List['GenTableColumnModel']] = Field(default=None, description='表列信息')
|
||||
tree_code: Optional[str] = Field(default=None, description='树编码字段')
|
||||
tree_parent_code: Optional[str] = Field(default=None, description='树父编码字段')
|
||||
tree_name: Optional[str] = Field(default=None, description='树名称字段')
|
||||
parent_menu_id: Optional[int] = Field(default=None, description='解析出options里面的parentMenuId给前端用')
|
||||
parent_menu_name: Optional[str] = Field(default=None, description='上级菜单名称字段')
|
||||
sub: Optional[bool] = Field(default=None, description='是否为子表')
|
||||
tree: Optional[bool] = Field(default=None, description='是否为树表')
|
||||
crud: Optional[bool] = Field(default=None, description='是否为单表')
|
||||
|
||||
@model_validator(mode='after')
|
||||
def check_some_is(self) -> 'GenTableModel':
|
||||
self.sub = True if self.tpl_category and self.tpl_category == GenConstants.TPL_SUB else False
|
||||
self.tree = True if self.tpl_category and self.tpl_category == GenConstants.TPL_TREE else False
|
||||
self.crud = True if self.tpl_category and self.tpl_category == GenConstants.TPL_CRUD else False
|
||||
return self
|
||||
|
||||
@as_query
|
||||
class GenTablePageModel(GenTableBaseModel):
|
||||
"""
|
||||
分页查询模型
|
||||
"""
|
||||
page_num: int = Field(default=1, description='当前页码')
|
||||
page_size: int = Field(default=10, description='每页记录数')
|
||||
|
||||
@as_query
|
||||
class GenTableIdsModel(BaseModel):
|
||||
"""表的table_ids, 逗号分隔"""
|
||||
model_config = ConfigDict(alias_generator=to_camel, from_attributes=True)
|
||||
tb_ids: Optional[str] = Field(description='当前页码')
|
||||
@@ -1,22 +0,0 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
|
||||
from sqlalchemy import Boolean, Column, ForeignKey, String, Integer, Text, DateTime
|
||||
|
||||
from app.core.base_model import BaseMixin
|
||||
|
||||
|
||||
class FormModel(BaseMixin):
|
||||
__tablename__ = "gen_form"
|
||||
|
||||
content = Column(Text, nullable=False, comment='表单代码')
|
||||
|
||||
form_conf = Column(Text, nullable=False, comment='表单配置')
|
||||
|
||||
form_data = Column(Text, nullable=False, comment='表单内容')
|
||||
|
||||
generate_conf = Column(Text, nullable=False, comment='生成配置')
|
||||
|
||||
name = Column(String(255), nullable=False, comment='表单名称')
|
||||
|
||||
drawing_list = Column(Text, nullable=False, comment='字段列表')
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
from datetime import datetime
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
from pydantic.alias_generators import to_camel
|
||||
from typing import List, Literal, Optional, Union
|
||||
from module_admin.annotation.pydantic_annotation import as_query
|
||||
|
||||
|
||||
class SysTableModel(BaseModel):
|
||||
"""
|
||||
表对应pydantic模型
|
||||
"""
|
||||
model_config = ConfigDict(alias_generator=to_camel, from_attributes=True)
|
||||
align: Optional[str] = Field(default=None, description='对其方式')
|
||||
create_time: Optional[datetime] = Field(default=None, description='创建时间')
|
||||
del_flag: Optional[str] = Field(default=None, description='删除标志')
|
||||
field_name: Optional[str] = Field(default=None, description='字段名')
|
||||
fixed: Optional[str] = Field(default=None, description='固定表头')
|
||||
id: Optional[int] = Field(default=None, description='ID')
|
||||
label: Optional[str] = Field(default=None, description='字段标签')
|
||||
label_tip: Optional[str] = Field(default=None, description='字段标签解释')
|
||||
prop: Optional[str] = Field(default=None, description='驼峰属性')
|
||||
show: Optional[str] = Field(default=None, description='可见')
|
||||
sortable: Optional[str] = Field(default=None, description='可排序')
|
||||
table_name: Optional[str] = Field(default=None, description='表名')
|
||||
tooltip: Optional[str] = Field(default=None, description='超出隐藏')
|
||||
update_by: Optional[int] = Field(default=None, description='更新者')
|
||||
update_by_name: Optional[str] = Field(default=None, description='更新者')
|
||||
update_time: Optional[datetime] = Field(default=None, description='更新时间')
|
||||
width: Optional[int] = Field(default=None, description='宽度')
|
||||
sequence: Optional[int] = Field(default=None, description='字段顺序')
|
||||
|
||||
@as_query
|
||||
class SysTablePageModel(SysTableModel):
|
||||
"""
|
||||
分页查询模型
|
||||
"""
|
||||
page_num: int = Field(default=1, description='当前页码')
|
||||
page_size: int = Field(default=10, description='每页记录数')
|
||||
|
||||
|
||||
@as_query
|
||||
class DbTablePageModel(BaseModel):
|
||||
"""
|
||||
分页查询模型
|
||||
"""
|
||||
model_config = ConfigDict(alias_generator=to_camel, from_attributes=True)
|
||||
table_name: Optional[str] = Field(default=None, description='表名')
|
||||
table_comment: Optional[str] = Field(default=None, description='表描述')
|
||||
page_num: int = Field(default=1, description='当前页码')
|
||||
page_size: int = Field(default=10, description='每页记录数')
|
||||
|
||||
class SysTableColumnIdsModel(BaseModel):
|
||||
"""
|
||||
列排序
|
||||
"""
|
||||
model_config = ConfigDict(alias_generator=to_camel, from_attributes=True)
|
||||
ids: Optional[List[int]] = Field()
|
||||
@@ -1,192 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import io
|
||||
from typing import Any, List, Dict
|
||||
from fastapi import UploadFile
|
||||
import pandas as pd
|
||||
|
||||
from app.api.v1.schemas.system.auth_schema import AuthSchema
|
||||
from app.api.v1.schemas.demo.example_schema import ExampleCreateSchema, ExampleUpdateSchema, ExampleOutSchema
|
||||
from app.core.base_schema import BatchSetAvailable
|
||||
from app.api.v1.params.demo.example_param import ExampleQueryParams
|
||||
from app.api.v1.cruds.demo.example_crud import ExampleCRUD
|
||||
from app.core.exceptions import CustomException
|
||||
from app.utils.excel_util import ExcelUtil
|
||||
from app.core.logger import logger
|
||||
|
||||
|
||||
class ExampleService:
|
||||
"""
|
||||
示例管理模块服务层
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
async def get_example_detail_service(cls, auth: AuthSchema, id: int) -> Dict:
|
||||
"""详情"""
|
||||
obj = await ExampleCRUD(auth).get_by_id_crud(id=id)
|
||||
return ExampleOutSchema.model_validate(obj).model_dump()
|
||||
|
||||
@classmethod
|
||||
async def get_demo_list_service(cls, auth: AuthSchema, search: ExampleQueryParams = None, order_by: List[Dict[str, str]] = None) -> List[Dict]:
|
||||
"""列表查询"""
|
||||
if order_by:
|
||||
order_by = eval(order_by)
|
||||
obj_list = await ExampleCRUD(auth).get_list_crud(search=search.__dict__, order_by=order_by)
|
||||
return [ExampleOutSchema.model_validate(obj).model_dump() for obj in obj_list]
|
||||
|
||||
@classmethod
|
||||
async def create_example_service(cls, auth: AuthSchema, data: ExampleCreateSchema) -> Dict:
|
||||
"""创建"""
|
||||
obj = await ExampleCRUD(auth).get(name=data.name)
|
||||
if obj:
|
||||
raise CustomException(msg='创建失败,名称已存在')
|
||||
obj = await ExampleCRUD(auth).create_crud(data=data)
|
||||
return ExampleOutSchema.model_validate(obj).model_dump()
|
||||
|
||||
@classmethod
|
||||
async def update_example_service(cls, auth: AuthSchema, data: ExampleUpdateSchema) -> Dict:
|
||||
"""更新"""
|
||||
obj = await ExampleCRUD(auth).get_by_id_crud(id=data.id)
|
||||
if not obj:
|
||||
raise CustomException(msg='更新失败,该数据不存在')
|
||||
exist_obj = await ExampleCRUD(auth).get(name=data.name)
|
||||
if exist_obj and exist_obj.id != data.id:
|
||||
raise CustomException(msg='更新失败,名称重复')
|
||||
obj = await ExampleCRUD(auth).update_crud(id=data.id, data=data)
|
||||
return ExampleOutSchema.model_validate(obj).model_dump()
|
||||
|
||||
@classmethod
|
||||
async def delete_example_service(cls, auth: AuthSchema, ids: list[int]) -> None:
|
||||
"""删除"""
|
||||
if len(ids) < 1:
|
||||
raise CustomException(msg='删除失败,删除对象不能为空')
|
||||
for id in ids:
|
||||
obj = await ExampleCRUD(auth).get_by_id_crud(id=id)
|
||||
if not obj:
|
||||
raise CustomException(msg='删除失败,该数据不存在')
|
||||
await ExampleCRUD(auth).delete_crud(ids=ids)
|
||||
|
||||
@classmethod
|
||||
async def set_example_available_service(cls, auth: AuthSchema, data: BatchSetAvailable) -> None:
|
||||
"""批量设置状态"""
|
||||
await ExampleCRUD(auth).set_available_crud(ids=data.ids, status=data.status)
|
||||
|
||||
@classmethod
|
||||
async def batch_export_service(cls, obj_list: List[Dict[str, Any]]) -> bytes:
|
||||
"""批量导出"""
|
||||
mapping_dict = {
|
||||
'id': '编号',
|
||||
'name': '名称',
|
||||
'status': '状态',
|
||||
'description': '备注',
|
||||
'created_at': '创建时间',
|
||||
'updated_at': '更新时间',
|
||||
'creator': '创建者',
|
||||
}
|
||||
|
||||
# 复制数据并转换状态
|
||||
data = obj_list.copy()
|
||||
for item in data:
|
||||
# 处理状态
|
||||
item['status'] = '正常' if item.get('status') else '停用'
|
||||
# 处理公告类型
|
||||
item['creator'] = item.get('creator', {}).get('name', '未知') if isinstance(item.get('creator'), dict) else '未知'
|
||||
|
||||
return ExcelUtil.export_list2excel(list_data=obj_list, mapping_dict=mapping_dict)
|
||||
|
||||
@classmethod
|
||||
async def batch_import_service(cls, auth: AuthSchema, file: UploadFile, update_support: bool = False) -> str:
|
||||
"""批量导入"""
|
||||
|
||||
header_dict = {
|
||||
'名称': 'name',
|
||||
'状态': 'status',
|
||||
'描述': 'description'
|
||||
}
|
||||
|
||||
try:
|
||||
# 读取Excel文件
|
||||
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)
|
||||
|
||||
# 验证必填字段
|
||||
required_fields = ['name', 'status']
|
||||
for field in required_fields:
|
||||
if df[field].isnull().any():
|
||||
missing_rows = df[df[field].isnull()].index.tolist()
|
||||
raise CustomException(msg=f"{[k for k,v in header_dict.items() if v == field][0]}不能为空,第{[i+1 for i in missing_rows]}行")
|
||||
|
||||
error_msgs = []
|
||||
success_count = 0
|
||||
|
||||
# 处理每一行数据
|
||||
for index, row in df.iterrows():
|
||||
try:
|
||||
# 数据转换前的类型检查
|
||||
try:
|
||||
name = str(row['name'])
|
||||
except ValueError:
|
||||
error_msgs.append(f"第{index+1}行: 名称必须是字符串")
|
||||
continue
|
||||
try:
|
||||
status = True if row['status'] == '正常' else False
|
||||
except ValueError:
|
||||
error_msgs.append(f"第{index+1}行: 状态必须是'正常'或'停用'")
|
||||
continue
|
||||
|
||||
# 构建用户数据
|
||||
data = {
|
||||
"name": name,
|
||||
"status": status,
|
||||
"description": str(row['description']).strip() if not pd.isna(row['description']) else None,
|
||||
}
|
||||
|
||||
# 处理用户导入
|
||||
exists_user = await ExampleCRUD(auth).get(name=data["name"])
|
||||
if exists_user:
|
||||
if update_support:
|
||||
await ExampleCRUD(auth).update(id=exists_user.id, data=data)
|
||||
success_count += 1
|
||||
else:
|
||||
error_msgs.append(f"第{index+1}行: 用户 {data['username']} 已存在")
|
||||
else:
|
||||
await ExampleCRUD(auth).create(data=data)
|
||||
success_count += 1
|
||||
|
||||
except Exception as e:
|
||||
error_msgs.append(f"第{index+1}行: {str(e)}")
|
||||
continue
|
||||
|
||||
# 返回详细的导入结果
|
||||
result = f"成功导入 {success_count} 条数据"
|
||||
if error_msgs:
|
||||
result += "\n错误信息:\n" + "\n".join(error_msgs)
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"批量导入用户失败: {str(e)}")
|
||||
raise CustomException(msg=f"导入失败: {str(e)}")
|
||||
|
||||
@classmethod
|
||||
async def import_template_download_service(cls) -> bytes:
|
||||
"""下载导入模板"""
|
||||
header_list = ['名称', '状态', '描述']
|
||||
selector_header_list = ['状态']
|
||||
option_list = [{'状态': ['正常', '停用']}]
|
||||
return ExcelUtil.get_excel_template(
|
||||
header_list=header_list,
|
||||
selector_header_list=selector_header_list,
|
||||
option_list=option_list
|
||||
)
|
||||
@@ -1,2 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
|
||||
from sqlalchemy import Boolean, Column, ForeignKey, String, Integer, Text, DateTime
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
from app.core.base_model import BaseMixin
|
||||
|
||||
|
||||
class FormData(BaseMixin):
|
||||
__tablename__ = "gen_form_data"
|
||||
|
||||
form_data = Column(Text, nullable=False, comment='表单数据')
|
||||
|
||||
form_id = Column(Integer, ForeignKey(SysForm.id), nullable=False, comment='表单ID')
|
||||
|
||||
form_name = Column(String(255), nullable=False, comment='表单名称')
|
||||
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
from datetime import datetime
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
from pydantic.alias_generators import to_camel
|
||||
from typing import List, Literal, Optional, Union
|
||||
from module_admin.annotation.pydantic_annotation import as_query
|
||||
|
||||
|
||||
class SysFormDataModel(BaseModel):
|
||||
"""
|
||||
表对应pydantic模型
|
||||
"""
|
||||
model_config = ConfigDict(alias_generator=to_camel, from_attributes=True)
|
||||
create_by: Optional[int] = Field(default=None, description='创建者')
|
||||
create_time: Optional[datetime] = Field(default=None, description='创建时间')
|
||||
del_flag: Optional[str] = Field(default=None, description='删除标志')
|
||||
dept_id: Optional[int] = Field(default=None, description='部门id')
|
||||
form_data: Optional[str] = Field(default=None, description='表单数据')
|
||||
form_id: Optional[int] = Field(default=None, description='表单ID')
|
||||
form_name: Optional[str] = Field(default=None, description='表单名称')
|
||||
id: Optional[int] = Field(default=None, description='id')
|
||||
update_time: Optional[datetime] = Field(default=None, description='更新时间')
|
||||
|
||||
|
||||
@as_query
|
||||
class SysFormDataPageModel(SysFormDataModel):
|
||||
"""
|
||||
分页查询模型
|
||||
"""
|
||||
page_num: int = Field(default=1, description='当前页码')
|
||||
page_size: int = Field(default=10, description='每页记录数')
|
||||
@@ -1,192 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import io
|
||||
from typing import Any, List, Dict
|
||||
from fastapi import UploadFile
|
||||
import pandas as pd
|
||||
|
||||
from app.api.v1.schemas.system.auth_schema import AuthSchema
|
||||
from app.api.v1.schemas.demo.example_schema import ExampleCreateSchema, ExampleUpdateSchema, ExampleOutSchema
|
||||
from app.core.base_schema import BatchSetAvailable
|
||||
from app.api.v1.params.demo.example_param import ExampleQueryParams
|
||||
from app.api.v1.cruds.demo.example_crud import ExampleCRUD
|
||||
from app.core.exceptions import CustomException
|
||||
from app.utils.excel_util import ExcelUtil
|
||||
from app.core.logger import logger
|
||||
|
||||
|
||||
class ExampleService:
|
||||
"""
|
||||
示例管理模块服务层
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
async def get_example_detail_service(cls, auth: AuthSchema, id: int) -> Dict:
|
||||
"""详情"""
|
||||
obj = await ExampleCRUD(auth).get_by_id_crud(id=id)
|
||||
return ExampleOutSchema.model_validate(obj).model_dump()
|
||||
|
||||
@classmethod
|
||||
async def get_demo_list_service(cls, auth: AuthSchema, search: ExampleQueryParams = None, order_by: List[Dict[str, str]] = None) -> List[Dict]:
|
||||
"""列表查询"""
|
||||
if order_by:
|
||||
order_by = eval(order_by)
|
||||
obj_list = await ExampleCRUD(auth).get_list_crud(search=search.__dict__, order_by=order_by)
|
||||
return [ExampleOutSchema.model_validate(obj).model_dump() for obj in obj_list]
|
||||
|
||||
@classmethod
|
||||
async def create_example_service(cls, auth: AuthSchema, data: ExampleCreateSchema) -> Dict:
|
||||
"""创建"""
|
||||
obj = await ExampleCRUD(auth).get(name=data.name)
|
||||
if obj:
|
||||
raise CustomException(msg='创建失败,名称已存在')
|
||||
obj = await ExampleCRUD(auth).create_crud(data=data)
|
||||
return ExampleOutSchema.model_validate(obj).model_dump()
|
||||
|
||||
@classmethod
|
||||
async def update_example_service(cls, auth: AuthSchema, data: ExampleUpdateSchema) -> Dict:
|
||||
"""更新"""
|
||||
obj = await ExampleCRUD(auth).get_by_id_crud(id=data.id)
|
||||
if not obj:
|
||||
raise CustomException(msg='更新失败,该数据不存在')
|
||||
exist_obj = await ExampleCRUD(auth).get(name=data.name)
|
||||
if exist_obj and exist_obj.id != data.id:
|
||||
raise CustomException(msg='更新失败,名称重复')
|
||||
obj = await ExampleCRUD(auth).update_crud(id=data.id, data=data)
|
||||
return ExampleOutSchema.model_validate(obj).model_dump()
|
||||
|
||||
@classmethod
|
||||
async def delete_example_service(cls, auth: AuthSchema, ids: list[int]) -> None:
|
||||
"""删除"""
|
||||
if len(ids) < 1:
|
||||
raise CustomException(msg='删除失败,删除对象不能为空')
|
||||
for id in ids:
|
||||
obj = await ExampleCRUD(auth).get_by_id_crud(id=id)
|
||||
if not obj:
|
||||
raise CustomException(msg='删除失败,该数据不存在')
|
||||
await ExampleCRUD(auth).delete_crud(ids=ids)
|
||||
|
||||
@classmethod
|
||||
async def set_example_available_service(cls, auth: AuthSchema, data: BatchSetAvailable) -> None:
|
||||
"""批量设置状态"""
|
||||
await ExampleCRUD(auth).set_available_crud(ids=data.ids, status=data.status)
|
||||
|
||||
@classmethod
|
||||
async def batch_export_service(cls, obj_list: List[Dict[str, Any]]) -> bytes:
|
||||
"""批量导出"""
|
||||
mapping_dict = {
|
||||
'id': '编号',
|
||||
'name': '名称',
|
||||
'status': '状态',
|
||||
'description': '备注',
|
||||
'created_at': '创建时间',
|
||||
'updated_at': '更新时间',
|
||||
'creator': '创建者',
|
||||
}
|
||||
|
||||
# 复制数据并转换状态
|
||||
data = obj_list.copy()
|
||||
for item in data:
|
||||
# 处理状态
|
||||
item['status'] = '正常' if item.get('status') else '停用'
|
||||
# 处理公告类型
|
||||
item['creator'] = item.get('creator', {}).get('name', '未知') if isinstance(item.get('creator'), dict) else '未知'
|
||||
|
||||
return ExcelUtil.export_list2excel(list_data=obj_list, mapping_dict=mapping_dict)
|
||||
|
||||
@classmethod
|
||||
async def batch_import_service(cls, auth: AuthSchema, file: UploadFile, update_support: bool = False) -> str:
|
||||
"""批量导入"""
|
||||
|
||||
header_dict = {
|
||||
'名称': 'name',
|
||||
'状态': 'status',
|
||||
'描述': 'description'
|
||||
}
|
||||
|
||||
try:
|
||||
# 读取Excel文件
|
||||
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)
|
||||
|
||||
# 验证必填字段
|
||||
required_fields = ['name', 'status']
|
||||
for field in required_fields:
|
||||
if df[field].isnull().any():
|
||||
missing_rows = df[df[field].isnull()].index.tolist()
|
||||
raise CustomException(msg=f"{[k for k,v in header_dict.items() if v == field][0]}不能为空,第{[i+1 for i in missing_rows]}行")
|
||||
|
||||
error_msgs = []
|
||||
success_count = 0
|
||||
|
||||
# 处理每一行数据
|
||||
for index, row in df.iterrows():
|
||||
try:
|
||||
# 数据转换前的类型检查
|
||||
try:
|
||||
name = str(row['name'])
|
||||
except ValueError:
|
||||
error_msgs.append(f"第{index+1}行: 名称必须是字符串")
|
||||
continue
|
||||
try:
|
||||
status = True if row['status'] == '正常' else False
|
||||
except ValueError:
|
||||
error_msgs.append(f"第{index+1}行: 状态必须是'正常'或'停用'")
|
||||
continue
|
||||
|
||||
# 构建用户数据
|
||||
data = {
|
||||
"name": name,
|
||||
"status": status,
|
||||
"description": str(row['description']).strip() if not pd.isna(row['description']) else None,
|
||||
}
|
||||
|
||||
# 处理用户导入
|
||||
exists_user = await ExampleCRUD(auth).get(name=data["name"])
|
||||
if exists_user:
|
||||
if update_support:
|
||||
await ExampleCRUD(auth).update(id=exists_user.id, data=data)
|
||||
success_count += 1
|
||||
else:
|
||||
error_msgs.append(f"第{index+1}行: 用户 {data['username']} 已存在")
|
||||
else:
|
||||
await ExampleCRUD(auth).create(data=data)
|
||||
success_count += 1
|
||||
|
||||
except Exception as e:
|
||||
error_msgs.append(f"第{index+1}行: {str(e)}")
|
||||
continue
|
||||
|
||||
# 返回详细的导入结果
|
||||
result = f"成功导入 {success_count} 条数据"
|
||||
if error_msgs:
|
||||
result += "\n错误信息:\n" + "\n".join(error_msgs)
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"批量导入用户失败: {str(e)}")
|
||||
raise CustomException(msg=f"导入失败: {str(e)}")
|
||||
|
||||
@classmethod
|
||||
async def import_template_download_service(cls) -> bytes:
|
||||
"""下载导入模板"""
|
||||
header_list = ['名称', '状态', '描述']
|
||||
selector_header_list = ['状态']
|
||||
option_list = [{'状态': ['正常', '停用']}]
|
||||
return ExcelUtil.get_excel_template(
|
||||
header_list=header_list,
|
||||
selector_header_list=selector_header_list,
|
||||
option_list=option_list
|
||||
)
|
||||
@@ -0,0 +1,158 @@
|
||||
from datetime import datetime
|
||||
from fastapi import APIRouter, Depends, Query, Request
|
||||
from pydantic_validation_decorator import ValidateFields
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from config.enums import BusinessType
|
||||
from config.env import GenConfig
|
||||
from config.get_db import get_db
|
||||
from module_admin.annotation.log_annotation import Log
|
||||
from module_admin.aspect.interface_auth import CheckRoleInterfaceAuth, CheckUserInterfaceAuth
|
||||
from module_admin.service.login_service import LoginService
|
||||
from module_admin.entity.vo.user_vo import CurrentUserModel
|
||||
from module_generator.entity.vo.gen_vo import DeleteGenTableModel, EditGenTableModel, GenTablePageQueryModel
|
||||
from module_generator.service.gen_service import GenTableColumnService, GenTableService
|
||||
from utils.common_util import bytes2file_response
|
||||
from utils.log_util import logger
|
||||
from utils.page_util import PageResponseModel
|
||||
from utils.response_util import ResponseUtil
|
||||
|
||||
|
||||
genController = APIRouter(prefix='/tool/gen', dependencies=[Depends(LoginService.get_current_user)])
|
||||
|
||||
|
||||
@genController.get(
|
||||
'/list', response_model=PageResponseModel, dependencies=[Depends(CheckUserInterfaceAuth('tool:gen:list'))]
|
||||
)
|
||||
async def get_gen_table_list(
|
||||
request: Request,
|
||||
gen_page_query: GenTablePageQueryModel = Depends(GenTablePageQueryModel.as_query),
|
||||
query_db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
# 获取分页数据
|
||||
gen_page_query_result = await GenTableService.get_gen_table_list_services(query_db, gen_page_query, is_page=True)
|
||||
logger.info('获取成功')
|
||||
|
||||
return ResponseUtil.success(model_content=gen_page_query_result)
|
||||
|
||||
|
||||
@genController.get(
|
||||
'/db/list', response_model=PageResponseModel, dependencies=[Depends(CheckUserInterfaceAuth('tool:gen:list'))]
|
||||
)
|
||||
async def get_gen_db_table_list(
|
||||
request: Request,
|
||||
gen_page_query: GenTablePageQueryModel = Depends(GenTablePageQueryModel.as_query),
|
||||
query_db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
# 获取分页数据
|
||||
gen_page_query_result = await GenTableService.get_gen_db_table_list_services(query_db, gen_page_query, is_page=True)
|
||||
logger.info('获取成功')
|
||||
|
||||
return ResponseUtil.success(model_content=gen_page_query_result)
|
||||
|
||||
|
||||
@genController.post('/importTable', dependencies=[Depends(CheckUserInterfaceAuth('tool:gen:import'))])
|
||||
@Log(title='代码生成', business_type=BusinessType.IMPORT)
|
||||
async def import_gen_table(
|
||||
request: Request,
|
||||
tables: str = Query(),
|
||||
query_db: AsyncSession = Depends(get_db),
|
||||
current_user: CurrentUserModel = Depends(LoginService.get_current_user),
|
||||
):
|
||||
table_names = tables.split(',') if tables else []
|
||||
add_gen_table_list = await GenTableService.get_gen_db_table_list_by_name_services(query_db, table_names)
|
||||
add_gen_table_result = await GenTableService.import_gen_table_services(query_db, add_gen_table_list, current_user)
|
||||
logger.info(add_gen_table_result.message)
|
||||
|
||||
return ResponseUtil.success(msg=add_gen_table_result.message)
|
||||
|
||||
|
||||
@genController.put('', dependencies=[Depends(CheckUserInterfaceAuth('tool:gen:edit'))])
|
||||
@ValidateFields(validate_model='edit_gen_table')
|
||||
@Log(title='代码生成', business_type=BusinessType.UPDATE)
|
||||
async def edit_gen_table(
|
||||
request: Request,
|
||||
edit_gen_table: EditGenTableModel,
|
||||
query_db: AsyncSession = Depends(get_db),
|
||||
current_user: CurrentUserModel = Depends(LoginService.get_current_user),
|
||||
):
|
||||
edit_gen_table.update_by = current_user.user.user_name
|
||||
edit_gen_table.update_time = datetime.now()
|
||||
await GenTableService.validate_edit(edit_gen_table)
|
||||
edit_gen_result = await GenTableService.edit_gen_table_services(query_db, edit_gen_table)
|
||||
logger.info(edit_gen_result.message)
|
||||
|
||||
return ResponseUtil.success(msg=edit_gen_result.message)
|
||||
|
||||
|
||||
@genController.delete('/{table_ids}', dependencies=[Depends(CheckUserInterfaceAuth('tool:gen:remove'))])
|
||||
@Log(title='代码生成', business_type=BusinessType.DELETE)
|
||||
async def delete_gen_table(request: Request, table_ids: str, query_db: AsyncSession = Depends(get_db)):
|
||||
delete_gen_table = DeleteGenTableModel(tableIds=table_ids)
|
||||
delete_gen_table_result = await GenTableService.delete_gen_table_services(query_db, delete_gen_table)
|
||||
logger.info(delete_gen_table_result.message)
|
||||
|
||||
return ResponseUtil.success(msg=delete_gen_table_result.message)
|
||||
|
||||
|
||||
@genController.post('/createTable', dependencies=[Depends(CheckRoleInterfaceAuth('admin'))])
|
||||
@Log(title='创建表', business_type=BusinessType.OTHER)
|
||||
async def create_table(
|
||||
request: Request,
|
||||
sql: str = Query(),
|
||||
query_db: AsyncSession = Depends(get_db),
|
||||
current_user: CurrentUserModel = Depends(LoginService.get_current_user),
|
||||
):
|
||||
create_table_result = await GenTableService.create_table_services(query_db, sql, current_user)
|
||||
logger.info(create_table_result.message)
|
||||
|
||||
return ResponseUtil.success(msg=create_table_result.message)
|
||||
|
||||
|
||||
@genController.get('/batchGenCode', dependencies=[Depends(CheckUserInterfaceAuth('tool:gen:code'))])
|
||||
@Log(title='代码生成', business_type=BusinessType.GENCODE)
|
||||
async def batch_gen_code(request: Request, tables: str = Query(), query_db: AsyncSession = Depends(get_db)):
|
||||
table_names = tables.split(',') if tables else []
|
||||
batch_gen_code_result = await GenTableService.batch_gen_code_services(query_db, table_names)
|
||||
logger.info('生成代码成功')
|
||||
|
||||
return ResponseUtil.streaming(data=bytes2file_response(batch_gen_code_result))
|
||||
|
||||
|
||||
@genController.get('/genCode/{table_name}', dependencies=[Depends(CheckUserInterfaceAuth('tool:gen:code'))])
|
||||
@Log(title='代码生成', business_type=BusinessType.GENCODE)
|
||||
async def gen_code_local(request: Request, table_name: str, query_db: AsyncSession = Depends(get_db)):
|
||||
if not GenConfig.allow_overwrite:
|
||||
logger.error('【系统预设】不允许生成文件覆盖到本地')
|
||||
return ResponseUtil.error('【系统预设】不允许生成文件覆盖到本地')
|
||||
gen_code_local_result = await GenTableService.generate_code_services(query_db, table_name)
|
||||
logger.info(gen_code_local_result.message)
|
||||
|
||||
return ResponseUtil.success(msg=gen_code_local_result.message)
|
||||
|
||||
|
||||
@genController.get('/{table_id}', dependencies=[Depends(CheckUserInterfaceAuth('tool:gen:query'))])
|
||||
async def query_detail_gen_table(request: Request, table_id: int, query_db: AsyncSession = Depends(get_db)):
|
||||
gen_table = await GenTableService.get_gen_table_by_id_services(query_db, table_id)
|
||||
gen_tables = await GenTableService.get_gen_table_all_services(query_db)
|
||||
gen_columns = await GenTableColumnService.get_gen_table_column_list_by_table_id_services(query_db, table_id)
|
||||
gen_table_detail_result = dict(info=gen_table, rows=gen_columns, tables=gen_tables)
|
||||
logger.info(f'获取table_id为{table_id}的信息成功')
|
||||
|
||||
return ResponseUtil.success(data=gen_table_detail_result)
|
||||
|
||||
|
||||
@genController.get('/preview/{table_id}', dependencies=[Depends(CheckUserInterfaceAuth('tool:gen:preview'))])
|
||||
async def preview_code(request: Request, table_id: int, query_db: AsyncSession = Depends(get_db)):
|
||||
preview_code_result = await GenTableService.preview_code_services(query_db, table_id)
|
||||
logger.info('获取预览代码成功')
|
||||
|
||||
return ResponseUtil.success(data=preview_code_result)
|
||||
|
||||
|
||||
@genController.get('/synchDb/{table_name}', dependencies=[Depends(CheckUserInterfaceAuth('tool:gen:edit'))])
|
||||
@Log(title='代码生成', business_type=BusinessType.UPDATE)
|
||||
async def sync_db(request: Request, table_name: str, query_db: AsyncSession = Depends(get_db)):
|
||||
sync_db_result = await GenTableService.sync_db_services(query_db, table_name)
|
||||
logger.info(sync_db_result.message)
|
||||
|
||||
return ResponseUtil.success(data=sync_db_result.message)
|
||||
@@ -0,0 +1,393 @@
|
||||
from datetime import datetime, time
|
||||
from sqlalchemy import delete, func, select, text, update
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy.orm import selectinload
|
||||
from sqlglot.expressions import Expression
|
||||
from typing import List
|
||||
from config.env import DataBaseConfig
|
||||
from module_generator.entity.do.gen_do import GenTable, GenTableColumn
|
||||
from module_generator.entity.vo.gen_vo import (
|
||||
GenTableBaseModel,
|
||||
GenTableColumnBaseModel,
|
||||
GenTableColumnModel,
|
||||
GenTableModel,
|
||||
GenTablePageQueryModel,
|
||||
)
|
||||
from utils.page_util import PageUtil
|
||||
|
||||
|
||||
class GenTableDao:
|
||||
"""
|
||||
代码生成业务表模块数据库操作层
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
async def get_gen_table_by_id(cls, db: AsyncSession, table_id: int):
|
||||
"""
|
||||
根据业务表id获取需要生成的业务表信息
|
||||
|
||||
:param db: orm对象
|
||||
:param table_id: 业务表id
|
||||
:return: 需要生成的业务表信息对象
|
||||
"""
|
||||
gen_table_info = (
|
||||
(
|
||||
await db.execute(
|
||||
select(GenTable).options(selectinload(GenTable.columns)).where(GenTable.table_id == table_id)
|
||||
)
|
||||
)
|
||||
.scalars()
|
||||
.first()
|
||||
)
|
||||
|
||||
return gen_table_info
|
||||
|
||||
@classmethod
|
||||
async def get_gen_table_by_name(cls, db: AsyncSession, table_name: str):
|
||||
"""
|
||||
根据业务表名称获取需要生成的业务表信息
|
||||
|
||||
:param db: orm对象
|
||||
:param table_name: 业务表名称
|
||||
:return: 需要生成的业务表信息对象
|
||||
"""
|
||||
gen_table_info = (
|
||||
(
|
||||
await db.execute(
|
||||
select(GenTable).options(selectinload(GenTable.columns)).where(GenTable.table_name == table_name)
|
||||
)
|
||||
)
|
||||
.scalars()
|
||||
.first()
|
||||
)
|
||||
|
||||
return gen_table_info
|
||||
|
||||
@classmethod
|
||||
async def get_gen_table_all(cls, db: AsyncSession):
|
||||
"""
|
||||
获取所有业务表信息
|
||||
|
||||
:param db: orm对象
|
||||
:return: 所有业务表信息
|
||||
"""
|
||||
gen_table_all = (await db.execute(select(GenTable).options(selectinload(GenTable.columns)))).scalars().all()
|
||||
|
||||
return gen_table_all
|
||||
|
||||
@classmethod
|
||||
async def create_table_by_sql_dao(cls, db: AsyncSession, sql_statements: List[Expression]):
|
||||
"""
|
||||
根据sql语句创建表结构
|
||||
|
||||
:param db: orm对象
|
||||
:param sql_statements: sql语句的ast列表
|
||||
:return:
|
||||
"""
|
||||
for sql_statement in sql_statements:
|
||||
sql = sql_statement.sql(dialect=DataBaseConfig.sqlglot_parse_dialect)
|
||||
await db.execute(text(sql))
|
||||
|
||||
@classmethod
|
||||
async def get_gen_table_list(cls, db: AsyncSession, query_object: GenTablePageQueryModel, is_page: bool = False):
|
||||
"""
|
||||
根据查询参数获取代码生成业务表列表信息
|
||||
|
||||
:param db: orm对象
|
||||
:param query_object: 查询参数对象
|
||||
:param is_page: 是否开启分页
|
||||
:return: 代码生成业务表列表信息对象
|
||||
"""
|
||||
query = (
|
||||
select(GenTable)
|
||||
.options(selectinload(GenTable.columns))
|
||||
.where(
|
||||
func.lower(GenTable.table_name).like(f'%{query_object.table_name.lower()}%')
|
||||
if query_object.table_name
|
||||
else True,
|
||||
func.lower(GenTable.table_comment).like(f'%{query_object.table_comment.lower()}%')
|
||||
if query_object.table_comment
|
||||
else True,
|
||||
GenTable.create_time.between(
|
||||
datetime.combine(datetime.strptime(query_object.begin_time, '%Y-%m-%d'), time(00, 00, 00)),
|
||||
datetime.combine(datetime.strptime(query_object.end_time, '%Y-%m-%d'), time(23, 59, 59)),
|
||||
)
|
||||
if query_object.begin_time and query_object.end_time
|
||||
else True,
|
||||
)
|
||||
.distinct()
|
||||
)
|
||||
gen_table_list = await PageUtil.paginate(db, query, query_object.page_num, query_object.page_size, is_page)
|
||||
|
||||
return gen_table_list
|
||||
|
||||
@classmethod
|
||||
async def get_gen_db_table_list(cls, db: AsyncSession, query_object: GenTablePageQueryModel, is_page: bool = False):
|
||||
"""
|
||||
根据查询参数获取数据库列表信息
|
||||
|
||||
:param db: orm对象
|
||||
:param query_object: 查询参数对象
|
||||
:param is_page: 是否开启分页
|
||||
:return: 数据库列表信息对象
|
||||
"""
|
||||
if DataBaseConfig.db_type == 'postgresql':
|
||||
query_sql = """
|
||||
table_name as table_name,
|
||||
table_comment as table_comment,
|
||||
create_time as create_time,
|
||||
update_time as update_time
|
||||
from
|
||||
list_table
|
||||
where
|
||||
table_name not like 'apscheduler_%'
|
||||
and table_name not like 'gen_%'
|
||||
and table_name not in (select table_name from gen_table)
|
||||
"""
|
||||
else:
|
||||
query_sql = """
|
||||
table_name as table_name,
|
||||
table_comment as table_comment,
|
||||
create_time as create_time,
|
||||
update_time as update_time
|
||||
from
|
||||
information_schema.tables
|
||||
where
|
||||
table_schema = (select database())
|
||||
and table_name not like 'apscheduler\_%'
|
||||
and table_name not like 'gen\_%'
|
||||
and table_name not in (select table_name from gen_table)
|
||||
"""
|
||||
if query_object.table_name:
|
||||
query_sql += """and lower(table_name) like lower(concat('%', :table_name, '%'))"""
|
||||
if query_object.table_comment:
|
||||
query_sql += """and lower(table_comment) like lower(concat('%', :table_comment, '%'))"""
|
||||
if query_object.begin_time:
|
||||
if DataBaseConfig.db_type == 'postgresql':
|
||||
query_sql += """and create_time::date >= to_date(:begin_time, 'yyyy-MM-dd')"""
|
||||
else:
|
||||
query_sql += """and date_format(create_time, '%Y%m%d') >= date_format(:begin_time, '%Y%m%d')"""
|
||||
if query_object.end_time:
|
||||
if DataBaseConfig.db_type == 'postgresql':
|
||||
query_sql += """and create_time::date <= to_date(:end_time, 'yyyy-MM-dd')"""
|
||||
else:
|
||||
query_sql += """and date_format(create_time, '%Y%m%d') >= date_format(:end_time, '%Y%m%d')"""
|
||||
query_sql += """order by create_time desc"""
|
||||
query = select(
|
||||
text(query_sql).bindparams(
|
||||
**{
|
||||
k: v
|
||||
for k, v in query_object.model_dump(exclude_none=True, exclude={'page_num', 'page_size'}).items()
|
||||
}
|
||||
)
|
||||
)
|
||||
gen_db_table_list = await PageUtil.paginate(db, query, query_object.page_num, query_object.page_size, is_page)
|
||||
|
||||
return gen_db_table_list
|
||||
|
||||
@classmethod
|
||||
async def get_gen_db_table_list_by_names(cls, db: AsyncSession, table_names: List[str]):
|
||||
"""
|
||||
根据业务表名称组获取数据库列表信息
|
||||
|
||||
:param db: orm对象
|
||||
:param table_names: 业务表名称组
|
||||
:return: 数据库列表信息对象
|
||||
"""
|
||||
if DataBaseConfig.db_type == 'postgresql':
|
||||
query_sql = """
|
||||
select
|
||||
table_name as table_name,
|
||||
table_comment as table_comment,
|
||||
create_time as create_time,
|
||||
update_time as update_time
|
||||
from
|
||||
list_table
|
||||
where
|
||||
table_name not like 'qrtz_%'
|
||||
and table_name not like 'gen_%'
|
||||
and table_name = any(:table_names)
|
||||
"""
|
||||
else:
|
||||
query_sql = """
|
||||
select
|
||||
table_name as table_name,
|
||||
table_comment as table_comment,
|
||||
create_time as create_time,
|
||||
update_time as update_time
|
||||
from
|
||||
information_schema.tables
|
||||
where
|
||||
table_name not like 'qrtz\_%'
|
||||
and table_name not like 'gen\_%'
|
||||
and table_schema = (select database())
|
||||
and table_name in :table_names
|
||||
"""
|
||||
query = text(query_sql).bindparams(table_names=tuple(table_names))
|
||||
gen_db_table_list = (await db.execute(query)).fetchall()
|
||||
|
||||
return gen_db_table_list
|
||||
|
||||
@classmethod
|
||||
async def add_gen_table_dao(cls, db: AsyncSession, gen_table: GenTableModel):
|
||||
"""
|
||||
新增业务表数据库操作
|
||||
|
||||
:param db: orm对象
|
||||
:param gen_table: 业务表对象
|
||||
:return:
|
||||
"""
|
||||
db_gen_table = GenTable(**GenTableBaseModel(**gen_table.model_dump(by_alias=True)).model_dump())
|
||||
db.add(db_gen_table)
|
||||
await db.flush()
|
||||
|
||||
return db_gen_table
|
||||
|
||||
@classmethod
|
||||
async def edit_gen_table_dao(cls, db: AsyncSession, gen_table: dict):
|
||||
"""
|
||||
编辑业务表数据库操作
|
||||
|
||||
:param db: orm对象
|
||||
:param gen_table: 需要更新的业务表字典
|
||||
:return:
|
||||
"""
|
||||
await db.execute(update(GenTable), [GenTableBaseModel(**gen_table).model_dump()])
|
||||
|
||||
@classmethod
|
||||
async def delete_gen_table_dao(cls, db: AsyncSession, gen_table: GenTableModel):
|
||||
"""
|
||||
删除业务表数据库操作
|
||||
|
||||
:param db: orm对象
|
||||
:param gen_table: 业务表对象
|
||||
:return:
|
||||
"""
|
||||
await db.execute(delete(GenTable).where(GenTable.table_id.in_([gen_table.table_id])))
|
||||
|
||||
|
||||
class GenTableColumnDao:
|
||||
"""
|
||||
代码生成业务表字段模块数据库操作层
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
async def get_gen_table_column_list_by_table_id(cls, db: AsyncSession, table_id: int):
|
||||
"""
|
||||
根据业务表id获取需要生成的业务表字段列表信息
|
||||
|
||||
:param db: orm对象
|
||||
:param table_id: 业务表id
|
||||
:return: 需要生成的业务表字段列表信息对象
|
||||
"""
|
||||
gen_table_column_list = (
|
||||
(
|
||||
await db.execute(
|
||||
select(GenTableColumn).where(GenTableColumn.table_id == table_id).order_by(GenTableColumn.sort)
|
||||
)
|
||||
)
|
||||
.scalars()
|
||||
.all()
|
||||
)
|
||||
|
||||
return gen_table_column_list
|
||||
|
||||
@classmethod
|
||||
async def get_gen_db_table_columns_by_name(cls, db: AsyncSession, table_name: str):
|
||||
"""
|
||||
根据业务表名称获取业务表字段列表信息
|
||||
|
||||
:param db: orm对象
|
||||
:param table_name: 业务表名称
|
||||
:return: 业务表字段列表信息对象
|
||||
"""
|
||||
if DataBaseConfig.db_type == 'postgresql':
|
||||
query_sql = """
|
||||
select
|
||||
column_name, is_required, is_pk, sort, column_comment, is_increment, column_type
|
||||
from
|
||||
list_column
|
||||
where
|
||||
table_name = :table_name
|
||||
"""
|
||||
else:
|
||||
query_sql = """
|
||||
select
|
||||
column_name as column_name,
|
||||
case
|
||||
when is_nullable = 'no' and column_key != 'PRI' then '1'
|
||||
else '0'
|
||||
end as is_required,
|
||||
case
|
||||
when column_key = 'PRI' then '1'
|
||||
else '0'
|
||||
end as is_pk,
|
||||
ordinal_position as sort,
|
||||
column_comment as column_comment,
|
||||
case
|
||||
when extra = 'auto_increment' then '1'
|
||||
else '0'
|
||||
end as is_increment,
|
||||
column_type as column_type
|
||||
from
|
||||
information_schema.columns
|
||||
where
|
||||
table_schema = (select database())
|
||||
and table_name = :table_name
|
||||
order by
|
||||
ordinal_position
|
||||
"""
|
||||
query = text(query_sql).bindparams(table_name=table_name)
|
||||
gen_db_table_columns = (await db.execute(query)).fetchall()
|
||||
|
||||
return gen_db_table_columns
|
||||
|
||||
@classmethod
|
||||
async def add_gen_table_column_dao(cls, db: AsyncSession, gen_table_column: GenTableColumnModel):
|
||||
"""
|
||||
新增业务表字段数据库操作
|
||||
|
||||
:param db: orm对象
|
||||
:param gen_table_column: 岗位对象
|
||||
:return:
|
||||
"""
|
||||
db_gen_table_column = GenTableColumn(
|
||||
**GenTableColumnBaseModel(**gen_table_column.model_dump(by_alias=True)).model_dump()
|
||||
)
|
||||
db.add(db_gen_table_column)
|
||||
await db.flush()
|
||||
|
||||
return db_gen_table_column
|
||||
|
||||
@classmethod
|
||||
async def edit_gen_table_column_dao(cls, db: AsyncSession, gen_table_column: dict):
|
||||
"""
|
||||
编辑业务表字段数据库操作
|
||||
|
||||
:param db: orm对象
|
||||
:param gen_table_column: 需要更新的业务表字段字典
|
||||
:return:
|
||||
"""
|
||||
await db.execute(update(GenTableColumn), [GenTableColumnBaseModel(**gen_table_column).model_dump()])
|
||||
|
||||
@classmethod
|
||||
async def delete_gen_table_column_by_table_id_dao(cls, db: AsyncSession, gen_table_column: GenTableColumnModel):
|
||||
"""
|
||||
通过业务表id删除业务表字段数据库操作
|
||||
|
||||
:param db: orm对象
|
||||
:param gen_table_column: 业务表字段对象
|
||||
:return:
|
||||
"""
|
||||
await db.execute(delete(GenTableColumn).where(GenTableColumn.table_id.in_([gen_table_column.table_id])))
|
||||
|
||||
@classmethod
|
||||
async def delete_gen_table_column_by_column_id_dao(cls, db: AsyncSession, gen_table_column: GenTableColumnModel):
|
||||
"""
|
||||
通过业务字段id删除业务表字段数据库操作
|
||||
|
||||
:param db: orm对象
|
||||
:param post: 业务表字段对象
|
||||
:return:
|
||||
"""
|
||||
await db.execute(delete(GenTableColumn).where(GenTableColumn.column_id.in_([gen_table_column.column_id])))
|
||||
@@ -0,0 +1,95 @@
|
||||
from datetime import datetime
|
||||
from sqlalchemy import Column, DateTime, ForeignKey, Integer, String
|
||||
from sqlalchemy.orm import relationship
|
||||
from config.database import Base
|
||||
from sqlalchemy import Boolean, Column, ForeignKey, String, Integer, Text, DateTime
|
||||
|
||||
from app.core.base_model import BaseMixin
|
||||
from sqlalchemy import Boolean, Column, ForeignKey, String, Integer, Text, DateTime
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
from app.core.base_model import BaseMixin
|
||||
|
||||
from sqlalchemy import Boolean, Column, ForeignKey, String, Integer, Text, DateTime
|
||||
|
||||
from app.core.base_model import BaseMixin
|
||||
|
||||
|
||||
class PageModel(BaseMixin):
|
||||
__tablename__ = "gen_page"
|
||||
|
||||
page_name = Column(String(length=255), comment='页面名称')
|
||||
|
||||
keywords = Column(String(length=500), comment='页面关键词')
|
||||
|
||||
title = Column(String(length=500), comment='页面title标题')
|
||||
|
||||
|
||||
class GenTable(Base):
|
||||
"""
|
||||
代码生成业务表
|
||||
"""
|
||||
|
||||
__tablename__ = 'gen_table'
|
||||
|
||||
table_id = Column(Integer, primary_key=True, autoincrement=True, comment='编号')
|
||||
table_name = Column(String(200), nullable=True, default='', comment='表名称')
|
||||
table_comment = Column(String(500), nullable=True, default='', comment='表描述')
|
||||
sub_table_name = Column(String(64), nullable=True, comment='关联子表的表名')
|
||||
sub_table_fk_name = Column(String(64), nullable=True, comment='子表关联的外键名')
|
||||
class_name = Column(String(100), nullable=True, default='', comment='实体类名称')
|
||||
tpl_category = Column(String(200), nullable=True, default='crud', comment='使用的模板(crud单表操作 tree树表操作)')
|
||||
tpl_web_type = Column(
|
||||
String(30), nullable=True, default='', comment='前端模板类型(element-ui模版 element-plus模版)'
|
||||
)
|
||||
package_name = Column(String(100), nullable=True, comment='生成包路径')
|
||||
module_name = Column(String(30), nullable=True, comment='生成模块名')
|
||||
business_name = Column(String(30), nullable=True, comment='生成业务名')
|
||||
function_name = Column(String(100), nullable=True, comment='生成功能名')
|
||||
function_author = Column(String(100), nullable=True, comment='生成功能作者')
|
||||
gen_type = Column(String(1), nullable=True, default='0', comment='生成代码方式(0zip压缩包 1自定义路径)')
|
||||
gen_path = Column(String(200), nullable=True, default='/', comment='生成路径(不填默认项目路径)')
|
||||
options = Column(String(1000), nullable=True, comment='其它生成选项')
|
||||
create_by = Column(String(64), default='', comment='创建者')
|
||||
create_time = Column(DateTime, nullable=True, default=datetime.now(), comment='创建时间')
|
||||
update_by = Column(String(64), default='', comment='更新者')
|
||||
update_time = Column(DateTime, nullable=True, default=datetime.now(), comment='更新时间')
|
||||
remark = Column(String(500), nullable=True, default=None, comment='备注')
|
||||
|
||||
columns = relationship('GenTableColumn', order_by='GenTableColumn.sort', back_populates='tables')
|
||||
|
||||
|
||||
class GenTableColumn(Base):
|
||||
"""
|
||||
代码生成业务表字段
|
||||
"""
|
||||
|
||||
__tablename__ = 'gen_table_column'
|
||||
|
||||
column_id = Column(Integer, primary_key=True, autoincrement=True, comment='编号')
|
||||
table_id = Column(Integer, ForeignKey('gen_table.table_id'), nullable=True, comment='归属表编号')
|
||||
column_name = Column(String(200), nullable=True, comment='列名称')
|
||||
column_comment = Column(String(500), nullable=True, comment='列描述')
|
||||
column_type = Column(String(100), nullable=True, comment='列类型')
|
||||
python_type = Column(String(500), nullable=True, comment='PYTHON类型')
|
||||
python_field = Column(String(200), nullable=True, comment='PYTHON字段名')
|
||||
is_pk = Column(String(1), nullable=True, comment='是否主键(1是)')
|
||||
is_increment = Column(String(1), nullable=True, comment='是否自增(1是)')
|
||||
is_required = Column(String(1), nullable=True, comment='是否必填(1是)')
|
||||
is_unique = Column(String(1), nullable=True, comment='是否唯一(1是)')
|
||||
is_insert = Column(String(1), nullable=True, comment='是否为插入字段(1是)')
|
||||
is_edit = Column(String(1), nullable=True, comment='是否编辑字段(1是)')
|
||||
is_list = Column(String(1), nullable=True, comment='是否列表字段(1是)')
|
||||
is_query = Column(String(1), nullable=True, comment='是否查询字段(1是)')
|
||||
query_type = Column(String(200), nullable=True, default='EQ', comment='查询方式(等于、不等于、大于、小于、范围)')
|
||||
html_type = Column(
|
||||
String(200), nullable=True, comment='显示类型(文本框、文本域、下拉框、复选框、单选框、日期控件)'
|
||||
)
|
||||
dict_type = Column(String(200), nullable=True, default='', comment='字典类型')
|
||||
sort = Column(Integer, nullable=True, comment='排序')
|
||||
create_by = Column(String(64), default='', comment='创建者')
|
||||
create_time = Column(DateTime, nullable=True, default=datetime.now(), comment='创建时间')
|
||||
update_by = Column(String(64), default='', comment='更新者')
|
||||
update_time = Column(DateTime, nullable=True, default=datetime.now(), comment='更新时间')
|
||||
|
||||
tables = relationship('GenTable', back_populates='columns')
|
||||
@@ -0,0 +1,274 @@
|
||||
from datetime import datetime
|
||||
from pydantic import BaseModel, ConfigDict, Field, model_validator
|
||||
from pydantic.alias_generators import to_camel
|
||||
from pydantic_validation_decorator import NotBlank
|
||||
from typing import List, Literal, Optional
|
||||
from config.constant import GenConstant
|
||||
from module_admin.annotation.pydantic_annotation import as_query
|
||||
from utils.string_util import StringUtil
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
from pydantic.alias_generators import to_camel
|
||||
from typing import List, Literal, Optional, Union
|
||||
from module_admin.annotation.pydantic_annotation import as_query
|
||||
# -*- coding:utf-8 -*-
|
||||
from datetime import datetime
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
from pydantic.alias_generators import to_camel
|
||||
from typing import List, Literal, Optional, Union
|
||||
from module_admin.annotation.pydantic_annotation import as_query
|
||||
|
||||
|
||||
class GenTableBaseModel(BaseModel):
|
||||
"""
|
||||
代码生成业务表对应pydantic模型
|
||||
"""
|
||||
|
||||
model_config = ConfigDict(alias_generator=to_camel, from_attributes=True)
|
||||
|
||||
table_id: Optional[int] = Field(default=None, description='编号')
|
||||
table_name: Optional[str] = Field(default=None, description='表名称')
|
||||
table_comment: Optional[str] = Field(default=None, description='表描述')
|
||||
sub_table_name: Optional[str] = Field(default=None, description='关联子表的表名')
|
||||
sub_table_fk_name: Optional[str] = Field(default=None, description='子表关联的外键名')
|
||||
class_name: Optional[str] = Field(default=None, description='实体类名称')
|
||||
tpl_category: Optional[str] = Field(default=None, description='使用的模板(crud单表操作 tree树表操作)')
|
||||
tpl_web_type: Optional[str] = Field(default=None, description='前端模板类型(element-ui模版 element-plus模版)')
|
||||
package_name: Optional[str] = Field(default=None, description='生成包路径')
|
||||
module_name: Optional[str] = Field(default=None, description='生成模块名')
|
||||
business_name: Optional[str] = Field(default=None, description='生成业务名')
|
||||
function_name: Optional[str] = Field(default=None, description='生成功能名')
|
||||
function_author: Optional[str] = Field(default=None, description='生成功能作者')
|
||||
gen_type: Optional[Literal['0', '1']] = Field(default=None, description='生成代码方式(0zip压缩包 1自定义路径)')
|
||||
gen_path: Optional[str] = Field(default=None, description='生成路径(不填默认项目路径)')
|
||||
options: Optional[str] = Field(default=None, description='其它生成选项')
|
||||
create_by: Optional[str] = Field(default=None, description='创建者')
|
||||
create_time: Optional[datetime] = Field(default=None, description='创建时间')
|
||||
update_by: Optional[str] = Field(default=None, description='更新者')
|
||||
update_time: Optional[datetime] = Field(default=None, description='更新时间')
|
||||
remark: Optional[str] = Field(default=None, description='备注')
|
||||
|
||||
@NotBlank(field_name='table_name', message='表名称不能为空')
|
||||
def get_table_name(self):
|
||||
return self.table_name
|
||||
|
||||
@NotBlank(field_name='table_comment', message='表描述不能为空')
|
||||
def get_table_comment(self):
|
||||
return self.table_comment
|
||||
|
||||
@NotBlank(field_name='class_name', message='实体类名称不能为空')
|
||||
def get_class_name(self):
|
||||
return self.class_name
|
||||
|
||||
@NotBlank(field_name='package_name', message='生成包路径不能为空')
|
||||
def get_package_name(self):
|
||||
return self.package_name
|
||||
|
||||
@NotBlank(field_name='module_name', message='生成模块名不能为空')
|
||||
def get_module_name(self):
|
||||
return self.module_name
|
||||
|
||||
@NotBlank(field_name='business_name', message='生成业务名不能为空')
|
||||
def get_business_name(self):
|
||||
return self.business_name
|
||||
|
||||
@NotBlank(field_name='function_name', message='生成功能名不能为空')
|
||||
def get_function_name(self):
|
||||
return self.function_name
|
||||
|
||||
@NotBlank(field_name='function_author', message='生成功能作者不能为空')
|
||||
def get_function_author(self):
|
||||
return self.function_author
|
||||
|
||||
def validate_fields(self):
|
||||
self.get_table_name()
|
||||
self.get_table_comment()
|
||||
self.get_class_name()
|
||||
self.get_package_name()
|
||||
self.get_module_name()
|
||||
self.get_business_name()
|
||||
self.get_function_name()
|
||||
self.get_function_author()
|
||||
|
||||
|
||||
class GenTableModel(GenTableBaseModel):
|
||||
"""
|
||||
代码生成业务表模型
|
||||
"""
|
||||
|
||||
pk_column: Optional['GenTableColumnModel'] = Field(default=None, description='主键信息')
|
||||
sub_table: Optional['GenTableModel'] = Field(default=None, description='子表信息')
|
||||
columns: Optional[List['GenTableColumnModel']] = Field(default=None, description='表列信息')
|
||||
tree_code: Optional[str] = Field(default=None, description='树编码字段')
|
||||
tree_parent_code: Optional[str] = Field(default=None, description='树父编码字段')
|
||||
tree_name: Optional[str] = Field(default=None, description='树名称字段')
|
||||
parent_menu_id: Optional[int] = Field(default=None, description='上级菜单ID字段')
|
||||
parent_menu_name: Optional[str] = Field(default=None, description='上级菜单名称字段')
|
||||
sub: Optional[bool] = Field(default=None, description='是否为子表')
|
||||
tree: Optional[bool] = Field(default=None, description='是否为树表')
|
||||
crud: Optional[bool] = Field(default=None, description='是否为单表')
|
||||
|
||||
@model_validator(mode='after')
|
||||
def check_some_is(self) -> 'GenTableModel':
|
||||
self.sub = True if self.tpl_category and self.tpl_category == GenConstant.TPL_SUB else False
|
||||
self.tree = True if self.tpl_category and self.tpl_category == GenConstant.TPL_TREE else False
|
||||
self.crud = True if self.tpl_category and self.tpl_category == GenConstant.TPL_CRUD else False
|
||||
return self
|
||||
|
||||
|
||||
class EditGenTableModel(GenTableModel):
|
||||
"""
|
||||
修改代码生成业务表模型
|
||||
"""
|
||||
|
||||
params: Optional['GenTableParamsModel'] = Field(default=None, description='业务表参数')
|
||||
|
||||
|
||||
class GenTableParamsModel(BaseModel):
|
||||
"""
|
||||
代码生成业务表参数模型
|
||||
"""
|
||||
|
||||
model_config = ConfigDict(alias_generator=to_camel)
|
||||
|
||||
tree_code: Optional[str] = Field(default=None, description='树编码字段')
|
||||
tree_parent_code: Optional[str] = Field(default=None, description='树父编码字段')
|
||||
tree_name: Optional[str] = Field(default=None, description='树名称字段')
|
||||
parent_menu_id: Optional[int] = Field(default=None, description='上级菜单ID字段')
|
||||
|
||||
|
||||
class GenTableQueryModel(GenTableBaseModel):
|
||||
"""
|
||||
代码生成业务表不分页查询模型
|
||||
"""
|
||||
|
||||
begin_time: Optional[str] = Field(default=None, description='开始时间')
|
||||
end_time: Optional[str] = Field(default=None, description='结束时间')
|
||||
|
||||
|
||||
@as_query
|
||||
class GenTablePageQueryModel(GenTableQueryModel):
|
||||
"""
|
||||
代码生成业务表分页查询模型
|
||||
"""
|
||||
|
||||
page_num: int = Field(default=1, description='当前页码')
|
||||
page_size: int = Field(default=10, description='每页记录数')
|
||||
|
||||
|
||||
class DeleteGenTableModel(BaseModel):
|
||||
"""
|
||||
删除代码生成业务表模型
|
||||
"""
|
||||
|
||||
model_config = ConfigDict(alias_generator=to_camel)
|
||||
|
||||
table_ids: str = Field(description='需要删除的代码生成业务表ID')
|
||||
|
||||
|
||||
class GenTableColumnBaseModel(BaseModel):
|
||||
"""
|
||||
代码生成业务表字段对应pydantic模型
|
||||
"""
|
||||
|
||||
model_config = ConfigDict(alias_generator=to_camel, from_attributes=True)
|
||||
|
||||
column_id: Optional[int] = Field(default=None, description='编号')
|
||||
table_id: Optional[int] = Field(default=None, description='归属表编号')
|
||||
column_name: Optional[str] = Field(default=None, description='列名称')
|
||||
column_comment: Optional[str] = Field(default=None, description='列描述')
|
||||
column_type: Optional[str] = Field(default=None, description='列类型')
|
||||
python_type: Optional[str] = Field(default=None, description='PYTHON类型')
|
||||
python_field: Optional[str] = Field(default=None, description='PYTHON字段名')
|
||||
is_pk: Optional[str] = Field(default=None, description='是否主键(1是)')
|
||||
is_increment: Optional[str] = Field(default=None, description='是否自增(1是)')
|
||||
is_required: Optional[str] = Field(default=None, description='是否必填(1是)')
|
||||
is_unique: Optional[str] = Field(default=None, description='是否唯一(1是)')
|
||||
is_insert: Optional[str] = Field(default=None, description='是否为插入字段(1是)')
|
||||
is_edit: Optional[str] = Field(default=None, description='是否编辑字段(1是)')
|
||||
is_list: Optional[str] = Field(default=None, description='是否列表字段(1是)')
|
||||
is_query: Optional[str] = Field(default=None, description='是否查询字段(1是)')
|
||||
query_type: Optional[str] = Field(default=None, description='查询方式(等于、不等于、大于、小于、范围)')
|
||||
html_type: Optional[str] = Field(
|
||||
default=None, description='显示类型(文本框、文本域、下拉框、复选框、单选框、日期控件)'
|
||||
)
|
||||
dict_type: Optional[str] = Field(default=None, description='字典类型')
|
||||
sort: Optional[int] = Field(default=None, description='排序')
|
||||
create_by: Optional[str] = Field(default=None, description='创建者')
|
||||
create_time: Optional[datetime] = Field(default=None, description='创建时间')
|
||||
update_by: Optional[str] = Field(default=None, description='更新者')
|
||||
update_time: Optional[datetime] = Field(default=None, description='更新时间')
|
||||
|
||||
@NotBlank(field_name='python_field', message='Python属性不能为空')
|
||||
def get_python_field(self):
|
||||
return self.python_field
|
||||
|
||||
def validate_fields(self):
|
||||
self.get_python_field()
|
||||
|
||||
|
||||
class GenTableColumnModel(GenTableColumnBaseModel):
|
||||
"""
|
||||
代码生成业务表字段模型
|
||||
"""
|
||||
|
||||
cap_python_field: Optional[str] = Field(default=None, description='字段大写形式')
|
||||
pk: Optional[bool] = Field(default=None, description='是否主键')
|
||||
increment: Optional[bool] = Field(default=None, description='是否自增')
|
||||
required: Optional[bool] = Field(default=None, description='是否必填')
|
||||
unique: Optional[bool] = Field(default=None, description='是否唯一')
|
||||
insert: Optional[bool] = Field(default=None, description='是否为插入字段')
|
||||
edit: Optional[bool] = Field(default=None, description='是否编辑字段')
|
||||
list: Optional[bool] = Field(default=None, description='是否列表字段')
|
||||
query: Optional[bool] = Field(default=None, description='是否查询字段')
|
||||
super_column: Optional[bool] = Field(default=None, description='是否为基类字段')
|
||||
usable_column: Optional[bool] = Field(default=None, description='是否为基类字段白名单')
|
||||
|
||||
@model_validator(mode='after')
|
||||
def check_some_is(self) -> 'GenTableModel':
|
||||
self.cap_python_field = self.python_field[0].upper() + self.python_field[1:] if self.python_field else None
|
||||
self.pk = True if self.is_pk and self.is_pk == '1' else False
|
||||
self.increment = True if self.is_increment and self.is_increment == '1' else False
|
||||
self.required = True if self.is_required and self.is_required == '1' else False
|
||||
self.unique = True if self.is_unique and self.is_unique == '1' else False
|
||||
self.insert = True if self.is_insert and self.is_insert == '1' else False
|
||||
self.edit = True if self.is_edit and self.is_edit == '1' else False
|
||||
self.list = True if self.is_list and self.is_list == '1' else False
|
||||
self.query = True if self.is_query and self.is_query == '1' else False
|
||||
self.super_column = (
|
||||
True
|
||||
if StringUtil.equals_any_ignore_case(self.python_field, GenConstant.TREE_ENTITY + GenConstant.BASE_ENTITY)
|
||||
else False
|
||||
)
|
||||
self.usable_column = (
|
||||
True if StringUtil.equals_any_ignore_case(self.python_field, ['parentId', 'orderNum', 'remark']) else False
|
||||
)
|
||||
return self
|
||||
|
||||
|
||||
class GenTableColumnQueryModel(GenTableColumnBaseModel):
|
||||
"""
|
||||
代码生成业务表字段不分页查询模型
|
||||
"""
|
||||
|
||||
begin_time: Optional[str] = Field(default=None, description='开始时间')
|
||||
end_time: Optional[str] = Field(default=None, description='结束时间')
|
||||
|
||||
|
||||
@as_query
|
||||
class GenTableColumnPageQueryModel(GenTableColumnQueryModel):
|
||||
"""
|
||||
代码生成业务表字段分页查询模型
|
||||
"""
|
||||
|
||||
page_num: int = Field(default=1, description='当前页码')
|
||||
page_size: int = Field(default=10, description='每页记录数')
|
||||
|
||||
|
||||
class DeleteGenTableColumnModel(BaseModel):
|
||||
"""
|
||||
删除代码生成业务表字段模型
|
||||
"""
|
||||
|
||||
model_config = ConfigDict(alias_generator=to_camel)
|
||||
|
||||
column_ids: str = Field(description='需要删除的代码生成业务表字段ID')
|
||||
@@ -0,0 +1,499 @@
|
||||
import io
|
||||
import json
|
||||
import os
|
||||
import zipfile
|
||||
from datetime import datetime
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlglot import parse as sqlglot_parse
|
||||
from sqlglot.expressions import Add, Alter, Create, Delete, Drop, Expression, Insert, Table, TruncateTable, Update
|
||||
from typing import List
|
||||
from config.constant import GenConstant
|
||||
from config.env import DataBaseConfig, GenConfig
|
||||
from exceptions.exception import ServiceException
|
||||
from module_admin.entity.vo.common_vo import CrudResponseModel
|
||||
from module_admin.entity.vo.user_vo import CurrentUserModel
|
||||
from module_generator.entity.vo.gen_vo import (
|
||||
DeleteGenTableModel,
|
||||
EditGenTableModel,
|
||||
GenTableColumnModel,
|
||||
GenTableModel,
|
||||
GenTablePageQueryModel,
|
||||
)
|
||||
from module_generator.dao.gen_dao import GenTableColumnDao, GenTableDao
|
||||
from utils.common_util import CamelCaseUtil
|
||||
from utils.gen_util import GenUtils
|
||||
from utils.template_util import TemplateInitializer, TemplateUtils
|
||||
|
||||
|
||||
class GenTableService:
|
||||
"""
|
||||
代码生成业务表服务层
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
async def get_gen_table_list_services(
|
||||
cls, query_db: AsyncSession, query_object: GenTablePageQueryModel, is_page: bool = False
|
||||
):
|
||||
"""
|
||||
获取代码生成业务表列表信息service
|
||||
|
||||
:param query_db: orm对象
|
||||
:param query_object: 查询参数对象
|
||||
:param is_page: 是否开启分页
|
||||
:return: 代码生成业务列表信息对象
|
||||
"""
|
||||
gen_table_list_result = await GenTableDao.get_gen_table_list(query_db, query_object, is_page)
|
||||
|
||||
return gen_table_list_result
|
||||
|
||||
@classmethod
|
||||
async def get_gen_db_table_list_services(
|
||||
cls, query_db: AsyncSession, query_object: GenTablePageQueryModel, is_page: bool = False
|
||||
):
|
||||
"""
|
||||
获取数据库列表信息service
|
||||
|
||||
:param query_db: orm对象
|
||||
:param query_object: 查询参数对象
|
||||
:param is_page: 是否开启分页
|
||||
:return: 数据库列表信息对象
|
||||
"""
|
||||
gen_db_table_list_result = await GenTableDao.get_gen_db_table_list(query_db, query_object, is_page)
|
||||
|
||||
return gen_db_table_list_result
|
||||
|
||||
@classmethod
|
||||
async def get_gen_db_table_list_by_name_services(cls, query_db: AsyncSession, table_names: List[str]):
|
||||
"""
|
||||
根据表名称组获取数据库列表信息service
|
||||
|
||||
:param query_db: orm对象
|
||||
:param table_names: 表名称组
|
||||
:return: 数据库列表信息对象
|
||||
"""
|
||||
gen_db_table_list_result = await GenTableDao.get_gen_db_table_list_by_names(query_db, table_names)
|
||||
|
||||
return [GenTableModel(**gen_table) for gen_table in CamelCaseUtil.transform_result(gen_db_table_list_result)]
|
||||
|
||||
@classmethod
|
||||
async def import_gen_table_services(
|
||||
cls, query_db: AsyncSession, gen_table_list: List[GenTableModel], current_user: CurrentUserModel
|
||||
):
|
||||
"""
|
||||
导入表结构service
|
||||
|
||||
:param query_db: orm对象
|
||||
:param gen_table_list: 导入表列表
|
||||
:param current_user: 当前用户信息对象
|
||||
:return: 导入结果
|
||||
"""
|
||||
try:
|
||||
for table in gen_table_list:
|
||||
table_name = table.table_name
|
||||
GenUtils.init_table(table, current_user.user.user_name)
|
||||
add_gen_table = await GenTableDao.add_gen_table_dao(query_db, table)
|
||||
if add_gen_table:
|
||||
table.table_id = add_gen_table.table_id
|
||||
gen_table_columns = await GenTableColumnDao.get_gen_db_table_columns_by_name(query_db, table_name)
|
||||
for column in [
|
||||
GenTableColumnModel(**gen_table_column)
|
||||
for gen_table_column in CamelCaseUtil.transform_result(gen_table_columns)
|
||||
]:
|
||||
GenUtils.init_column_field(column, table)
|
||||
await GenTableColumnDao.add_gen_table_column_dao(query_db, column)
|
||||
await query_db.commit()
|
||||
return CrudResponseModel(is_success=True, message='导入成功')
|
||||
except Exception as e:
|
||||
await query_db.rollback()
|
||||
raise ServiceException(message=f'导入失败, {str(e)}')
|
||||
|
||||
@classmethod
|
||||
async def edit_gen_table_services(cls, query_db: AsyncSession, page_object: EditGenTableModel):
|
||||
"""
|
||||
编辑业务表信息service
|
||||
|
||||
:param query_db: orm对象
|
||||
:param page_object: 编辑业务表对象
|
||||
:return: 编辑业务表校验结果
|
||||
"""
|
||||
edit_gen_table = page_object.model_dump(exclude_unset=True, by_alias=True)
|
||||
gen_table_info = await cls.get_gen_table_by_id_services(query_db, page_object.table_id)
|
||||
if gen_table_info.table_id:
|
||||
try:
|
||||
edit_gen_table['options'] = json.dumps(edit_gen_table.get('params'))
|
||||
await GenTableDao.edit_gen_table_dao(query_db, edit_gen_table)
|
||||
for gen_table_column in page_object.columns:
|
||||
gen_table_column.update_by = page_object.update_by
|
||||
gen_table_column.update_time = datetime.now()
|
||||
await GenTableColumnDao.edit_gen_table_column_dao(
|
||||
query_db, gen_table_column.model_dump(by_alias=True)
|
||||
)
|
||||
await query_db.commit()
|
||||
return CrudResponseModel(is_success=True, message='更新成功')
|
||||
except Exception as e:
|
||||
await query_db.rollback()
|
||||
raise e
|
||||
else:
|
||||
raise ServiceException(message='业务表不存在')
|
||||
|
||||
@classmethod
|
||||
async def delete_gen_table_services(cls, query_db: AsyncSession, page_object: DeleteGenTableModel):
|
||||
"""
|
||||
删除业务表信息service
|
||||
|
||||
:param query_db: orm对象
|
||||
:param page_object: 删除业务表对象
|
||||
:return: 删除业务表校验结果
|
||||
"""
|
||||
if page_object.table_ids:
|
||||
table_id_list = page_object.table_ids.split(',')
|
||||
try:
|
||||
for table_id in table_id_list:
|
||||
await GenTableDao.delete_gen_table_dao(query_db, GenTableModel(tableId=table_id))
|
||||
await GenTableColumnDao.delete_gen_table_column_by_table_id_dao(
|
||||
query_db, GenTableColumnModel(tableId=table_id)
|
||||
)
|
||||
await query_db.commit()
|
||||
return CrudResponseModel(is_success=True, message='删除成功')
|
||||
except Exception as e:
|
||||
await query_db.rollback()
|
||||
raise e
|
||||
else:
|
||||
raise ServiceException(message='传入业务表id为空')
|
||||
|
||||
@classmethod
|
||||
async def get_gen_table_by_id_services(cls, query_db: AsyncSession, table_id: int):
|
||||
"""
|
||||
获取需要生成的业务表详细信息service
|
||||
|
||||
:param query_db: orm对象
|
||||
:param table_id: 需要生成的业务表id
|
||||
:return: 需要生成的业务表id对应的信息
|
||||
"""
|
||||
gen_table = await GenTableDao.get_gen_table_by_id(query_db, table_id)
|
||||
result = await cls.set_table_from_options(GenTableModel(**CamelCaseUtil.transform_result(gen_table)))
|
||||
|
||||
return result
|
||||
|
||||
@classmethod
|
||||
async def get_gen_table_all_services(cls, query_db: AsyncSession):
|
||||
"""
|
||||
获取所有业务表信息service
|
||||
|
||||
:param query_db: orm对象
|
||||
:return: 所有业务表信息
|
||||
"""
|
||||
gen_table_all = await GenTableDao.get_gen_table_all(query_db)
|
||||
result = [GenTableModel(**gen_table) for gen_table in CamelCaseUtil.transform_result(gen_table_all)]
|
||||
|
||||
return result
|
||||
|
||||
@classmethod
|
||||
async def create_table_services(cls, query_db: AsyncSession, sql: str, current_user: CurrentUserModel):
|
||||
"""
|
||||
创建表结构service
|
||||
|
||||
:param query_db: orm对象
|
||||
:param sql: 建表语句
|
||||
:param current_user: 当前用户信息对象
|
||||
:return: 创建表结构结果
|
||||
"""
|
||||
sql_statements = sqlglot_parse(sql, dialect=DataBaseConfig.sqlglot_parse_dialect)
|
||||
if cls.__is_valid_create_table(sql_statements):
|
||||
try:
|
||||
table_names = cls.__get_table_names(sql_statements)
|
||||
await GenTableDao.create_table_by_sql_dao(query_db, sql_statements)
|
||||
gen_table_list = await cls.get_gen_db_table_list_by_name_services(query_db, table_names)
|
||||
await cls.import_gen_table_services(query_db, gen_table_list, current_user)
|
||||
|
||||
return CrudResponseModel(is_success=True, message='创建表结构成功')
|
||||
except Exception as e:
|
||||
raise ServiceException(message=f'创建表结构异常,详细错误信息:{str(e)}')
|
||||
else:
|
||||
raise ServiceException(message='建表语句不合法')
|
||||
|
||||
@classmethod
|
||||
def __is_valid_create_table(cls, sql_statements: List[Expression]):
|
||||
"""
|
||||
校验sql语句是否为合法的建表语句
|
||||
|
||||
:param sql_statements: sql语句的ast列表
|
||||
:return: 校验结果
|
||||
"""
|
||||
validate_create = [isinstance(sql_statement, Create) for sql_statement in sql_statements]
|
||||
validate_forbidden_keywords = [
|
||||
isinstance(
|
||||
sql_statement,
|
||||
(Add, Alter, Delete, Drop, Insert, TruncateTable, Update),
|
||||
)
|
||||
for sql_statement in sql_statements
|
||||
]
|
||||
if not any(validate_create) or any(validate_forbidden_keywords):
|
||||
return False
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def __get_table_names(cls, sql_statements: List[Expression]):
|
||||
"""
|
||||
获取sql语句中所有的建表表名
|
||||
|
||||
:param sql_statements: sql语句的ast列表
|
||||
:return: 建表表名列表
|
||||
"""
|
||||
table_names = []
|
||||
for sql_statement in sql_statements:
|
||||
if isinstance(sql_statement, Create):
|
||||
table_names.append(sql_statement.find(Table).name)
|
||||
return table_names
|
||||
|
||||
@classmethod
|
||||
async def preview_code_services(cls, query_db: AsyncSession, table_id: int):
|
||||
"""
|
||||
预览代码service
|
||||
|
||||
:param query_db: orm对象
|
||||
:param table_id: 业务表id
|
||||
:return: 预览数据列表
|
||||
"""
|
||||
gen_table = GenTableModel(
|
||||
**CamelCaseUtil.transform_result(await GenTableDao.get_gen_table_by_id(query_db, table_id))
|
||||
)
|
||||
await cls.set_sub_table(query_db, gen_table)
|
||||
await cls.set_pk_column(gen_table)
|
||||
env = TemplateInitializer.init_jinja2()
|
||||
context = TemplateUtils.prepare_context(gen_table)
|
||||
template_list = TemplateUtils.get_template_list(gen_table.tpl_category, gen_table.tpl_web_type)
|
||||
preview_code_result = {}
|
||||
for template in template_list:
|
||||
render_content = env.get_template(template).render(**context)
|
||||
preview_code_result[template] = render_content
|
||||
return preview_code_result
|
||||
|
||||
@classmethod
|
||||
async def generate_code_services(cls, query_db: AsyncSession, table_name: str):
|
||||
"""
|
||||
生成代码至指定路径service
|
||||
|
||||
:param query_db: orm对象
|
||||
:param table_name: 业务表名称
|
||||
:return: 生成代码结果
|
||||
"""
|
||||
env = TemplateInitializer.init_jinja2()
|
||||
render_info = await cls.__get_gen_render_info(query_db, table_name)
|
||||
for template in render_info[0]:
|
||||
try:
|
||||
render_content = env.get_template(template).render(**render_info[2])
|
||||
gen_path = cls.__get_gen_path(render_info[3], template)
|
||||
os.makedirs(os.path.dirname(gen_path), exist_ok=True)
|
||||
with open(gen_path, 'w', encoding='utf-8') as f:
|
||||
f.write(render_content)
|
||||
except Exception as e:
|
||||
raise ServiceException(
|
||||
message=f'渲染模板失败,表名:{render_info[3].table_name},详细错误信息:{str(e)}'
|
||||
)
|
||||
|
||||
return CrudResponseModel(is_success=True, message='生成代码成功')
|
||||
|
||||
@classmethod
|
||||
async def batch_gen_code_services(cls, query_db: AsyncSession, table_names: List[str]):
|
||||
"""
|
||||
批量生成代码service
|
||||
|
||||
:param query_db: orm对象
|
||||
:param table_names: 业务表名称组
|
||||
:return: 下载代码结果
|
||||
"""
|
||||
zip_buffer = io.BytesIO()
|
||||
with zipfile.ZipFile(zip_buffer, 'w', zipfile.ZIP_DEFLATED) as zip_file:
|
||||
for table_name in table_names:
|
||||
env = TemplateInitializer.init_jinja2()
|
||||
render_info = await cls.__get_gen_render_info(query_db, table_name)
|
||||
for template_file, output_file in zip(render_info[0], render_info[1]):
|
||||
render_content = env.get_template(template_file).render(**render_info[2])
|
||||
zip_file.writestr(output_file, render_content)
|
||||
|
||||
zip_data = zip_buffer.getvalue()
|
||||
zip_buffer.close()
|
||||
return zip_data
|
||||
|
||||
@classmethod
|
||||
async def __get_gen_render_info(cls, query_db: AsyncSession, table_name: str):
|
||||
"""
|
||||
获取生成代码渲染模板相关信息
|
||||
|
||||
:param query_db: orm对象
|
||||
:param table_name: 业务表名称
|
||||
:return: 生成代码渲染模板相关信息
|
||||
"""
|
||||
gen_table = GenTableModel(
|
||||
**CamelCaseUtil.transform_result(await GenTableDao.get_gen_table_by_name(query_db, table_name))
|
||||
)
|
||||
await cls.set_sub_table(query_db, gen_table)
|
||||
await cls.set_pk_column(gen_table)
|
||||
context = TemplateUtils.prepare_context(gen_table)
|
||||
template_list = TemplateUtils.get_template_list(gen_table.tpl_category, gen_table.tpl_web_type)
|
||||
output_files = [TemplateUtils.get_file_name(template, gen_table) for template in template_list]
|
||||
|
||||
return [template_list, output_files, context, gen_table]
|
||||
|
||||
@classmethod
|
||||
def __get_gen_path(cls, gen_table: GenTableModel, template: str):
|
||||
"""
|
||||
根据GenTableModel对象和模板名称生成路径
|
||||
|
||||
:param gen_table: GenTableModel对象
|
||||
:param template: 模板名称
|
||||
:return: 生成的路径
|
||||
"""
|
||||
gen_path = gen_table.gen_path
|
||||
if gen_path == '/':
|
||||
return os.path.join(os.getcwd(), GenConfig.GEN_PATH, TemplateUtils.get_file_name(template, gen_table))
|
||||
else:
|
||||
return os.path.join(gen_path, TemplateUtils.get_file_name(template, gen_table))
|
||||
|
||||
@classmethod
|
||||
async def sync_db_services(cls, query_db: AsyncSession, table_name: str):
|
||||
"""
|
||||
同步数据库service
|
||||
|
||||
:param query_db: orm对象
|
||||
:param table_name: 业务表名称
|
||||
:return: 同步数据库结果
|
||||
"""
|
||||
gen_table = await GenTableDao.get_gen_table_by_name(query_db, table_name)
|
||||
table = GenTableModel(**CamelCaseUtil.transform_result(gen_table))
|
||||
table_columns = table.columns
|
||||
table_column_map = {column.column_name: column for column in table_columns}
|
||||
query_db_table_columns = await GenTableColumnDao.get_gen_db_table_columns_by_name(query_db, table_name)
|
||||
db_table_columns = [
|
||||
GenTableColumnModel(**column) for column in CamelCaseUtil.transform_result(query_db_table_columns)
|
||||
]
|
||||
if not db_table_columns:
|
||||
raise ServiceException('同步数据失败,原表结构不存在')
|
||||
db_table_column_names = [column.column_name for column in db_table_columns]
|
||||
try:
|
||||
for column in db_table_columns:
|
||||
GenUtils.init_column_field(column, table)
|
||||
if column.column_name in table_column_map:
|
||||
prev_column = table_column_map[column.column_name]
|
||||
column.column_id = prev_column.column_id
|
||||
if column.list:
|
||||
column.dict_type = prev_column.dict_type
|
||||
column.query_type = prev_column.query_type
|
||||
if (
|
||||
prev_column.is_required != ''
|
||||
and not column.pk
|
||||
and (column.insert or column.edit)
|
||||
and (column.usable_column or column.super_column)
|
||||
):
|
||||
column.is_required = prev_column.is_required
|
||||
column.html_type = prev_column.html_type
|
||||
await GenTableColumnDao.edit_gen_table_column_dao(query_db, column.model_dump(by_alias=True))
|
||||
else:
|
||||
await GenTableColumnDao.add_gen_table_column_dao(query_db, column)
|
||||
del_columns = [column for column in table_columns if column.column_name not in db_table_column_names]
|
||||
if del_columns:
|
||||
for column in del_columns:
|
||||
await GenTableColumnDao.delete_gen_table_column_by_column_id_dao(query_db, column)
|
||||
await query_db.commit()
|
||||
return CrudResponseModel(is_success=True, message='同步成功')
|
||||
except Exception as e:
|
||||
await query_db.rollback()
|
||||
raise e
|
||||
|
||||
@classmethod
|
||||
async def set_sub_table(cls, query_db: AsyncSession, gen_table: GenTableModel):
|
||||
"""
|
||||
设置主子表信息
|
||||
|
||||
:param query_db: orm对象
|
||||
:param gen_table: 业务表信息
|
||||
:return:
|
||||
"""
|
||||
if gen_table.sub_table_name:
|
||||
sub_table = await GenTableDao.get_gen_table_by_name(query_db, gen_table.sub_table_name)
|
||||
gen_table.sub_table = GenTableModel(**CamelCaseUtil.transform_result(sub_table))
|
||||
|
||||
@classmethod
|
||||
async def set_pk_column(cls, gen_table: GenTableModel):
|
||||
"""
|
||||
设置主键列信息
|
||||
|
||||
:param gen_table: 业务表信息
|
||||
:return:
|
||||
"""
|
||||
for column in gen_table.columns:
|
||||
if column.pk:
|
||||
gen_table.pk_column = column
|
||||
break
|
||||
if gen_table.pk_column is None:
|
||||
gen_table.pk_column = gen_table.columns[0]
|
||||
if gen_table.tpl_category == GenConstant.TPL_SUB:
|
||||
for column in gen_table.sub_table.columns:
|
||||
if column.pk:
|
||||
gen_table.sub_table.pk_column = column
|
||||
break
|
||||
if gen_table.sub_table.columns is None:
|
||||
gen_table.sub_table.pk_column = gen_table.sub_table.columns[0]
|
||||
|
||||
@classmethod
|
||||
async def set_table_from_options(cls, gen_table: GenTableModel):
|
||||
"""
|
||||
设置代码生成其他选项值
|
||||
|
||||
:param gen_table: 生成对象
|
||||
:return: 设置后的生成对象
|
||||
"""
|
||||
params_obj = json.loads(gen_table.options) if gen_table.options else None
|
||||
if params_obj:
|
||||
gen_table.tree_code = params_obj.get(GenConstant.TREE_CODE)
|
||||
gen_table.tree_parent_code = params_obj.get(GenConstant.TREE_PARENT_CODE)
|
||||
gen_table.tree_name = params_obj.get(GenConstant.TREE_NAME)
|
||||
gen_table.parent_menu_id = params_obj.get(GenConstant.PARENT_MENU_ID)
|
||||
gen_table.parent_menu_name = params_obj.get(GenConstant.PARENT_MENU_NAME)
|
||||
|
||||
return gen_table
|
||||
|
||||
@classmethod
|
||||
async def validate_edit(cls, edit_gen_table: EditGenTableModel):
|
||||
"""
|
||||
编辑保存参数校验
|
||||
|
||||
:param edit_gen_table: 编辑业务表对象
|
||||
"""
|
||||
if edit_gen_table.tpl_category == GenConstant.TPL_TREE:
|
||||
params_obj = edit_gen_table.params.model_dump(by_alias=True)
|
||||
|
||||
if GenConstant.TREE_CODE not in params_obj:
|
||||
raise ServiceException(message='树编码字段不能为空')
|
||||
elif GenConstant.TREE_PARENT_CODE not in params_obj:
|
||||
raise ServiceException(message='树父编码字段不能为空')
|
||||
elif GenConstant.TREE_NAME not in params_obj:
|
||||
raise ServiceException(message='树名称字段不能为空')
|
||||
elif edit_gen_table.tpl_category == GenConstant.TPL_SUB:
|
||||
if not edit_gen_table.sub_table_name:
|
||||
raise ServiceException(message='关联子表的表名不能为空')
|
||||
elif not edit_gen_table.sub_table_fk_name:
|
||||
raise ServiceException(message='子表关联的外键名不能为空')
|
||||
|
||||
|
||||
class GenTableColumnService:
|
||||
"""
|
||||
代码生成业务表字段服务层
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
async def get_gen_table_column_list_by_table_id_services(cls, query_db: AsyncSession, table_id: int):
|
||||
"""
|
||||
获取业务表字段列表信息service
|
||||
|
||||
:param query_db: orm对象
|
||||
:param table_id: 业务表格id
|
||||
:return: 业务表字段列表信息对象
|
||||
"""
|
||||
gen_table_column_list_result = await GenTableColumnDao.get_gen_table_column_list_by_table_id(query_db, table_id)
|
||||
|
||||
return [
|
||||
GenTableColumnModel(**gen_table_column)
|
||||
for gen_table_column in CamelCaseUtil.transform_result(gen_table_column_list_result)
|
||||
]
|
||||
@@ -1,2 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
|
||||
from sqlalchemy import Boolean, Column, ForeignKey, String, Integer, Text, DateTime
|
||||
|
||||
from app.core.base_model import BaseMixin
|
||||
|
||||
class PageModel(BaseMixin):
|
||||
__tablename__ = "gen_page"
|
||||
|
||||
page_name = Column(String(length=255), comment='页面名称')
|
||||
|
||||
keywords = Column(String(length=500), comment='页面关键词')
|
||||
|
||||
title = Column(String(length=500), comment='页面title标题')
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
|
||||
from sqlalchemy import Boolean, Column, ForeignKey, String, Integer, Text, DateTime
|
||||
|
||||
from app.core.base_model import BaseMixin
|
||||
|
||||
class PageModel(BaseMixin):
|
||||
__tablename__ = "gen_page"
|
||||
|
||||
page_name = Column(String(length=255), comment='页面名称')
|
||||
|
||||
keywords = Column(String(length=500), comment='页面关键词')
|
||||
|
||||
title = Column(String(length=500), comment='页面title标题')
|
||||
|
||||
@@ -1,192 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import io
|
||||
from typing import Any, List, Dict
|
||||
from fastapi import UploadFile
|
||||
import pandas as pd
|
||||
|
||||
from app.api.v1.schemas.system.auth_schema import AuthSchema
|
||||
from app.api.v1.schemas.demo.example_schema import ExampleCreateSchema, ExampleUpdateSchema, ExampleOutSchema
|
||||
from app.core.base_schema import BatchSetAvailable
|
||||
from app.api.v1.params.demo.example_param import ExampleQueryParams
|
||||
from app.api.v1.cruds.demo.example_crud import ExampleCRUD
|
||||
from app.core.exceptions import CustomException
|
||||
from app.utils.excel_util import ExcelUtil
|
||||
from app.core.logger import logger
|
||||
|
||||
|
||||
class ExampleService:
|
||||
"""
|
||||
示例管理模块服务层
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
async def get_example_detail_service(cls, auth: AuthSchema, id: int) -> Dict:
|
||||
"""详情"""
|
||||
obj = await ExampleCRUD(auth).get_by_id_crud(id=id)
|
||||
return ExampleOutSchema.model_validate(obj).model_dump()
|
||||
|
||||
@classmethod
|
||||
async def get_demo_list_service(cls, auth: AuthSchema, search: ExampleQueryParams = None, order_by: List[Dict[str, str]] = None) -> List[Dict]:
|
||||
"""列表查询"""
|
||||
if order_by:
|
||||
order_by = eval(order_by)
|
||||
obj_list = await ExampleCRUD(auth).get_list_crud(search=search.__dict__, order_by=order_by)
|
||||
return [ExampleOutSchema.model_validate(obj).model_dump() for obj in obj_list]
|
||||
|
||||
@classmethod
|
||||
async def create_example_service(cls, auth: AuthSchema, data: ExampleCreateSchema) -> Dict:
|
||||
"""创建"""
|
||||
obj = await ExampleCRUD(auth).get(name=data.name)
|
||||
if obj:
|
||||
raise CustomException(msg='创建失败,名称已存在')
|
||||
obj = await ExampleCRUD(auth).create_crud(data=data)
|
||||
return ExampleOutSchema.model_validate(obj).model_dump()
|
||||
|
||||
@classmethod
|
||||
async def update_example_service(cls, auth: AuthSchema, data: ExampleUpdateSchema) -> Dict:
|
||||
"""更新"""
|
||||
obj = await ExampleCRUD(auth).get_by_id_crud(id=data.id)
|
||||
if not obj:
|
||||
raise CustomException(msg='更新失败,该数据不存在')
|
||||
exist_obj = await ExampleCRUD(auth).get(name=data.name)
|
||||
if exist_obj and exist_obj.id != data.id:
|
||||
raise CustomException(msg='更新失败,名称重复')
|
||||
obj = await ExampleCRUD(auth).update_crud(id=data.id, data=data)
|
||||
return ExampleOutSchema.model_validate(obj).model_dump()
|
||||
|
||||
@classmethod
|
||||
async def delete_example_service(cls, auth: AuthSchema, ids: list[int]) -> None:
|
||||
"""删除"""
|
||||
if len(ids) < 1:
|
||||
raise CustomException(msg='删除失败,删除对象不能为空')
|
||||
for id in ids:
|
||||
obj = await ExampleCRUD(auth).get_by_id_crud(id=id)
|
||||
if not obj:
|
||||
raise CustomException(msg='删除失败,该数据不存在')
|
||||
await ExampleCRUD(auth).delete_crud(ids=ids)
|
||||
|
||||
@classmethod
|
||||
async def set_example_available_service(cls, auth: AuthSchema, data: BatchSetAvailable) -> None:
|
||||
"""批量设置状态"""
|
||||
await ExampleCRUD(auth).set_available_crud(ids=data.ids, status=data.status)
|
||||
|
||||
@classmethod
|
||||
async def batch_export_service(cls, obj_list: List[Dict[str, Any]]) -> bytes:
|
||||
"""批量导出"""
|
||||
mapping_dict = {
|
||||
'id': '编号',
|
||||
'name': '名称',
|
||||
'status': '状态',
|
||||
'description': '备注',
|
||||
'created_at': '创建时间',
|
||||
'updated_at': '更新时间',
|
||||
'creator': '创建者',
|
||||
}
|
||||
|
||||
# 复制数据并转换状态
|
||||
data = obj_list.copy()
|
||||
for item in data:
|
||||
# 处理状态
|
||||
item['status'] = '正常' if item.get('status') else '停用'
|
||||
# 处理公告类型
|
||||
item['creator'] = item.get('creator', {}).get('name', '未知') if isinstance(item.get('creator'), dict) else '未知'
|
||||
|
||||
return ExcelUtil.export_list2excel(list_data=obj_list, mapping_dict=mapping_dict)
|
||||
|
||||
@classmethod
|
||||
async def batch_import_service(cls, auth: AuthSchema, file: UploadFile, update_support: bool = False) -> str:
|
||||
"""批量导入"""
|
||||
|
||||
header_dict = {
|
||||
'名称': 'name',
|
||||
'状态': 'status',
|
||||
'描述': 'description'
|
||||
}
|
||||
|
||||
try:
|
||||
# 读取Excel文件
|
||||
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)
|
||||
|
||||
# 验证必填字段
|
||||
required_fields = ['name', 'status']
|
||||
for field in required_fields:
|
||||
if df[field].isnull().any():
|
||||
missing_rows = df[df[field].isnull()].index.tolist()
|
||||
raise CustomException(msg=f"{[k for k,v in header_dict.items() if v == field][0]}不能为空,第{[i+1 for i in missing_rows]}行")
|
||||
|
||||
error_msgs = []
|
||||
success_count = 0
|
||||
|
||||
# 处理每一行数据
|
||||
for index, row in df.iterrows():
|
||||
try:
|
||||
# 数据转换前的类型检查
|
||||
try:
|
||||
name = str(row['name'])
|
||||
except ValueError:
|
||||
error_msgs.append(f"第{index+1}行: 名称必须是字符串")
|
||||
continue
|
||||
try:
|
||||
status = True if row['status'] == '正常' else False
|
||||
except ValueError:
|
||||
error_msgs.append(f"第{index+1}行: 状态必须是'正常'或'停用'")
|
||||
continue
|
||||
|
||||
# 构建用户数据
|
||||
data = {
|
||||
"name": name,
|
||||
"status": status,
|
||||
"description": str(row['description']).strip() if not pd.isna(row['description']) else None,
|
||||
}
|
||||
|
||||
# 处理用户导入
|
||||
exists_user = await ExampleCRUD(auth).get(name=data["name"])
|
||||
if exists_user:
|
||||
if update_support:
|
||||
await ExampleCRUD(auth).update(id=exists_user.id, data=data)
|
||||
success_count += 1
|
||||
else:
|
||||
error_msgs.append(f"第{index+1}行: 用户 {data['username']} 已存在")
|
||||
else:
|
||||
await ExampleCRUD(auth).create(data=data)
|
||||
success_count += 1
|
||||
|
||||
except Exception as e:
|
||||
error_msgs.append(f"第{index+1}行: {str(e)}")
|
||||
continue
|
||||
|
||||
# 返回详细的导入结果
|
||||
result = f"成功导入 {success_count} 条数据"
|
||||
if error_msgs:
|
||||
result += "\n错误信息:\n" + "\n".join(error_msgs)
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"批量导入用户失败: {str(e)}")
|
||||
raise CustomException(msg=f"导入失败: {str(e)}")
|
||||
|
||||
@classmethod
|
||||
async def import_template_download_service(cls) -> bytes:
|
||||
"""下载导入模板"""
|
||||
header_list = ['名称', '状态', '描述']
|
||||
selector_header_list = ['状态']
|
||||
option_list = [{'状态': ['正常', '停用']}]
|
||||
return ExcelUtil.get_excel_template(
|
||||
header_list=header_list,
|
||||
selector_header_list=selector_header_list,
|
||||
option_list=option_list
|
||||
)
|
||||
@@ -1,27 +0,0 @@
|
||||
from typing import List
|
||||
|
||||
from module_gen.dao.gen_table_column_dao import GenTableColumnDao
|
||||
from module_gen.entity.do.gen_table_column_do import GenTableColumn
|
||||
from module_gen.entity.vo.gen_table_column_vo import GenTableColumnPageModel
|
||||
|
||||
|
||||
class GenTableColumnService:
|
||||
"""代码生成业务字段 服务层实现"""
|
||||
|
||||
# @classmethod
|
||||
# async def select_gen_table_column_by_table_id(cls, table_id: int, query_db) -> List[GenTableColumn]:
|
||||
# """查询业务字段列表"""
|
||||
# return await GenTableColumnDao.get_gen_table_column_list(query_db, GenTableColumnPageModel(tableId=table_id))
|
||||
#
|
||||
#
|
||||
# async def insert_gen_table_column(cls, gen_table_column: GenTableColumn) -> int:
|
||||
# """新增业务字段"""
|
||||
# return await GenTableColumnDao.insert_gen_table_column(gen_table_column)
|
||||
#
|
||||
# async def update_gen_table_column(cls, gen_table_column: GenTableColumn) -> int:
|
||||
# """修改业务字段"""
|
||||
# return await GenTableColumnDao.update_gen_table_column(gen_table_column)
|
||||
#
|
||||
# async def delete_gen_table_column_by_ids(cls, ids: List[int]) -> int:
|
||||
# """删除业务字段信息"""
|
||||
# return await GenTableColumnDao.delete_gen_table_column_by_ids(ids)
|
||||
@@ -1,210 +0,0 @@
|
||||
import io
|
||||
import json
|
||||
import zipfile
|
||||
from io import BytesIO
|
||||
from math import trunc
|
||||
from typing import List, Optional, Dict
|
||||
|
||||
from watchfiles import awatch
|
||||
|
||||
from module_gen.dao.gen_table_dao import GenTableDao
|
||||
from module_gen.dao.gen_table_column_dao import GenTableColumnDao
|
||||
from module_gen.entity.do.gen_table_column_do import GenTableColumn
|
||||
from module_gen.entity.do.gen_table_do import GenTable
|
||||
from module_gen.entity.vo.gen_table_options_vo import GenTableOptionModel
|
||||
from module_gen.entity.vo.gen_table_vo import GenTablePageModel, GenTableModel
|
||||
from module_gen.utils.gen_utils import GenUtils
|
||||
from module_gen.utils.velocity_utils import VelocityUtils
|
||||
from module_gen.entity.vo.gen_table_column_vo import GenTableColumnModel, GenTableColumnPageModel
|
||||
from utils.common_util import CamelCaseUtil, SnakeCaseUtil
|
||||
from utils.page_util import PageResponseModel
|
||||
|
||||
|
||||
class GenTableService:
|
||||
"""代码生成 服务层实现"""
|
||||
|
||||
@classmethod
|
||||
async def select_gen_table_list(cls, gen_table: GenTablePageModel, query_db, data_scope_sql) -> PageResponseModel:
|
||||
"""查询业务信息"""
|
||||
return await GenTableDao.get_gen_table_list(query_db, gen_table, data_scope_sql, is_page=True)
|
||||
|
||||
@classmethod
|
||||
async def select_all_gen_table_list(cls, query_db, data_scope_sql) -> PageResponseModel:
|
||||
"""查询业务信息"""
|
||||
return await GenTableDao.get_gen_table_list(query_db, GenTablePageModel(), data_scope_sql, is_page=False)
|
||||
|
||||
@classmethod
|
||||
async def select_gen_table_by_id(cls, table_id: int, query_db, data_scope_sql) -> Optional[GenTableModel]:
|
||||
"""查询业务信息"""
|
||||
gen_table = await GenTableDao.get_by_id(query_db, table_id)
|
||||
|
||||
columns = await GenTableColumnDao.get_gen_table_column_list(query_db,
|
||||
GenTableColumnPageModel(tableId=table_id),
|
||||
data_scope_sql)
|
||||
result = GenTableModel(**CamelCaseUtil.transform_result(gen_table))
|
||||
if result.options:
|
||||
table_options = GenTableOptionModel(**json.loads(result.options))
|
||||
result.parent_menu_id = table_options.parent_menu_id
|
||||
result.columns = columns
|
||||
return result
|
||||
|
||||
@classmethod
|
||||
async def select_gen_table_by_name(cls, table_name: str, query_db) -> Optional[GenTableModel]:
|
||||
"""查询表名称业务信息"""
|
||||
gen_table = await GenTableDao.get_by_table_name(query_db, table_name)
|
||||
columns = await GenTableColumnDao.get_gen_table_column_list(query_db,
|
||||
GenTableColumnPageModel(
|
||||
tableId=gen_table.table_id))
|
||||
result = GenTableModel(**CamelCaseUtil.transform_result(gen_table))
|
||||
if result.options:
|
||||
table_options = GenTableOptionModel(**json.loads(result.options))
|
||||
result.parent_menu_id = table_options.parent_menu_id
|
||||
result.columns = columns
|
||||
return result
|
||||
|
||||
@classmethod
|
||||
async def select_db_table_list(cls, gen_table: GenTablePageModel, query_db, data_scope_sql) -> PageResponseModel:
|
||||
"""查询数据库列表"""
|
||||
return await GenTableDao.select_db_table_list(query_db, gen_table, is_page=True)
|
||||
|
||||
@classmethod
|
||||
async def select_db_table_list_by_names(cls, table_names: List[str], query_db) -> List[GenTableModel]:
|
||||
"""查询数据库列表"""
|
||||
return await GenTableDao.select_db_table_list_by_names(query_db, table_names)
|
||||
|
||||
@classmethod
|
||||
async def import_gen_table(cls, table_list: List[str], query_db) -> None:
|
||||
"""导入表结构"""
|
||||
tables = await GenTableService.select_db_table_list_by_names(table_list, query_db)
|
||||
for table in tables:
|
||||
gen_table = GenTableModel()
|
||||
gen_table.table_name = table.table_name
|
||||
gen_table.table_comment = table.table_comment
|
||||
|
||||
# 查询表列信息
|
||||
columns = await GenTableColumnDao.select_db_table_columns_by_name(query_db, table.table_name)
|
||||
|
||||
GenUtils.init_table(gen_table, columns)
|
||||
# 添加表信息
|
||||
gen_table_result = await GenTableDao.add_gen_table(query_db, gen_table)
|
||||
# 添加列信息
|
||||
for i, column in enumerate(columns):
|
||||
column.table_id = gen_table_result.table_id
|
||||
await GenTableColumnDao.add_gen_table_column(query_db, column)
|
||||
await query_db.commit()
|
||||
|
||||
@classmethod
|
||||
async def validate_edit(cls, gen_table: GenTableModel) -> None:
|
||||
"""验证编辑"""
|
||||
if gen_table.tpl_category == "tree":
|
||||
if not all([gen_table.tree_code, gen_table.tree_parent_code, gen_table.tree_name]):
|
||||
raise ValueError("树表配置必须填写树编码字段、树父编码字段和树名称字段")
|
||||
|
||||
@classmethod
|
||||
async def update_gen_table(cls, query_db, gen_table: GenTableModel) -> None:
|
||||
"""业务信息"""
|
||||
columns_dicts = gen_table.columns
|
||||
# columns = [GenTableColumnModel(**columns_dict) for columns_dict in columns_dicts]
|
||||
|
||||
gen_table.options = json.dumps(gen_table.params)
|
||||
await GenTableDao.edit_gen_table(query_db, gen_table)
|
||||
if gen_table.columns:
|
||||
for column in gen_table.columns:
|
||||
column.table_id = gen_table.table_id
|
||||
await GenTableColumnDao.edit_gen_table_column(query_db, column, exclude_unset=False)
|
||||
|
||||
@classmethod
|
||||
async def delete_gen_table_by_ids(cls, query_db, ids: List[int]) -> None:
|
||||
"""删除业务对象"""
|
||||
await GenTableDao.del_gen_table_by_ids(query_db, ids, soft_del=False)
|
||||
await GenTableColumnDao.del_gen_table_column_by_table_ids(query_db, ids, soft_del=False)
|
||||
|
||||
# @classmethod
|
||||
# async def generate_code(cls, table_name: str, query_db) -> None:
|
||||
# """生成代码(自定义路径)"""
|
||||
# # 查询表信息
|
||||
# table = await GenTableService.select_gen_table_by_name(table_name, query_db)
|
||||
# # 生成代码
|
||||
# if table:
|
||||
# # 获取模板列表
|
||||
# templates = GenUtils.get_template_path(table.tpl_category)
|
||||
# context = await VelocityUtils.get_render_params(table, query_db)
|
||||
# # 生成代码
|
||||
# for template_name, template_path in templates.items():
|
||||
# # 渲染模板
|
||||
#
|
||||
#
|
||||
# # 获取生成路径
|
||||
# file_name = GenUtils.get_file_name(template_name, table)
|
||||
# if file_name:
|
||||
# try:
|
||||
# file_path = table.gen_path + "/" + file_name
|
||||
# # 写入文件
|
||||
# VelocityUtils.write_file(template_path, context, file_path)
|
||||
# except Exception as e:
|
||||
# raise RuntimeError(f"渲染模板失败,表名:{table.table_name}")
|
||||
|
||||
|
||||
@classmethod
|
||||
async def sync_db(cls, query_db, table_name: str, data_scope_sql) -> None:
|
||||
|
||||
table = await GenTableDao.get_by_table_name(query_db, table_name)
|
||||
table_columns_dicts = await GenTableColumnDao.get_gen_table_column_list(query_db,
|
||||
GenTableColumnPageModel(tableName=table_name),
|
||||
data_scope_sql)
|
||||
table_columns = [GenTableColumnModel(**tcd) for tcd in table_columns_dicts]
|
||||
db_table_columns = await GenTableColumnDao.select_db_table_columns_by_name(query_db, table_name)
|
||||
if not db_table_columns or len(db_table_columns) == 0:
|
||||
return None
|
||||
|
||||
for i, db_table_column in enumerate(db_table_columns):
|
||||
GenUtils.init_column_field(db_table_column, GenTableModel(**CamelCaseUtil.transform_result(table)))
|
||||
prev_column = next((table_column for table_column in table_columns if table_column.column_name == db_table_column.column_name), None)
|
||||
if prev_column:
|
||||
db_table_column.column_id = prev_column.column_id
|
||||
if db_table_column.is_list:
|
||||
db_table_column.dict_type = prev_column.dict_type
|
||||
db_table_column.query_type = prev_column.query_type
|
||||
db_table_column.is_required = prev_column.is_required
|
||||
db_table_column.html_type = prev_column.html_type
|
||||
await GenTableColumnDao.edit_gen_table_column(query_db, db_table_column, auto_commit=False, exclude_unset=True)
|
||||
else:
|
||||
await GenTableColumnDao.add_gen_table_column(query_db, db_table_column)
|
||||
|
||||
dbc_names = {dbc.column_name for dbc in db_table_columns}
|
||||
del_columns = [t_column for t_column in table_columns if t_column.column_name not in dbc_names]
|
||||
for i, del_column in enumerate(del_columns):
|
||||
await GenTableColumnDao.del_gen_table_column(query_db, del_column, auto_commit=False, soft_del=False)
|
||||
await query_db.commit()
|
||||
|
||||
|
||||
@classmethod
|
||||
async def preview_code(cls, query_db, table_id: int, data_scope_sql) -> (Dict[str, str], GenTableModel):
|
||||
"""预览模板代码"""
|
||||
table = await cls.select_gen_table_by_id(table_id, query_db, data_scope_sql)
|
||||
render_params = await VelocityUtils.get_render_params(table, query_db)
|
||||
templates = GenUtils.get_template_path(table.tpl_category)
|
||||
preview_result = {}
|
||||
for template_name, template_path in templates.items():
|
||||
template = VelocityUtils.get_template(template_path)
|
||||
content = template.render(**render_params)
|
||||
preview_result[template_name] = content
|
||||
return preview_result, table
|
||||
|
||||
@classmethod
|
||||
async def batch_generate_code(cls, query_db, data_scope_sql, table_id_array:List[str]) -> BytesIO:
|
||||
"""批量下载生成代码"""
|
||||
zip_buffer = io.BytesIO()
|
||||
with zipfile.ZipFile(zip_buffer, "w", zipfile.ZIP_DEFLATED) as zip_file:
|
||||
for i, table_id in enumerate(table_id_array):
|
||||
preview_result, table = await cls.preview_code(query_db, int(table_id), data_scope_sql)
|
||||
for filename, content in preview_result.items():
|
||||
target_file_name = GenUtils.get_file_name(filename, table)
|
||||
zip_file.writestr(target_file_name, content)
|
||||
zip_buffer.seek(0)
|
||||
return zip_buffer
|
||||
|
||||
@classmethod
|
||||
async def create_table(cls, query_db, sql) -> bool:
|
||||
"""数据库表创建"""
|
||||
return await GenTableDao.create_table(query_db, sql)
|
||||
@@ -1,2 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
import datetime
|
||||
|
||||
from sqlalchemy import Boolean, Column, ForeignKey, String, Integer, Text, DateTime
|
||||
|
||||
from app.core.base_model import BaseMixin
|
||||
|
||||
|
||||
class SysTable(BaseMixin):
|
||||
__tablename__ = "gen_table"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
create_time = Column(DateTime, nullable=False, default=datetime.datetime.now, comment='创建时间')
|
||||
update_time = Column(DateTime, nullable=False, default=datetime.datetime.now, onupdate=datetime.datetime.now, index=True, comment='更新时间')
|
||||
del_flag = Column(String(1), nullable=False, default='0', server_default=text("'0'"), comment='删除标志(0代表存在 2代表删除)')
|
||||
|
||||
align = Column(String(255), nullable=False, default='left', comment='对其方式')
|
||||
|
||||
field_name = Column(String(255), nullable=False, comment='字段名')
|
||||
|
||||
fixed = Column(String(1), nullable=False, default='0', comment='固定表头')
|
||||
|
||||
label = Column(String(255), nullable=False, comment='字段标签')
|
||||
|
||||
label_tip = Column(String(255), comment='字段标签解释')
|
||||
|
||||
prop = Column(String(255), nullable=False, comment='驼峰属性')
|
||||
|
||||
show = Column(String(1), nullable=False, default='1', comment='可见')
|
||||
|
||||
sortable = Column(String(1), nullable=False, default='0', comment='可排序')
|
||||
|
||||
table_name = Column(String(255), nullable=False, comment='表名')
|
||||
|
||||
tooltip = Column(String(1), nullable=False, default='1', comment='超出隐藏')
|
||||
|
||||
update_by = Column(Integer, comment='更新者')
|
||||
|
||||
update_by_name = Column(String(255), comment='更新者')
|
||||
|
||||
width = Column(Integer, nullable=False, default=150, comment='宽度')
|
||||
|
||||
sequence = Column(Integer, nullable=False, default=0, comment='字段顺序')
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
import datetime
|
||||
|
||||
from sqlalchemy import Boolean, Column, ForeignKey, String, Integer, Text, DateTime
|
||||
|
||||
from app.core.base_model import BaseMixin
|
||||
|
||||
|
||||
class SysTable(BaseMixin):
|
||||
__tablename__ = "gen_table"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
create_time = Column(DateTime, nullable=False, default=datetime.datetime.now, comment='创建时间')
|
||||
update_time = Column(DateTime, nullable=False, default=datetime.datetime.now, onupdate=datetime.datetime.now, index=True, comment='更新时间')
|
||||
del_flag = Column(String(1), nullable=False, default='0', server_default=text("'0'"), comment='删除标志(0代表存在 2代表删除)')
|
||||
|
||||
align = Column(String(255), nullable=False, default='left', comment='对其方式')
|
||||
|
||||
field_name = Column(String(255), nullable=False, comment='字段名')
|
||||
|
||||
fixed = Column(String(1), nullable=False, default='0', comment='固定表头')
|
||||
|
||||
label = Column(String(255), nullable=False, comment='字段标签')
|
||||
|
||||
label_tip = Column(String(255), comment='字段标签解释')
|
||||
|
||||
prop = Column(String(255), nullable=False, comment='驼峰属性')
|
||||
|
||||
show = Column(String(1), nullable=False, default='1', comment='可见')
|
||||
|
||||
sortable = Column(String(1), nullable=False, default='0', comment='可排序')
|
||||
|
||||
table_name = Column(String(255), nullable=False, comment='表名')
|
||||
|
||||
tooltip = Column(String(1), nullable=False, default='1', comment='超出隐藏')
|
||||
|
||||
update_by = Column(Integer, comment='更新者')
|
||||
|
||||
update_by_name = Column(String(255), comment='更新者')
|
||||
|
||||
width = Column(Integer, nullable=False, default=150, comment='宽度')
|
||||
|
||||
sequence = Column(Integer, nullable=False, default=0, comment='字段顺序')
|
||||
|
||||
@@ -1,192 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import io
|
||||
from typing import Any, List, Dict
|
||||
from fastapi import UploadFile
|
||||
import pandas as pd
|
||||
|
||||
from app.api.v1.schemas.system.auth_schema import AuthSchema
|
||||
from app.api.v1.schemas.demo.example_schema import ExampleCreateSchema, ExampleUpdateSchema, ExampleOutSchema
|
||||
from app.core.base_schema import BatchSetAvailable
|
||||
from app.api.v1.params.demo.example_param import ExampleQueryParams
|
||||
from app.api.v1.cruds.demo.example_crud import ExampleCRUD
|
||||
from app.core.exceptions import CustomException
|
||||
from app.utils.excel_util import ExcelUtil
|
||||
from app.core.logger import logger
|
||||
|
||||
|
||||
class ExampleService:
|
||||
"""
|
||||
示例管理模块服务层
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
async def get_example_detail_service(cls, auth: AuthSchema, id: int) -> Dict:
|
||||
"""详情"""
|
||||
obj = await ExampleCRUD(auth).get_by_id_crud(id=id)
|
||||
return ExampleOutSchema.model_validate(obj).model_dump()
|
||||
|
||||
@classmethod
|
||||
async def get_demo_list_service(cls, auth: AuthSchema, search: ExampleQueryParams = None, order_by: List[Dict[str, str]] = None) -> List[Dict]:
|
||||
"""列表查询"""
|
||||
if order_by:
|
||||
order_by = eval(order_by)
|
||||
obj_list = await ExampleCRUD(auth).get_list_crud(search=search.__dict__, order_by=order_by)
|
||||
return [ExampleOutSchema.model_validate(obj).model_dump() for obj in obj_list]
|
||||
|
||||
@classmethod
|
||||
async def create_example_service(cls, auth: AuthSchema, data: ExampleCreateSchema) -> Dict:
|
||||
"""创建"""
|
||||
obj = await ExampleCRUD(auth).get(name=data.name)
|
||||
if obj:
|
||||
raise CustomException(msg='创建失败,名称已存在')
|
||||
obj = await ExampleCRUD(auth).create_crud(data=data)
|
||||
return ExampleOutSchema.model_validate(obj).model_dump()
|
||||
|
||||
@classmethod
|
||||
async def update_example_service(cls, auth: AuthSchema, data: ExampleUpdateSchema) -> Dict:
|
||||
"""更新"""
|
||||
obj = await ExampleCRUD(auth).get_by_id_crud(id=data.id)
|
||||
if not obj:
|
||||
raise CustomException(msg='更新失败,该数据不存在')
|
||||
exist_obj = await ExampleCRUD(auth).get(name=data.name)
|
||||
if exist_obj and exist_obj.id != data.id:
|
||||
raise CustomException(msg='更新失败,名称重复')
|
||||
obj = await ExampleCRUD(auth).update_crud(id=data.id, data=data)
|
||||
return ExampleOutSchema.model_validate(obj).model_dump()
|
||||
|
||||
@classmethod
|
||||
async def delete_example_service(cls, auth: AuthSchema, ids: list[int]) -> None:
|
||||
"""删除"""
|
||||
if len(ids) < 1:
|
||||
raise CustomException(msg='删除失败,删除对象不能为空')
|
||||
for id in ids:
|
||||
obj = await ExampleCRUD(auth).get_by_id_crud(id=id)
|
||||
if not obj:
|
||||
raise CustomException(msg='删除失败,该数据不存在')
|
||||
await ExampleCRUD(auth).delete_crud(ids=ids)
|
||||
|
||||
@classmethod
|
||||
async def set_example_available_service(cls, auth: AuthSchema, data: BatchSetAvailable) -> None:
|
||||
"""批量设置状态"""
|
||||
await ExampleCRUD(auth).set_available_crud(ids=data.ids, status=data.status)
|
||||
|
||||
@classmethod
|
||||
async def batch_export_service(cls, obj_list: List[Dict[str, Any]]) -> bytes:
|
||||
"""批量导出"""
|
||||
mapping_dict = {
|
||||
'id': '编号',
|
||||
'name': '名称',
|
||||
'status': '状态',
|
||||
'description': '备注',
|
||||
'created_at': '创建时间',
|
||||
'updated_at': '更新时间',
|
||||
'creator': '创建者',
|
||||
}
|
||||
|
||||
# 复制数据并转换状态
|
||||
data = obj_list.copy()
|
||||
for item in data:
|
||||
# 处理状态
|
||||
item['status'] = '正常' if item.get('status') else '停用'
|
||||
# 处理公告类型
|
||||
item['creator'] = item.get('creator', {}).get('name', '未知') if isinstance(item.get('creator'), dict) else '未知'
|
||||
|
||||
return ExcelUtil.export_list2excel(list_data=obj_list, mapping_dict=mapping_dict)
|
||||
|
||||
@classmethod
|
||||
async def batch_import_service(cls, auth: AuthSchema, file: UploadFile, update_support: bool = False) -> str:
|
||||
"""批量导入"""
|
||||
|
||||
header_dict = {
|
||||
'名称': 'name',
|
||||
'状态': 'status',
|
||||
'描述': 'description'
|
||||
}
|
||||
|
||||
try:
|
||||
# 读取Excel文件
|
||||
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)
|
||||
|
||||
# 验证必填字段
|
||||
required_fields = ['name', 'status']
|
||||
for field in required_fields:
|
||||
if df[field].isnull().any():
|
||||
missing_rows = df[df[field].isnull()].index.tolist()
|
||||
raise CustomException(msg=f"{[k for k,v in header_dict.items() if v == field][0]}不能为空,第{[i+1 for i in missing_rows]}行")
|
||||
|
||||
error_msgs = []
|
||||
success_count = 0
|
||||
|
||||
# 处理每一行数据
|
||||
for index, row in df.iterrows():
|
||||
try:
|
||||
# 数据转换前的类型检查
|
||||
try:
|
||||
name = str(row['name'])
|
||||
except ValueError:
|
||||
error_msgs.append(f"第{index+1}行: 名称必须是字符串")
|
||||
continue
|
||||
try:
|
||||
status = True if row['status'] == '正常' else False
|
||||
except ValueError:
|
||||
error_msgs.append(f"第{index+1}行: 状态必须是'正常'或'停用'")
|
||||
continue
|
||||
|
||||
# 构建用户数据
|
||||
data = {
|
||||
"name": name,
|
||||
"status": status,
|
||||
"description": str(row['description']).strip() if not pd.isna(row['description']) else None,
|
||||
}
|
||||
|
||||
# 处理用户导入
|
||||
exists_user = await ExampleCRUD(auth).get(name=data["name"])
|
||||
if exists_user:
|
||||
if update_support:
|
||||
await ExampleCRUD(auth).update(id=exists_user.id, data=data)
|
||||
success_count += 1
|
||||
else:
|
||||
error_msgs.append(f"第{index+1}行: 用户 {data['username']} 已存在")
|
||||
else:
|
||||
await ExampleCRUD(auth).create(data=data)
|
||||
success_count += 1
|
||||
|
||||
except Exception as e:
|
||||
error_msgs.append(f"第{index+1}行: {str(e)}")
|
||||
continue
|
||||
|
||||
# 返回详细的导入结果
|
||||
result = f"成功导入 {success_count} 条数据"
|
||||
if error_msgs:
|
||||
result += "\n错误信息:\n" + "\n".join(error_msgs)
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"批量导入用户失败: {str(e)}")
|
||||
raise CustomException(msg=f"导入失败: {str(e)}")
|
||||
|
||||
@classmethod
|
||||
async def import_template_download_service(cls) -> bytes:
|
||||
"""下载导入模板"""
|
||||
header_list = ['名称', '状态', '描述']
|
||||
selector_header_list = ['状态']
|
||||
option_list = [{'状态': ['正常', '停用']}]
|
||||
return ExcelUtil.get_excel_template(
|
||||
header_list=header_list,
|
||||
selector_header_list=selector_header_list,
|
||||
option_list=option_list
|
||||
)
|
||||
@@ -1,186 +0,0 @@
|
||||
import os
|
||||
from typing import List, Dict, Any
|
||||
|
||||
from click.types import convert_type
|
||||
from sqlalchemy import Boolean
|
||||
|
||||
from module_gen.constants.gen_constants import GenConstants
|
||||
from module_gen.entity.do.gen_table_column_do import GenTableColumn
|
||||
from module_gen.entity.do.gen_table_do import GenTable
|
||||
from module_gen.entity.vo.gen_table_vo import GenTableModel
|
||||
from module_gen.entity.vo.gen_table_column_vo import GenTableColumnModel
|
||||
|
||||
|
||||
class GenUtils:
|
||||
"""代码生成器 工具类"""
|
||||
|
||||
@classmethod
|
||||
def init_table(cls, table: GenTableModel, columns: List[GenTableColumnModel]) -> None:
|
||||
"""初始化表信息"""
|
||||
table.class_name = cls.convert_class_name(table.table_name)
|
||||
table.package_name = cls.get_package_name(table.table_name)
|
||||
table.module_name = cls.get_module_name(table.table_name)
|
||||
table.business_name = cls.get_business_name(table.table_name)
|
||||
table.function_name = table.table_comment
|
||||
table.function_author = "FluxAdmin"
|
||||
|
||||
# 初始化列属性字段
|
||||
for column in columns:
|
||||
cls.init_column_field(column, table)
|
||||
|
||||
|
||||
# 设置主键列信息
|
||||
# for column in columns:
|
||||
# if column.is_pk == "1":
|
||||
# table.pk_column = column
|
||||
# break
|
||||
|
||||
@classmethod
|
||||
def init_column_field(cls, column: GenTableColumnModel, table: GenTableModel):
|
||||
data_type = cls.get_db_type(column.column_type)
|
||||
column_name = column.column_name
|
||||
column.table_id = table.table_id
|
||||
# 设置python字段名
|
||||
column.python_field = column_name
|
||||
# 设置默认类型
|
||||
column.python_type = GenConstants.MYSQL_TO_PYTHON.get(data_type.upper(), "Any")
|
||||
column.query_type = GenConstants.QUERY_EQ
|
||||
|
||||
if data_type in GenConstants.TYPE_STRING or data_type in GenConstants.TYPE_TEXT:
|
||||
# 字符串长度超过500设置为文本域
|
||||
column_length = cls.get_column_length(column.column_type)
|
||||
html_type = GenConstants.HTML_TEXTAREA if column_length >= 500 or (data_type in GenConstants.TYPE_TEXT) \
|
||||
else GenConstants.HTML_INPUT
|
||||
column.html_type = html_type
|
||||
elif data_type in GenConstants.TYPE_DATE_TIME:
|
||||
column.html_type = GenConstants.HTML_DATETIME
|
||||
elif data_type in GenConstants.TYPE_NUMBER:
|
||||
column.html_type = GenConstants.HTML_INPUT
|
||||
# 插入字段
|
||||
if column.column_name not in GenConstants.COLUMN_NAME_NOT_EDIT and not column.is_pk == '1':
|
||||
column.is_insert = GenConstants.REQUIRE
|
||||
# 编辑字段
|
||||
if column.column_name not in GenConstants.COLUMN_NAME_NOT_EDIT and not column.is_pk == '1':
|
||||
column.is_edit = GenConstants.REQUIRE
|
||||
# 列表字段
|
||||
if column.column_name not in GenConstants.COLUMN_NAME_NOT_LIST and not column.is_pk == '1':
|
||||
column.is_list = GenConstants.REQUIRE
|
||||
# 查询字段
|
||||
if column.column_name not in GenConstants.COLUMN_NAME_NOT_QUERY and not column.is_pk == '1':
|
||||
column.is_query = GenConstants.REQUIRE
|
||||
|
||||
|
||||
@classmethod
|
||||
def convert_html_type(cls, column_name: str) -> str:
|
||||
|
||||
|
||||
# 状态字段初始化
|
||||
if column_name.lower().endswith('_status'):
|
||||
return GenConstants.HTML_RADIO
|
||||
# 类型字段初始化
|
||||
elif column_name.lower().endswith('_type'):
|
||||
return GenConstants.HTML_SELECT
|
||||
# 内容字段初始化
|
||||
elif column_name.lower().endswith('_content'):
|
||||
return GenConstants.HTML_EDITOR
|
||||
# 文件字段初始化
|
||||
elif column_name.lower().endswith('_file'):
|
||||
return GenConstants.HTML_FILE_UPLOAD
|
||||
# 图片字段初始化
|
||||
elif column_name.lower().endswith('_image'):
|
||||
return GenConstants.HTML_IMAGE_UPLOAD
|
||||
else:
|
||||
return GenConstants.HTML_INPUT
|
||||
|
||||
@classmethod
|
||||
def get_db_type(cls, column_type):
|
||||
# 解析数据库类型逻辑,示例返回列的类型
|
||||
return column_type.split('(')[0]
|
||||
@classmethod
|
||||
def get_column_length(cls, column_type):
|
||||
# 获取列的长度逻辑,这里简化为返回一个默认值
|
||||
if '(' in column_type:
|
||||
return int(column_type.split('(')[1].split(')')[0])
|
||||
return 0
|
||||
@classmethod
|
||||
def convert_class_name(cls, table_name: str) -> str:
|
||||
"""表名转换成Java类名"""
|
||||
return ''.join(word.title() for word in table_name.lower().split('_'))
|
||||
|
||||
@classmethod
|
||||
def convert_python_field(cls, column_name: str) -> str:
|
||||
"""列名转换成Python属性名"""
|
||||
# words = column_name.lower().split('_')
|
||||
# return words[0] + ''.join(word.title() for word in words[1:])
|
||||
return column_name.lower()
|
||||
|
||||
@classmethod
|
||||
def get_package_name(cls, table_name: str) -> str:
|
||||
"""获取包名"""
|
||||
return "module_admin" # 可配置的包名
|
||||
|
||||
@classmethod
|
||||
def get_module_name(cls, table_name: str) -> str:
|
||||
"""获取模块名"""
|
||||
return table_name.split('_')[0]
|
||||
|
||||
@classmethod
|
||||
def get_business_name(cls, table_name: str) -> str:
|
||||
"""获取业务名"""
|
||||
words = table_name.split('_')
|
||||
return words[1] if len(words) > 1 else words[0]
|
||||
|
||||
@classmethod
|
||||
def get_template_path(cls, tpl_category: str) -> Dict[str, str]:
|
||||
"""获取模板信息"""
|
||||
templates = {
|
||||
# Python相关模板
|
||||
'controller.py': 'python/controller_template.j2',
|
||||
'do.py': 'python/model_do_template.j2',
|
||||
'vo.py': 'python/model_vo_template.j2',
|
||||
'service.py': 'python/service_template.j2',
|
||||
'dao.py': 'python/dao_template.j2',
|
||||
# Vue相关模板
|
||||
'index.vue': 'vue/index.vue.j2',
|
||||
'api.js': 'vue/api.js.j2',
|
||||
# SQL脚本模板
|
||||
'sql': 'sql/sql.j2',
|
||||
|
||||
}
|
||||
|
||||
# 树表特殊处理
|
||||
# if tpl_category == "tree":
|
||||
# templates.update({
|
||||
# 'entity': 'java/tree_entity.java.vm',
|
||||
# 'mapper': 'java/tree_mapper.java.vm',
|
||||
# 'service': 'java/tree_service.java.vm',
|
||||
# 'service_impl': 'java/tree_service_impl.java.vm',
|
||||
# 'controller': 'java/tree_controller.java.vm'
|
||||
# })
|
||||
|
||||
return templates
|
||||
|
||||
|
||||
|
||||
@classmethod
|
||||
def get_file_name(cls, template_name: str, table) -> str:
|
||||
"""获取文件名"""
|
||||
target_file_name = "unknown_file_name"
|
||||
if template_name.endswith("controller.py"):
|
||||
target_file_name = f"python/controller/{table.table_name}_{template_name}"
|
||||
elif template_name.endswith("do.py"):
|
||||
target_file_name = f"python/entity/do/{table.table_name}_{template_name}"
|
||||
elif template_name.endswith("vo.py"):
|
||||
target_file_name = f"python/entity/vo/{table.table_name}_{template_name}"
|
||||
elif template_name.endswith("service.py"):
|
||||
target_file_name = f"python/service/{table.table_name}_{template_name}"
|
||||
elif template_name.endswith("dao.py"):
|
||||
target_file_name = f"python/dao/{table.table_name}_{template_name}"
|
||||
elif template_name.endswith('index.vue'):
|
||||
target_file_name = f'vue/views/{table.module_name}/{table.business_name}/index.vue'
|
||||
if template_name.endswith('api.js'):
|
||||
target_file_name = f'vue/api/{table.module_name}/{table.business_name}.js'
|
||||
|
||||
if template_name.endswith('sql'):
|
||||
target_file_name = f'sql/{table.business_name}.sql'
|
||||
return target_file_name
|
||||
@@ -1,53 +0,0 @@
|
||||
import re
|
||||
|
||||
from module_gen.constants.gen_constants import GenConstants
|
||||
|
||||
|
||||
def snake_to_pascal_case(value):
|
||||
"""将下划线命名 (snake_case) 转换大驼峰"""
|
||||
return ''.join(word.capitalize() for word in value.split('_'))
|
||||
|
||||
|
||||
def snake_to_camel(snake_str):
|
||||
"""将下划线命名 (snake_case) 转换小驼峰"""
|
||||
components = snake_str.split('_')
|
||||
return components[0] + ''.join(x.title() for x in components[1:])
|
||||
|
||||
def snake_2_colon(snake_str: str) -> str:
|
||||
"""将下划线命名 (snake_case) 转换冒号分隔"""
|
||||
return snake_str.replace('_', ':')
|
||||
|
||||
def is_base_column(column_name: str) -> bool:
|
||||
"""判断是否是基础字段"""
|
||||
return column_name in GenConstants.BASE_ENTITY
|
||||
|
||||
def get_sqlalchemy_type(mysql_field_type: str) -> str:
|
||||
"""mysql_field_type 转sqlalchemy类型"""
|
||||
if mysql_field_type:
|
||||
base_type = mysql_field_type.split("(", 1)[0]
|
||||
if base_type.upper() in GenConstants.MYSQL_TO_SQLALCHEMY.keys():
|
||||
sqlalchemy_type = GenConstants.MYSQL_TO_SQLALCHEMY[base_type.upper()]
|
||||
if sqlalchemy_type == 'String' :
|
||||
match = re.search(r'\((.*?)\)', mysql_field_type)
|
||||
if match:
|
||||
return f'{sqlalchemy_type}({match.group(1)})'
|
||||
else:
|
||||
return f'{sqlalchemy_type}'
|
||||
else:
|
||||
return f'{sqlalchemy_type}'
|
||||
return "String"
|
||||
|
||||
def get_column_options(col) -> str:
|
||||
options = []
|
||||
# 主键
|
||||
if col['isPk'] == "1":
|
||||
options.append("primary_key=True")
|
||||
# 是否允许为空
|
||||
if col['isRequired'] == "1":
|
||||
options.append("nullable=False")
|
||||
# 自增
|
||||
if col["isIncrement"] == "1":
|
||||
options.append("autoincrement=True")
|
||||
# 注释
|
||||
options.append(f"comment='{col['columnComment']}'")
|
||||
return ", ".join(options)
|
||||
@@ -1,45 +0,0 @@
|
||||
from jinja2 import Environment, FileSystemLoader, select_autoescape
|
||||
import os
|
||||
|
||||
from module_gen.utils.jinja2_tools import snake_to_pascal_case, snake_to_camel, snake_2_colon, is_base_column, \
|
||||
get_sqlalchemy_type, get_column_options
|
||||
|
||||
|
||||
class VelocityInitializer:
|
||||
"""模板引擎初始化器"""
|
||||
|
||||
@staticmethod
|
||||
def init_velocity() -> Environment:
|
||||
"""初始化模板引擎"""
|
||||
try:
|
||||
# 设置模板加载器
|
||||
template_dir = os.path.abspath(os.path.join(os.getcwd(), 'module_gen/templates'))
|
||||
loader = FileSystemLoader(template_dir)
|
||||
|
||||
# 创建Jinja2环境
|
||||
env = Environment(
|
||||
loader=loader,
|
||||
autoescape=select_autoescape(['html', 'xml']),
|
||||
trim_blocks=True,
|
||||
lstrip_blocks=True
|
||||
)
|
||||
|
||||
# 添加自定义过滤器
|
||||
env.filters.update({
|
||||
'capitalize': lambda x: x.capitalize(),
|
||||
'lower': lambda x: x.lower(),
|
||||
'upper': lambda x: x.upper(),
|
||||
'camelcase': lambda x: ''.join(word.title() for word in x.split('_')),
|
||||
'snakecase': lambda x: '_'.join(x.lower().split()),
|
||||
'snake_to_pascal_case': snake_to_pascal_case,
|
||||
'snake_to_camel': snake_to_camel,
|
||||
'snake_2_colon': snake_2_colon,
|
||||
'is_base_column': is_base_column,
|
||||
'get_sqlalchemy_type': get_sqlalchemy_type,
|
||||
'get_column_options': get_column_options,
|
||||
})
|
||||
|
||||
return env
|
||||
|
||||
except Exception as e:
|
||||
raise RuntimeError(f"初始化模板引擎失败: {str(e)}")
|
||||
@@ -1,198 +0,0 @@
|
||||
from typing import Dict, Any, List
|
||||
from jinja2 import Template
|
||||
|
||||
from module_gen.constants.gen_constants import GenConstants
|
||||
from module_gen.dao.gen_table_column_dao import GenTableColumnDao
|
||||
from module_gen.dao.gen_table_dao import GenTableDao
|
||||
from module_gen.entity.do.gen_table_column_do import GenTableColumn
|
||||
from module_gen.entity.do.gen_table_do import GenTable
|
||||
from module_gen.entity.vo.gen_table_column_vo import GenTableColumnPageModel, GenTableColumnModel
|
||||
from module_gen.entity.vo.gen_table_vo import GenTableModel
|
||||
from module_gen.utils.velocity_initializer import VelocityInitializer
|
||||
import os
|
||||
|
||||
from utils.common_util import CamelCaseUtil
|
||||
|
||||
|
||||
class VelocityUtils:
|
||||
"""模板处理工具类"""
|
||||
|
||||
|
||||
# 默认上级菜单,系统工具
|
||||
DEFAULT_PARENT_MENU_ID = "3"
|
||||
|
||||
# 环境对象
|
||||
_env = None
|
||||
|
||||
@classmethod
|
||||
def get_env(cls):
|
||||
"""获取模板环境对象"""
|
||||
if cls._env is None:
|
||||
cls._env = VelocityInitializer.init_velocity()
|
||||
return cls._env
|
||||
|
||||
@classmethod
|
||||
def get_template(cls, template_path: str) -> Template:
|
||||
"""获取模板"""
|
||||
return cls.get_env().get_template(template_path)
|
||||
|
||||
@classmethod
|
||||
async def get_render_params(cls, gen_table: GenTableModel, query_db) -> Dict[str, Any]:
|
||||
"""设置模板变量信息"""
|
||||
# 设置python文件路径
|
||||
sub_table = None
|
||||
if gen_table.sub_table_name:
|
||||
# 子表信息
|
||||
sub_table = await GenTableDao.get_by_table_name(query_db, gen_table.sub_table_name)
|
||||
# 设置主子表信息
|
||||
await cls.set_sub_table_value(query_db, gen_table, sub_table)
|
||||
|
||||
# 设置主键列信息
|
||||
table_columns_dicts = await GenTableColumnDao.get_gen_table_column_list(query_db, GenTableColumnPageModel(tableId=gen_table.table_id))
|
||||
table_columns = [GenTableColumnModel(**tcd) for tcd in table_columns_dicts]
|
||||
pk_column = None
|
||||
for column in table_columns:
|
||||
if column.is_pk == "1":
|
||||
pk_column = column
|
||||
break
|
||||
|
||||
context = {
|
||||
# 文件名称
|
||||
"tableName": gen_table.table_name,
|
||||
# 小写类名
|
||||
"className": gen_table.class_name.lower(),
|
||||
# 大写类名
|
||||
"ClassName": gen_table.class_name,
|
||||
# 包路径
|
||||
"packageName": gen_table.package_name,
|
||||
# 模块名
|
||||
"moduleName": gen_table.module_name,
|
||||
# 业务名
|
||||
"businessName": gen_table.business_name,
|
||||
# 业务名(首字母大写)
|
||||
"BusinessName": gen_table.business_name.capitalize(),
|
||||
# 功能名称
|
||||
"functionName": gen_table.function_name,
|
||||
# 作者
|
||||
"author": gen_table.function_author,
|
||||
# 主键字段
|
||||
"pkColumn": pk_column.model_dump(by_alias=True) if pk_column else None,
|
||||
# 导入sqlalchemy需要导入的类型字段
|
||||
"importList": cls.get_import_list(table_columns),
|
||||
# 列集合
|
||||
"columns": [tcn.model_dump(by_alias=True) for tcn in table_columns],
|
||||
# 生成路径
|
||||
"genPath": gen_table.gen_path,
|
||||
# 表描述
|
||||
"tableComment": gen_table.table_comment,
|
||||
# 权限前缀
|
||||
"permissionPrefix": cls.get_permission_prefix(gen_table.module_name, gen_table.business_name),
|
||||
# 是否包含主键
|
||||
"hasPk": cls.has_pk_column(table_columns),
|
||||
# 是否包含Bigdecimal
|
||||
"hasBigDecimal": cls.has_column_big_decimal(table_columns),
|
||||
# 是否包含时间类型
|
||||
"hasDateTime": cls.has_column_datetime(table_columns),
|
||||
# 主键是否自增
|
||||
"auto": cls.is_pk_auto(table_columns),
|
||||
# 父级菜单ID
|
||||
"parentMenuId": gen_table.parent_menu_id,
|
||||
# 字段关联的字典名
|
||||
"dicts": cls.get_column_related_dicts(table_columns),
|
||||
}
|
||||
|
||||
if gen_table.tpl_category == "tree":
|
||||
context.update({
|
||||
"treeCode": gen_table.tree_code,
|
||||
"treeParentCode": gen_table.tree_parent_code,
|
||||
"treeName": gen_table.tree_name,
|
||||
"expandColumn": gen_table.tree_name,
|
||||
"tree_parent_code": gen_table.tree_parent_code,
|
||||
"tree_name": gen_table.tree_name
|
||||
})
|
||||
|
||||
if gen_table.tpl_category == "sub":
|
||||
context.update({
|
||||
"subTable": sub_table,
|
||||
"subTableName": gen_table.sub_table_name,
|
||||
"subTableFkName": gen_table.sub_table_fk_name,
|
||||
"subClassName": sub_table.class_name,
|
||||
"subclassName": sub_table.class_name.lower(),
|
||||
"subImportList": cls.get_import_list(sub_table.columns)
|
||||
})
|
||||
|
||||
return context
|
||||
|
||||
@classmethod
|
||||
def get_permission_prefix(cls, module_name: str, business_name: str) -> str:
|
||||
"""获取权限前缀"""
|
||||
return f"{module_name}:{business_name}"
|
||||
|
||||
@classmethod
|
||||
async def set_sub_table_value(cls, query_db, gen_table: GenTableModel, sub_table: GenTable):
|
||||
"""设置主子表信息"""
|
||||
table_columns = await GenTableColumnDao.get_list_by_table_id(query_db, sub_table.table_id)
|
||||
for column in table_columns:
|
||||
if column.is_pk == "1":
|
||||
gen_table.pk_column = column
|
||||
break
|
||||
|
||||
@classmethod
|
||||
def get_import_list(cls, table_columns: List[GenTableColumnModel]) -> str:
|
||||
"""获取需要导入的包列表"""
|
||||
sqlalchemy_types = []
|
||||
for i, table_column in enumerate(table_columns):
|
||||
if table_column.column_type:
|
||||
mysql_type = table_column.column_type.split("(")[0]
|
||||
if mysql_type.upper() in GenConstants.MYSQL_TO_SQLALCHEMY.keys():
|
||||
temp_type = GenConstants.MYSQL_TO_SQLALCHEMY[mysql_type.upper()]
|
||||
if temp_type not in sqlalchemy_types:
|
||||
sqlalchemy_types.append(temp_type)
|
||||
return ", ".join(sqlalchemy_types)
|
||||
|
||||
@staticmethod
|
||||
def has_column_datetime(columns: List[GenTableColumnModel]) -> bool:
|
||||
"""判断是否包含datetime"""
|
||||
return any(column.python_type == "Date" for column in columns)
|
||||
|
||||
@staticmethod
|
||||
def has_column_big_decimal(columns: List[GenTableColumnModel]) -> bool:
|
||||
"""判断是否包含BigDecimal"""
|
||||
return any(column.python_type == "BigDecimal" for column in columns)
|
||||
|
||||
@staticmethod
|
||||
def has_pk_column(columns: List[GenTableColumnModel]) -> bool:
|
||||
"""判断是否包含主键"""
|
||||
return any(column.is_pk == "1" for column in columns)
|
||||
|
||||
@staticmethod
|
||||
def is_pk_auto(columns: List[GenTableColumnModel]) -> bool:
|
||||
"""判断主键是否自增"""
|
||||
return any(column.is_pk == "1" and column.is_increment == "1" for column in columns)
|
||||
|
||||
@classmethod
|
||||
def write_file(cls, template_path: str, context: Dict[str, Any], file_path: str) -> None:
|
||||
"""渲染模板并写入文件"""
|
||||
try:
|
||||
# 获取生成文件的目录
|
||||
out_dir = os.path.dirname(file_path)
|
||||
if not os.path.exists(out_dir):
|
||||
os.makedirs(out_dir)
|
||||
|
||||
# 渲染模板
|
||||
template = cls.get_template(template_path)
|
||||
content = template.render(**context)
|
||||
|
||||
# 写入文件
|
||||
with open(file_path, 'w', encoding='utf-8') as f:
|
||||
f.write(content)
|
||||
except Exception as e:
|
||||
raise RuntimeError(f"渲染模板失败,模板路径:{template_path}") from e
|
||||
|
||||
@classmethod
|
||||
def get_column_related_dicts(cls, table_columns) -> str:
|
||||
dicts = []
|
||||
for table_column in table_columns:
|
||||
if table_column.dict_type:
|
||||
dicts.append(f"'{table_column.dict_type}'")
|
||||
return ", ".join(dicts)
|
||||
@@ -9,6 +9,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from redis.asyncio.client import Redis
|
||||
|
||||
from app.common.enums import RedisInitKeyConfig
|
||||
from app.core.database import AsyncSessionLocal
|
||||
from app.core.redis_crud import RedisCURD
|
||||
from app.utils.excel_util import ExcelUtil
|
||||
from app.utils.upload_util import UploadUtil
|
||||
@@ -158,27 +159,28 @@ class ConfigService:
|
||||
).model_dump()
|
||||
|
||||
@classmethod
|
||||
async def init_config_service(cls, redis: Redis, db: AsyncSession) -> bool:
|
||||
auth = AuthSchema(db=db)
|
||||
config_obj = await ConfigCRUD(auth).get_obj_list_crud()
|
||||
if not config_obj:
|
||||
raise CustomException(msg="系统配置不存在")
|
||||
try:
|
||||
# 保存到Redis并设置过期时间
|
||||
for config in config_obj:
|
||||
redis_key = (f"{RedisInitKeyConfig.SYSTEM_CONFIG.key}:{config.config_key}")
|
||||
config_obj_dict = ConfigOutSchema.model_validate(config).model_dump()
|
||||
value = json.dumps(config_obj_dict, ensure_ascii=False)
|
||||
result = await RedisCURD(redis).set(
|
||||
key=redis_key,
|
||||
value=value,
|
||||
)
|
||||
if not result:
|
||||
logger.error(f"初始化系统配置失败: {config_obj_dict}")
|
||||
raise CustomException(msg="初始化系统配置失败")
|
||||
except Exception as e:
|
||||
logger.error(f"初始化系统配置失败: {e}")
|
||||
raise CustomException(msg="初始化系统配置失败")
|
||||
async def init_config_service(cls, redis: Redis) -> bool:
|
||||
async with AsyncSessionLocal() as session:
|
||||
auth = AuthSchema(db=session)
|
||||
config_obj = await ConfigCRUD(auth).get_obj_list_crud()
|
||||
if not config_obj:
|
||||
raise CustomException(msg="系统配置不存在")
|
||||
try:
|
||||
# 保存到Redis并设置过期时间
|
||||
for config in config_obj:
|
||||
redis_key = (f"{RedisInitKeyConfig.SYSTEM_CONFIG.key}:{config.config_key}")
|
||||
config_obj_dict = ConfigOutSchema.model_validate(config).model_dump()
|
||||
value = json.dumps(config_obj_dict, ensure_ascii=False)
|
||||
result = await RedisCURD(redis).set(
|
||||
key=redis_key,
|
||||
value=value,
|
||||
)
|
||||
if not result:
|
||||
logger.error(f"初始化系统配置失败: {config_obj_dict}")
|
||||
raise CustomException(msg="初始化系统配置失败")
|
||||
except Exception as e:
|
||||
logger.error(f"初始化系统配置失败: {e}")
|
||||
raise CustomException(msg="初始化系统配置失败")
|
||||
|
||||
@classmethod
|
||||
async def get_init_config_service(cls, redis: Redis) -> Dict:
|
||||
|
||||
@@ -17,9 +17,12 @@ class DeptModel(ModelMixin):
|
||||
name: Mapped[str] = mapped_column(String(40),nullable=False,unique=True,comment="部门名称")
|
||||
order: Mapped[int] = mapped_column(Integer,nullable=False,default=999,comment="显示排序")
|
||||
|
||||
parent_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("system_dept.id", ondelete="CASCADE", onupdate="CASCADE"), nullable=True, index=True, comment="父级部门ID")
|
||||
parent: Mapped[Optional["DeptModel"]] = relationship("DeptModel", cascade="all, delete-orphan", uselist=False)
|
||||
parent_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("system_dept.id", ondelete="SET NULL", onupdate="CASCADE"), default=None, index=True, comment="父级部门ID")
|
||||
# parent: Mapped[Optional["DeptModel"]] = relationship("DeptModel", cascade="all, delete-orphan", uselist=False)
|
||||
|
||||
parent: Mapped[Optional['DeptModel']] = relationship(init=False, back_populates='children', remote_side=[id])
|
||||
children: Mapped[Optional[list['DeptModel']]] = relationship(init=False, back_populates='parent')
|
||||
|
||||
# 角色关联关系
|
||||
roles: Mapped[List["RoleModel"]] = relationship(secondary="system_role_depts", back_populates="depts", lazy="selectin")
|
||||
|
||||
@@ -27,6 +30,4 @@ class DeptModel(ModelMixin):
|
||||
users: Mapped[List["UserModel"]] = relationship(back_populates="dept", lazy="selectin")
|
||||
|
||||
# code: Mapped[Optional[str]] = mapped_column(String(20),nullable=True,unique=True,comment="部门编码")
|
||||
# leader_id: Mapped[Optional[int]] = mapped_column(Integer,nullable=True,comment="负责人ID")
|
||||
# parent: Mapped[Optional["DeptModel"]] = relationship(back_populates="children",remote_side=[id],lazy="select",uselist=False,foreign_keys=[parent_id])
|
||||
# children: Mapped[List["DeptModel"]] = relationship(back_populates="parent",lazy="select",cascade="all, delete-orphan",foreign_keys=[parent_id])
|
||||
# leader_id: Mapped[Optional[int]] = mapped_column(Integer,nullable=True,comment="负责人ID")
|
||||
@@ -7,6 +7,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.common.enums import RedisInitKeyConfig
|
||||
from app.utils.excel_util import ExcelUtil
|
||||
from app.core.database import AsyncSessionLocal
|
||||
from app.core.base_schema import BatchSetAvailable
|
||||
from app.core.redis_crud import RedisCURD
|
||||
from app.core.exceptions import CustomException
|
||||
@@ -187,33 +188,33 @@ class DictDataService:
|
||||
@classmethod
|
||||
async def init_dict_service(cls, redis: Redis, db: AsyncSession):
|
||||
"""应用初始化: 获取所有字典类型对应的字典数据信息并缓存service"""
|
||||
|
||||
auth = AuthSchema(db=db)
|
||||
obj_list = await DictTypeCRUD(auth).get_obj_list_crud()
|
||||
if not obj_list:
|
||||
logger.warning("未找到任何字典类型数据")
|
||||
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} 未找到对应的字典数据")
|
||||
continue
|
||||
|
||||
dict_data = [DictDataOutSchema.model_validate(row).model_dump() for row in dict_data_list if row]
|
||||
|
||||
# 保存到Redis并设置过期时间
|
||||
redis_key = f"{RedisInitKeyConfig.SYSTEM_DICT.key}:{dict_type}"
|
||||
try:
|
||||
value = json.dumps(dict_data, ensure_ascii=False)
|
||||
await RedisCURD(redis).set(
|
||||
key=redis_key,
|
||||
value=value,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"初始化字典数据失败: {e}")
|
||||
raise CustomException(msg=f"初始化字典数据失败 {e}")
|
||||
async with AsyncSessionLocal() as session:
|
||||
auth = AuthSchema(db=session)
|
||||
obj_list = await DictTypeCRUD(auth).get_obj_list_crud()
|
||||
if not obj_list:
|
||||
logger.warning("未找到任何字典类型数据")
|
||||
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} 未找到对应的字典数据")
|
||||
continue
|
||||
|
||||
dict_data = [DictDataOutSchema.model_validate(row).model_dump() for row in dict_data_list if row]
|
||||
|
||||
# 保存到Redis并设置过期时间
|
||||
redis_key = f"{RedisInitKeyConfig.SYSTEM_DICT.key}:{dict_type}"
|
||||
try:
|
||||
value = json.dumps(dict_data, ensure_ascii=False)
|
||||
await RedisCURD(redis).set(
|
||||
key=redis_key,
|
||||
value=value,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"初始化字典数据失败: {e}")
|
||||
raise CustomException(msg=f"初始化字典数据失败 {e}")
|
||||
|
||||
@classmethod
|
||||
async def get_init_dict_service(cls, redis: Redis, dict_type: str)->List[Dict]:
|
||||
|
||||
@@ -42,12 +42,12 @@ class MenuModel(ModelMixin):
|
||||
affix: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False, comment='是否固定标签页(True:是 False:否)')
|
||||
|
||||
parent_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey('system_menu.id', ondelete='SET NULL'), default=None, index=True, comment='父菜单ID')
|
||||
parent: Mapped[Optional['MenuModel']] = relationship(cascade='all, delete-orphan', primaryjoin="MenuModel.parent_id == MenuModel.id", uselist=False)
|
||||
# parent: Mapped[Optional['MenuModel']] = relationship(cascade='all, delete-orphan', primaryjoin="MenuModel.parent_id == MenuModel.id", uselist=False)
|
||||
|
||||
# 角色关联关系
|
||||
roles: Mapped[List["RoleModel"]] = relationship(secondary="system_role_menus", back_populates="menus", lazy="selectin")
|
||||
|
||||
# link: Mapped[Optional[str]] = mapped_column(String(255), comment='外链地址')
|
||||
# iframe: Mapped[Optional[str]] = mapped_column(String(255), comment='内嵌iframe地址')
|
||||
# parent: Mapped[Optional['MenuModel']] = relationship(init=False, back_populates='children', remote_side=[id])
|
||||
# children: Mapped[Optional[list['MenuModel']]] = relationship(init=False, back_populates='parent')
|
||||
parent: Mapped[Optional['MenuModel']] = relationship(init=False, back_populates='children', remote_side=[id])
|
||||
children: Mapped[Optional[list['MenuModel']]] = relationship(init=False, back_populates='parent')
|
||||
|
||||
Reference in New Issue
Block a user