ShiErFeng/shierfeng-fastapi-backend/cli/runtime/gen/service.py
2026-07-24 09:43:10 +08:00

413 lines
17 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from typing import Any
from cli.exit_codes import ARGUMENT_ERROR, DATABASE_ERROR, RUNTIME_ERROR
from .gateway import GenInfrastructureGateway
from .support import GenDomainSupport
class GenRuntimeService:
"""
代码生成运行时服务。
该服务作为代码生成运行时 facade对外统一暴露业务表、数据库物理表、
建表 SQL、代码预览、导出与数据库同步等入口。
:param infrastructure_gateway: 代码生成基础设施网关
:param domain_support: 代码生成领域支持对象
"""
def __init__(
self,
*,
infrastructure_gateway: GenInfrastructureGateway | None = None,
domain_support: GenDomainSupport | None = None,
) -> None:
"""
初始化代码生成运行时服务。
:param infrastructure_gateway: 代码生成基础设施网关
:param domain_support: 代码生成领域支持对象
:return: None
"""
self.infrastructure_gateway = infrastructure_gateway or GenInfrastructureGateway()
self.domain_support = domain_support or GenDomainSupport(self.infrastructure_gateway)
async def import_tables(self, table_names: list[str], *, dry_run: bool = False) -> dict[str, Any]:
"""
导入数据库表到代码生成业务表。
:param table_names: 待导入表名列表
:param dry_run: 是否仅演练执行
:return: 导入结果
"""
normalized_table_names = self.domain_support.normalize_table_names(table_names)
if not normalized_table_names:
return {'ok': False, 'message': '至少需要传入一个表名', 'exit_code': ARGUMENT_ERROR}
async_session_local = self.infrastructure_gateway.get_async_session_local()
gen_table_service = self.infrastructure_gateway.get_gen_table_service()
service_exception = self.infrastructure_gateway.get_service_exception_class()
try:
async with async_session_local() as session:
gen_table_list = await gen_table_service.get_gen_db_table_list_by_name_services(
session,
normalized_table_names,
)
matched_table_names = [gen_table.table_name for gen_table in gen_table_list if gen_table.table_name]
missing_table_names = [
table_name for table_name in normalized_table_names if table_name not in matched_table_names
]
if dry_run:
return {
'ok': True,
'message': '导入表结构演练完成,未执行实际写入',
'dryRun': True,
'requestedTables': normalized_table_names,
'matchedTables': matched_table_names,
'missingTables': missing_table_names,
}
result = await gen_table_service.import_gen_table_services(
session,
gen_table_list,
self.domain_support.build_cli_current_user(),
)
except service_exception as exc:
return {'ok': False, 'message': '导入表结构失败', 'error': str(exc), 'exit_code': DATABASE_ERROR}
except Exception as exc:
return {'ok': False, 'message': '导入表结构失败', 'error': str(exc), 'exit_code': DATABASE_ERROR}
return {
'ok': bool(result.is_success),
'message': result.message,
'requestedTables': normalized_table_names,
}
async def list_gen_tables(
self,
*,
table_name: str = '',
table_comment: str = '',
paged: bool = False,
page_num: int = 1,
page_size: int = 20,
) -> dict[str, Any]:
"""
查询代码生成业务表列表。
:param table_name: 表名称过滤条件
:param table_comment: 表描述过滤条件
:param paged: 是否启用分页
:param page_num: 页码
:param page_size: 每页数量
:return: 查询结果
"""
async_session_local = self.infrastructure_gateway.get_async_session_local()
gen_table_service = self.infrastructure_gateway.get_gen_table_service()
try:
gen_vo_module = self.infrastructure_gateway.get_gen_vo_module()
query_model = gen_vo_module.GenTablePageQueryModel(
tableName=table_name or None,
tableComment=table_comment or None,
pageNum=page_num,
pageSize=page_size,
)
async with async_session_local() as session:
result = await gen_table_service.get_gen_table_list_services(session, query_model, is_page=paged)
except Exception as exc:
return {
'ok': False,
'message': '读取代码生成业务表列表失败',
'error': str(exc),
'exit_code': DATABASE_ERROR,
}
filters = {
'tableName': table_name,
'tableComment': table_comment,
'paged': paged,
'pageNum': page_num,
'pageSize': page_size,
}
return self.domain_support.build_list_payload(result, filters=filters, paged=paged)
async def list_gen_db_tables(
self,
*,
table_name: str = '',
table_comment: str = '',
paged: bool = False,
page_num: int = 1,
page_size: int = 20,
) -> dict[str, Any]:
"""
查询数据库中可导入的物理表列表。
:param table_name: 表名称过滤条件
:param table_comment: 表描述过滤条件
:param paged: 是否启用分页
:param page_num: 页码
:param page_size: 每页数量
:return: 查询结果
"""
async_session_local = self.infrastructure_gateway.get_async_session_local()
gen_table_service = self.infrastructure_gateway.get_gen_table_service()
try:
gen_vo_module = self.infrastructure_gateway.get_gen_vo_module()
query_model = gen_vo_module.GenTablePageQueryModel(
tableName=table_name or None,
tableComment=table_comment or None,
pageNum=page_num,
pageSize=page_size,
)
async with async_session_local() as session:
result = await gen_table_service.get_gen_db_table_list_services(session, query_model, is_page=paged)
except Exception as exc:
return {'ok': False, 'message': '读取数据库表列表失败', 'error': str(exc), 'exit_code': DATABASE_ERROR}
filters = {
'tableName': table_name,
'tableComment': table_comment,
'paged': paged,
'pageNum': page_num,
'pageSize': page_size,
}
return self.domain_support.build_list_payload(result, filters=filters, paged=paged)
async def create_tables(self, sql: str, sql_file: str, *, dry_run: bool = False) -> dict[str, Any]:
"""
根据 SQL 创建表并导入代码生成业务表。
:param sql: 直接传入的 SQL 文本
:param sql_file: SQL 文件路径
:param dry_run: 是否仅演练执行
:return: 创建结果
"""
try:
sql_text = self.domain_support.resolve_sql_text(sql, sql_file)
sql_statements, table_names = self.domain_support.parse_create_table_sql(sql_text)
except ValueError as exc:
return {'ok': False, 'message': '创建表结构失败', 'error': str(exc), 'exit_code': ARGUMENT_ERROR}
except Exception as exc:
return {'ok': False, 'message': '创建表结构失败', 'error': str(exc), 'exit_code': RUNTIME_ERROR}
if dry_run:
return {
'ok': True,
'message': '建表语句演练完成,未执行实际建表',
'dryRun': True,
'statementCount': len(sql_statements),
'tableNames': table_names,
'sql': sql_text,
}
async_session_local = self.infrastructure_gateway.get_async_session_local()
gen_table_service = self.infrastructure_gateway.get_gen_table_service()
service_exception = self.infrastructure_gateway.get_service_exception_class()
try:
async with async_session_local() as session:
result = await gen_table_service.create_table_services(
session,
sql_text,
self.domain_support.build_cli_current_user(),
)
except service_exception as exc:
return {'ok': False, 'message': '创建表结构失败', 'error': str(exc), 'exit_code': DATABASE_ERROR}
except Exception as exc:
return {'ok': False, 'message': '创建表结构失败', 'error': str(exc), 'exit_code': DATABASE_ERROR}
return {
'ok': bool(result.is_success),
'message': result.message,
'tableNames': table_names,
}
async def preview_code(self, table_id: int) -> dict[str, Any]:
"""
预览指定业务表的代码生成结果。
:param table_id: 业务表 ID
:return: 预览结果
"""
async_session_local = self.infrastructure_gateway.get_async_session_local()
gen_table_service = self.infrastructure_gateway.get_gen_table_service()
service_exception = self.infrastructure_gateway.get_service_exception_class()
try:
async with async_session_local() as session:
preview_payload = await gen_table_service.preview_code_services(session, table_id)
except service_exception as exc:
return {'ok': False, 'message': '预览代码失败', 'error': str(exc), 'exit_code': DATABASE_ERROR}
except Exception as exc:
return {'ok': False, 'message': '预览代码失败', 'error': str(exc), 'exit_code': DATABASE_ERROR}
return {
'ok': True,
'tableId': table_id,
'templateCount': len(preview_payload),
'preview': preview_payload,
}
async def get_gen_table_detail(self, table_id: int) -> dict[str, Any]:
"""
读取单个代码生成业务表详情。
:param table_id: 业务表 ID
:return: 详情结果
"""
async_session_local = self.infrastructure_gateway.get_async_session_local()
gen_table_service = self.infrastructure_gateway.get_gen_table_service()
gen_table_column_service = self.infrastructure_gateway.get_gen_table_column_service()
try:
async with async_session_local() as session:
info = await gen_table_service.get_gen_table_by_id_services(session, table_id)
if not getattr(info, 'table_id', None):
return {
'ok': False,
'message': f'代码生成业务表不存在:{table_id}',
'tableId': table_id,
'exit_code': RUNTIME_ERROR,
}
rows = await gen_table_column_service.get_gen_table_column_list_by_table_id_services(session, table_id)
tables = await gen_table_service.get_gen_table_all_services(session)
except Exception as exc:
return {
'ok': False,
'message': '读取代码生成业务表详情失败',
'error': str(exc),
'exit_code': DATABASE_ERROR,
}
detail_payload = {
'info': self.domain_support.serialize_gen_item(info),
'rows': self.domain_support.serialize_gen_items(rows),
'tables': self.domain_support.serialize_gen_items(tables),
}
return {
'ok': True,
'tableId': table_id,
'tableName': detail_payload['info'].get('tableName', ''),
'columnCount': len(detail_payload['rows']),
'tableCount': len(detail_payload['tables']),
'detail': detail_payload,
}
async def export_code(
self,
table_names: list[str],
*,
mode: str = 'zip',
output_file: str = '',
dry_run: bool = False,
) -> dict[str, Any]:
"""
导出代码生成结果。
:param table_names: 业务表名称列表
:param mode: 导出模式,支持 `zip` 与 `local`
:param output_file: zip 导出目标文件路径
:param dry_run: 是否仅演练执行
:return: 导出结果
"""
normalized_table_names = self.domain_support.normalize_table_names(table_names)
if not normalized_table_names:
return {'ok': False, 'message': '至少需要传入一个表名', 'exit_code': ARGUMENT_ERROR}
if mode not in {'zip', 'local'}:
return {'ok': False, 'message': '导出模式仅支持 zip 或 local', 'exit_code': ARGUMENT_ERROR}
gen_config = self.infrastructure_gateway.get_gen_config()
if mode == 'local' and not gen_config.allow_overwrite:
return {
'ok': False,
'message': '当前系统配置不允许生成文件覆盖到本地',
'hint': '请检查 GenConfig.allow_overwrite 配置',
'exit_code': RUNTIME_ERROR,
}
if dry_run:
dry_run_payload: dict[str, Any] = {
'ok': True,
'message': '代码导出演练完成,未执行实际导出',
'dryRun': True,
'mode': mode,
'tableNames': normalized_table_names,
}
if mode == 'zip':
target_file = output_file.strip() or f'gen_code_{"_".join(normalized_table_names)}.zip'
dry_run_payload['outputFile'] = self.domain_support.resolve_output_file_path(target_file)
else:
dry_run_payload['genPath'] = gen_config.GEN_PATH
return dry_run_payload
async_session_local = self.infrastructure_gateway.get_async_session_local()
gen_table_service = self.infrastructure_gateway.get_gen_table_service()
service_exception = self.infrastructure_gateway.get_service_exception_class()
try:
async with async_session_local() as session:
if mode == 'zip':
zip_bytes = await gen_table_service.batch_gen_code_services(session, normalized_table_names)
else:
messages = []
for table_name in normalized_table_names:
result = await gen_table_service.generate_code_services(session, table_name)
messages.append({'tableName': table_name, 'message': result.message, 'ok': result.is_success})
return {
'ok': True,
'message': '代码已生成到本地目录',
'mode': mode,
'tableNames': normalized_table_names,
'genPath': gen_config.GEN_PATH,
'results': messages,
}
except service_exception as exc:
return {'ok': False, 'message': '导出代码失败', 'error': str(exc), 'exit_code': DATABASE_ERROR}
except Exception as exc:
return {'ok': False, 'message': '导出代码失败', 'error': str(exc), 'exit_code': DATABASE_ERROR}
target_file = output_file.strip() or f'gen_code_{"_".join(normalized_table_names)}.zip'
try:
target_path = self.domain_support.write_export_zip(target_file, zip_bytes)
except Exception as exc:
return {'ok': False, 'message': '写出导出文件失败', 'error': str(exc), 'exit_code': RUNTIME_ERROR}
return {
'ok': True,
'message': '代码压缩包导出完成',
'mode': mode,
'tableNames': normalized_table_names,
'outputFile': target_path,
'size': len(zip_bytes),
}
async def sync_gen_table_from_db(self, table_name: str) -> dict[str, Any]:
"""
将指定代码生成业务表与数据库表结构进行同步。
:param table_name: 业务表名称
:return: 同步结果
"""
normalized_table_name = table_name.strip()
if not normalized_table_name:
return {'ok': False, 'message': '表名不能为空', 'exit_code': ARGUMENT_ERROR}
async_session_local = self.infrastructure_gateway.get_async_session_local()
gen_table_service = self.infrastructure_gateway.get_gen_table_service()
service_exception = self.infrastructure_gateway.get_service_exception_class()
try:
async with async_session_local() as session:
result = await gen_table_service.sync_db_services(session, normalized_table_name)
except service_exception as exc:
return {'ok': False, 'message': '同步数据库表结构失败', 'error': str(exc), 'exit_code': DATABASE_ERROR}
except Exception as exc:
return {'ok': False, 'message': '同步数据库表结构失败', 'error': str(exc), 'exit_code': DATABASE_ERROR}
return {
'ok': bool(result.is_success),
'message': result.message,
'tableName': normalized_table_name,
}
GEN_RUNTIME = GenRuntimeService()