Files
2026-09-12 11:46:38 +08:00

1047 lines
38 KiB
Python

# +======================================================================
# | DjangoAdmin 企业数字化开发平台 [ 赋能开发者,助力企业发展 ]
# +----------------------------------------------------------------------
# | Copyright (c) 2021-2026 小蚂蚁云团队 版权所有
# +----------------------------------------------------------------------
# | 开源协议: Apache-2.0
# | 重要声明: 未经书面许可,不得移除或修改本框架相关版权标识。
# +----------------------------------------------------------------------
# | 官方网站: https://www.djangoadmin.cn
# +----------------------------------------------------------------------
# | 研发团队: 小蚂蚁云团队 荣誉出品
# +======================================================================
# | 版权和免责声明:
# | 一、知识产权声明
# | 本团队对该软件框架产品(以下简称“本产品”)拥有完整知识产权,包括但不限于商标权、专利权、
# | 著作权、商业秘密等。前述权利均受中华人民共和国及相关国际法律法规保护。未经本团队书面授权,
# | 任何个人或单位不得基于本产品本身申请任何形式的知识产权。
# | 二、使用限制与禁止条款
# | 您理解并同意,禁止将本产品用于以下任何行为:
# | 1. 任何违反中华人民共和国法律法规的用途;
# | 2. 任何侵害他人合法权益(包括但不限于人身权、财产权、知识产权等)的行为;
# | 3. 任何危害国家安全、公共利益及社会道德风尚的行为。
# | 三、责任豁免
# | 因使用本产品而产生的任何损失或法律纠纷(包括但不限于直接损失、间接损失、附带损失、衍生损失
# | 以及因违约、侵权等引发的责任),本团队均不承担任何法律责任。若因违规使用本产品导致任何第三
# | 方提起索赔或行政机关进行调查,您应独立承担全部责任。
# | 四、违约与法律责任
# | 如发现任何违反本声明的行为,本团队有权:
# | 1. 无条件配合公安机关及其他有权机关的调查取证工作;
# | 2. 依法追究相关方的民事责任,并要求赔偿全部损失;
# | 3. 保留进一步采取刑事控告等法律措施的权利。
# | 五、许可范围说明
# | 1、被授权方应视本产品源代码为核心机密信息,负有最高标准的保密与保管义务。禁止以任何形式(包
# | 括但不限于直接或间接的分享、复制、传播、分发、转售、出租、开源、反向工程、反编译、反汇编或通
# | 过网络服务提供给任何第三方)泄露、披露或处置源代码的全部或部分内容。任何未经授权的复制、使用、
# | 传播行为均构成对本团队知识产权的严重侵权,本团队有权依法追究其全部法律责任,并要求赔偿因此造
# | 成的一切经济损失及商誉损害。
# | 2、本产品仅限于公司或个人在法律允许范围内,用于内部且合规的软件产品研发。具体权利义务以《软件
# | 许可协议》附件为准。
# +======================================================================
"""
数据库表结构解析模块
=============================================================
该模块用于从数据库中读取表结构信息,自动生成代码生成器所需的配置。
支持解析字段类型、注释、长度、是否为空、默认值等信息,并智能识别字段类型。
"""
import os
import sys
import logging
import re
import json
from django.db import connection
from config.env import DATABASE_PREFIX
# ============================================================
# Django环境初始化
# ============================================================
# 获取当前脚本所在目录的绝对路径,作为项目根目录
project_root = os.path.dirname(os.path.abspath(__file__))
# 将项目根目录添加到Python模块搜索路径中,确保可以导入项目内的模块
if project_root not in sys.path:
sys.path.insert(0, project_root)
# 设置Django项目配置文件的环境变量
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'application.settings')
# 初始化Django框架
try:
import django
django.setup() # 加载Django配置,初始化应用
print("Django环境初始化成功")
except Exception as e:
print(f"Django环境初始化失败: {str(e)}")
print(" 请确保:")
print(" 1. 已激活虚拟环境")
print(" 2. 已安装所有依赖包")
print(" 3. application/settings.py 文件存在")
sys.exit(1) # 初始化失败时退出程序
class TableSchemaParser:
"""
数据库表结构解析器
=============================================================
从数据库中读取表结构,自动生成代码生成器配置。
功能特性:
- 自动识别字段类型(CharField、IntegerField、DateTimeField等)
- 自动识别图片字段(avatar、cover、logo、image等)
- 自动识别富文本字段(TextField且注释包含关键词)
- 自动识别状态字段(status、state、is_active等)
- 自动识别排序字段(sort、order、seq等)
- 自动识别唯一编码字段(code、sn、no等)
- 自动为常用字段设置查询条件(name、title、code、mobile、email等)
- 自动为选项字段设置查询条件(type、category、status等)
- 自动识别字段是否有数据库索引,并设置为查询条件
- 自动生成字段注释和用途说明
"""
def __init__(self, table_name):
"""
初始化表结构解析器
Args:
table_name: 数据库表名(可以带前缀或不带前缀)
"""
# 保存原始表名(用于数据库查询,可能带前缀)
self.raw_table_name = table_name
# 处理后的表名(用于生成代码,不带前缀)
# 如果传入的是带前缀的表名,移除前缀
if table_name.startswith(DATABASE_PREFIX):
self.table_name = table_name[len(DATABASE_PREFIX):]
else:
self.table_name = table_name
print(f"原始表名: {self.raw_table_name}, 处理后表名: {self.table_name}")
# 排除的系统字段(这些字段在代码生成时会被跳过)
self.exclude_fields = ['id', 'create_user', 'create_time', 'update_user', 'update_time', 'is_delete']
# 图片字段关键词(包含这些关键词的字段会被识别为图片字段)
self.image_keywords = ['avatar', 'cover', 'logo', 'image', 'photo', 'picture', 'img', 'icon', 'thumbnail',
'banner']
# 富文本字段关键词(包含这些关键词的字段会被识别为富文本编辑器)
self.rich_text_keywords = ['内容', '介绍', '说明', '描述', '详情', '正文']
# 状态字段关键词
self.status_keywords = ['status', 'state', 'is_active', 'is_enabled', 'is_deleted']
# 排序字段关键词
self.sort_keywords = ['sort', 'order', 'seq', 'sequence', 'priority']
# 唯一编码字段关键词
self.code_keywords = ['code', 'sn', 'no', 'serial', 'number', 'unique_code']
# 需要设置为查询条件的常用字段关键词
self.filter_keywords = [
'name', 'title', 'code', 'sn', 'no', # 名称/编码类
'mobile', 'phone', 'tel', 'email', # 联系方式类
'username', 'nickname', 'realname', # 用户信息类
'type', 'category', 'kind', 'class', # 类型分类类
'status', 'state', 'level', 'grade', # 状态等级类
'dept_id', 'department_id', # 部门类
'role_id', 'position_id', 'level_id', # 关联ID类
'province_code', 'city_code', 'district_code', # 地区代码类
'gender', 'sex', # 性别类
]
def parse(self):
"""
解析表结构,生成配置字典
Returns:
dict: 代码生成器配置字典
"""
try:
# 步骤1:获取表信息(注释、引擎等)
table_info = self._get_table_info()
print(f"表信息: {table_info}")
# 步骤2:获取字段列表
columns = self._get_columns()
print(f"获取到 {len(columns)} 个字段")
for col in columns:
print(f" - {col['COLUMN_NAME']}: {col.get('COLUMN_COMMENT', '无注释')}")
# 步骤3:获取字段索引信息
indexes = self._get_indexes()
print(f"索引字段: {indexes}")
# 步骤4:处理表名,移除前缀(用于生成模块名)
table_name_cleaned = self._clean_table_name()
print(f"清理后表名: {table_name_cleaned}")
# 步骤5:构建应用名称和模块名称
app_name = table_name_cleaned.lower() # 应用名使用小写
module_comment = self._get_module_comment(table_info) # 模块中文注释
model_class_name = self._to_pascal_case(table_name_cleaned) # 模型类名(帕斯卡命名)
# ========== 新增:生成URL路由前缀 ==========
# 将应用名中的下划线转换为斜杠,生成更友好的URL路径
# 例如:order_item -> order/item
# 单次词(不含下划线)保持不变
route_prefix = app_name.replace('_', '/')
# 记录转换结果,便于调试
if '_' in app_name:
print(f"🔧 URL路由转换: {app_name} -> {route_prefix}")
# ========== URL前缀生成结束 ==========
# 步骤6:构建字段列表
fields = self._build_fields(columns, indexes)
print(f"构建后字段数量: {len(fields)}")
for f in fields:
print(f" - {f['name']}: {f['comment']} (is_rich_text: {f.get('is_rich_text', False)})")
# 如果字段为空,返回错误
if not fields:
print("警告:没有找到任何字段,请检查表结构")
# 步骤7:判断功能标识(根据实际字段判断)
has_sort = self._has_field_by_keywords(columns, self.sort_keywords) # 是否有排序字段
has_status = self._has_field_by_keywords(columns, self.status_keywords) # 是否有状态字段
has_unique_code = self._has_field_by_keywords(columns, self.code_keywords) # 是否有唯一编码字段
# 全局标识
has_image_field = any(f.get('is_image', False) for f in fields) # 是否有图片字段
has_rich_text_field = any(f.get('is_rich_text', False) for f in fields) # 是否有富文本字段
# 步骤8:获取显示字段(优先使用name,否则使用第一个字符串字段)
display_field = self._get_display_field(columns)
# 步骤9:构建完整配置
config = {
# 基础信息
'app_name': app_name, # 应用名称
'module_comment': module_comment, # 模块中文名
'model_name': app_name, # 模型名称
'model_class_name': model_class_name, # 模型类名
'route_prefix': route_prefix, # URL路由前缀(用于后端路由和前端API)
# 业务描述
'business_description': f'标识系统中的{module_comment}数据', # 业务描述
'business_description_detail': f'包含{", ".join([f["comment"] for f in fields[:3]])}等属性' if fields else f'包含{module_comment}相关属性',
# 详细描述
# 权限和标识
'permission_prefix': f'sys:{app_name}', # 权限前缀
'primary_key': 'id', # 主键字段
'display_field': display_field, # 显示字段
'display_field_camel': self._to_camel_case(display_field), # 显示字段
# 表信息
'table_name': self.table_name, # 数据表名
'table_prefix': self._get_table_prefixes(), # 表前缀
'remove_prefix': True, # 是否移除前缀
# 菜单配置
'menu_group': 'tool', # 菜单分组
'parent_menu_id': 322, # 父级菜单ID
'menu_sort': 5, # 菜单排序
# 功能标识
'has_sort': has_sort, # 是否有排序功能
'has_status': has_status, # 是否有状态功能
'has_unique_code': has_unique_code, # 是否有唯一编码功能
'has_export': False, # 是否支持导出
'has_image_field': has_image_field, # 是否有图片字段
'has_rich_text_field': has_rich_text_field, # 是否有富文本字段
# 字段列表
'fields': fields, # 字段配置列表
}
return config
except Exception as e:
logging.error(f"解析表结构失败: {str(e)}")
raise
def _get_table_info(self):
"""
获取表的元数据信息(注释、引擎、行数等)
Returns:
dict: 表信息字典,包含 TABLE_NAME, TABLE_COMMENT, ENGINE, TABLE_ROWS, CREATE_TIME, UPDATE_TIME
"""
# 查询 information_schema 获取表信息
sql = """
SELECT
TABLE_NAME,
TABLE_COMMENT,
ENGINE,
TABLE_ROWS,
CREATE_TIME,
UPDATE_TIME
FROM
information_schema.TABLES
WHERE
TABLE_SCHEMA = (SELECT DATABASE())
AND TABLE_NAME = %s
"""
with connection.cursor() as cursor:
# 使用原始表名(可能带前缀)
cursor.execute(sql, [self.raw_table_name])
# 获取列名
columns = [col[0] for col in cursor.description]
row = cursor.fetchone()
if row:
# 将查询结果转换为字典格式
return dict(zip(columns, row))
return {}
def _get_columns(self):
"""
获取表的字段列表信息
Returns:
list: 字段信息列表,每个字段包含 COLUMN_NAME, COLUMN_COMMENT, DATA_TYPE 等
"""
# 查询 information_schema 获取字段信息
sql = """
SELECT
COLUMN_NAME,
COLUMN_COMMENT,
DATA_TYPE,
COLUMN_TYPE,
IS_NULLABLE,
COLUMN_DEFAULT,
EXTRA,
CHARACTER_MAXIMUM_LENGTH
FROM
information_schema.COLUMNS
WHERE
TABLE_SCHEMA = (SELECT DATABASE())
AND TABLE_NAME = %s
ORDER BY
ORDINAL_POSITION
"""
columns = []
with connection.cursor() as cursor:
# 使用原始表名(可能带前缀)
cursor.execute(sql, [self.raw_table_name])
# 获取列名
col_names = [col[0] for col in cursor.description]
rows = cursor.fetchall()
print(f"查询到 {len(rows)} 行数据") # 调试信息
for row in rows:
column = dict(zip(col_names, row))
# 调试:打印每个字段
print(f"字段: {column['COLUMN_NAME']}, 是否排除: {column['COLUMN_NAME'] in self.exclude_fields}")
# 排除系统字段(如 id, create_time 等)
if column['COLUMN_NAME'] not in self.exclude_fields:
columns.append(column)
else:
print(f" 排除系统字段: {column['COLUMN_NAME']}")
return columns
def _get_indexes(self):
"""
获取表的索引信息
Returns:
set: 有索引的字段名集合
"""
# 查询 information_schema 获取索引信息
sql = """
SELECT DISTINCT
COLUMN_NAME
FROM
information_schema.STATISTICS
WHERE
TABLE_SCHEMA = (SELECT DATABASE())
AND TABLE_NAME = %s
"""
indexed_columns = set()
with connection.cursor() as cursor:
# 使用原始表名(可能带前缀)
cursor.execute(sql, [self.raw_table_name])
rows = cursor.fetchall()
for row in rows:
indexed_columns.add(row[0])
return indexed_columns
def _clean_table_name(self):
"""
清理表名,移除前缀(用于生成应用名和模型名)
Returns:
str: 清理后的表名
"""
table_name = self.table_name
# 常见表名前缀列表
prefixes = ['sys_', 'tb_', 'django_', 'auth_', 'captcha_']
for prefix in prefixes:
if table_name.startswith(prefix):
table_name = table_name[len(prefix):]
break
return table_name
def _get_table_prefixes(self):
"""
获取可能的数据表前缀列表
Returns:
list: 前缀列表
"""
from config.env import DATABASE_PREFIX
return [DATABASE_PREFIX]
def _get_module_comment(self, table_info):
"""
获取模块中文注释
Args:
table_info: 表信息字典
Returns:
str: 模块中文注释
"""
# 优先使用表的注释
if table_info.get('TABLE_COMMENT'):
comment = table_info['TABLE_COMMENT']
# 移除"表"字,如果存在
if comment.endswith('表'):
comment = comment[:-1]
# 去除首尾空格
comment = comment.strip()
if comment:
return comment
# 否则从表名生成(将下划线分隔的英文名转换为中文式命名)
table_name_cleaned = self._clean_table_name()
name_parts = table_name_cleaned.split('_')
chinese_name = ''.join([part.title() for part in name_parts])
return chinese_name
def _get_field_type(self, column):
"""
根据数据库字段类型映射到Django字段类型
Args:
column: 字段信息字典
Returns:
str: Django字段类型
"""
data_type = column['DATA_TYPE'].lower()
column_name = column['COLUMN_NAME'].lower()
# 数据库类型到Django字段类型的映射表
type_mapping = {
'varchar': 'CharField',
'char': 'CharField',
'text': 'TextField',
'longtext': 'TextField',
'int': 'IntegerField',
'tinyint': 'IntegerField',
'smallint': 'IntegerField',
'bigint': 'IntegerField',
'datetime': 'DateTimeField',
'timestamp': 'DateTimeField',
'date': 'DateField',
'float': 'FloatField',
'double': 'FloatField',
'decimal': 'DecimalField',
'boolean': 'BooleanField',
}
return type_mapping.get(data_type, 'CharField')
def _get_field_type_display(self, field_type):
"""
获取字段类型的显示名称
Args:
field_type: Django字段类型
Returns:
str: 类型显示名称(中文)
"""
display_mapping = {
'CharField': '字符串',
'TextField': '长文本',
'IntegerField': '整数',
'DateTimeField': '日期时间',
'DateField': '日期',
'FloatField': '浮点数',
'DecimalField': '小数',
'BooleanField': '布尔值',
}
return display_mapping.get(field_type, '字符串')
def _get_max_length(self, column):
"""
获取字段最大长度
Args:
column: 字段信息字典
Returns:
int/None: 最大长度,如果字段类型不支持长度则返回 None
"""
if column['DATA_TYPE'].lower() in ['varchar', 'char']:
return column.get('CHARACTER_MAXIMUM_LENGTH')
return None
def _is_nullable(self, column):
"""
判断字段是否可为空
Args:
column: 字段信息字典
Returns:
bool: 是否可为空(YES 表示可为空)
"""
return column['IS_NULLABLE'] == 'YES'
def _get_default_value(self, column):
"""
获取字段默认值
Args:
column: 字段信息字典
Returns:
any: 默认值,处理掉字符串默认值的引号
"""
default = column.get('COLUMN_DEFAULT')
if default is None:
return None
# 处理字符串默认值(MySQL 返回的字符串默认值带单引号)
if isinstance(default, str) and default.startswith("'") and default.endswith("'"):
default = default[1:-1]
return default
def _is_image_field(self, column_name):
"""
判断是否为图片字段
Args:
column_name: 字段名
Returns:
bool: 是否为图片字段
"""
return any(img in column_name.lower() for img in self.image_keywords)
def _get_choices(self, column):
"""
获取字段的选项列表(从字段注释中解析)
同时返回 min_value 和 max_value
示例注释格式:'0-正常 1-下架' 或 '1:正常 2:停用'
Args:
column: 字段信息字典
Returns:
tuple: (choices列表, min_value, max_value)
"""
comment = column.get('COLUMN_COMMENT', '')
# 匹配 "0-正常 1-下架" 或 "1:正常 2:停用" 格式
pattern = r'(\d+)[:\-](\S+)'
matches = re.findall(pattern, comment)
if matches:
choices = []
values = []
for match in matches:
try:
value = int(match[0]) # 尝试转换为整数
except ValueError:
value = match[0] # 转换失败则保留字符串
values.append(value)
choices.append((value, match[1]))
# 计算取值范围(仅对数值类型有效)
if values:
min_value = min(values)
max_value = max(values)
return choices, min_value, max_value
return choices, None, None
return None, None, None
def _should_filterable(self, column, indexed_columns):
"""
判断字段是否应该作为查询条件
规则:
1. 有数据库索引的字段
2. 在 filter_keywords 中的常用字段
3. 有选项(choices)的整数字段
4. 状态字段
5. 编码字段
Args:
column: 字段信息字典
indexed_columns: 有索引的字段名集合
Returns:
bool: 是否作为查询条件
"""
column_name = column['COLUMN_NAME'].lower()
field_type = self._get_field_type(column)
choices, _, _ = self._get_choices(column)
has_choices = choices is not None
# 规则1:有数据库索引的字段
if column_name in indexed_columns:
return True
# 规则2:在 filter_keywords 中的常用字段
for keyword in self.filter_keywords:
if keyword in column_name or column_name in self.filter_keywords:
return True
# 规则3:有选项(choices)的整数字段
if field_type == 'IntegerField' and has_choices:
return True
# 规则4:状态字段
for keyword in self.status_keywords:
if keyword in column_name:
return True
# 规则5:编码字段
for keyword in self.code_keywords:
if keyword in column_name:
return True
return False
def _get_filter_type(self, column):
"""
获取查询条件的筛选类型
Args:
column: 字段信息字典
Returns:
str: 筛选类型(模糊查询/精确匹配)
"""
field_type = self._get_field_type(column)
column_name = column['COLUMN_NAME'].lower()
choices, _, _ = self._get_choices(column)
has_choices = choices is not None
# 字符串类型且不是编码字段,使用模糊查询
if field_type == 'CharField':
# 编码字段使用精确匹配
for keyword in self.code_keywords:
if keyword in column_name:
return '精确匹配'
return '模糊查询'
# 整数字段,有选项或状态字段,使用精确匹配
if field_type == 'IntegerField':
if has_choices:
return '精确匹配'
for keyword in self.status_keywords:
if keyword in column_name:
return '精确匹配'
# 其他类型默认精确匹配
return '精确匹配'
def _has_field_by_keywords(self, columns, keywords):
"""
判断表中是否存在包含关键词的字段
Args:
columns: 字段列表
keywords: 关键词列表
Returns:
bool: 是否存在
"""
for col in columns:
column_name = col['COLUMN_NAME'].lower()
for keyword in keywords:
if keyword in column_name or column_name == keyword:
return True
return False
def _get_display_field(self, columns):
"""
获取显示字段(优先使用 name,否则使用第一个字符串字段)
Args:
columns: 字段列表
Returns:
str: 显示字段名
"""
# 优先使用 name 字段
for col in columns:
if col['COLUMN_NAME'].lower() == 'name':
return 'name'
# 其次使用 title 字段
for col in columns:
if col['COLUMN_NAME'].lower() == 'title':
return 'title'
# 然后使用第一个字符串字段
for col in columns:
if self._get_field_type(col) == 'CharField':
return col['COLUMN_NAME']
# 默认使用第一个字段
if columns:
return columns[0]['COLUMN_NAME']
return 'id'
def _build_fields(self, columns, indexed_columns):
"""
构建字段列表配置
Args:
columns: 字段列表
indexed_columns: 有索引的字段名集合
Returns:
list: 字段配置列表
"""
fields = []
for col in columns:
column_name = col['COLUMN_NAME']
field_type = self._get_field_type(col)
is_image = self._is_image_field(column_name) # 是否为图片字段
is_status = self._is_status_field(column_name) # 是否为状态字段
is_sort = self._is_sort_field(column_name) # 是否为排序字段
# 判断是否为富文本字段(TextField 且注释包含关键词)
is_rich_text = False
if field_type == 'TextField':
comment = col.get('COLUMN_COMMENT', '')
for keyword in self.rich_text_keywords:
if keyword in comment:
is_rich_text = True
break
# 判断是否作为查询条件
is_filterable = self._should_filterable(col, indexed_columns)
# 获取字段注释(如果没有注释则使用字段名)
comment = col.get('COLUMN_COMMENT', column_name.title())
# 为特殊字段补充注释
if is_image and not comment:
comment = f'{column_name}图片'
elif is_status and not comment:
comment = f'{column_name}状态'
elif is_sort and not comment:
comment = f'{column_name}排序'
# 获取选项和取值范围
choices, min_value, max_value = self._get_choices(col)
# 构建字段配置字典
field_config = {
'name': column_name, # 字段名
'comment': comment, # 字段注释
'purpose': f'存储{comment}信息', # 字段用途说明
'type': field_type, # Django字段类型
'type_display': self._get_field_type_display(field_type), # 类型显示名称
'max_length': self._get_max_length(col), # 最大长度
'nullable': self._is_nullable(col), # 是否可为空
'required': not self._is_nullable(col), # 是否必填
'is_unique': col.get('EXTRA') == 'unique' or False, # 是否唯一
'db_index': column_name in indexed_columns, # 是否有数据库索引
'default': self._get_default_value(col), # 默认值
'in_form': True, # 是否在表单中显示
'in_list': True, # 是否在列表中显示
'filterable': is_filterable, # 是否可作为查询条件
'filter_type': self._get_filter_type(col) if is_filterable else '', # 筛选类型
'editable': True, # 是否可编辑
'searchable': field_type == 'CharField' and not is_image and is_filterable, # 是否可搜索
'is_image': is_image, # 是否为图片字段
'is_rich_text': is_rich_text, # 是否为富文本字段
}
# 添加选项(如果有)
if choices:
field_config['choices'] = choices
# 有选项时,筛选类型为精确匹配
field_config['filter_type'] = '精确匹配'
# 添加 min_value 和 max_value(仅对整数字段)
if field_type == 'IntegerField' and min_value is not None and max_value is not None:
field_config['min_value'] = min_value
field_config['max_value'] = max_value
# 图片字段特殊处理:不支持筛选和搜索
if is_image:
field_config['filterable'] = False
field_config['searchable'] = False
# 排序字段特殊处理:不支持筛选和搜索
if is_sort:
field_config['filterable'] = False
field_config['searchable'] = False
fields.append(field_config)
return fields
def _is_status_field(self, column_name):
"""
判断是否为状态字段
Args:
column_name: 字段名
Returns:
bool: 是否为状态字段
"""
return any(keyword in column_name.lower() for keyword in self.status_keywords)
def _is_sort_field(self, column_name):
"""
判断是否为排序字段
Args:
column_name: 字段名
Returns:
bool: 是否为排序字段
"""
return any(keyword in column_name.lower() for keyword in self.sort_keywords)
def _to_camel_case(self, snake_str):
"""
将下划线命名转换为驼峰命名
例如:'user_name' -> 'userName'
Args:
snake_str: 下划线命名字符串
Returns:
str: 驼峰命名字符串
"""
if not snake_str: # 处理 None 和空字符串
return snake_str
# 确保是字符串类型
snake_str = str(snake_str)
components = snake_str.split('_')
return components[0] + ''.join(x.title() for x in components[1:])
def _to_pascal_case(self, snake_str):
"""
将下划线命名转换为帕斯卡命名(首字母大写)
示例:user_info -> UserInfo
Args:
snake_str: 下划线命名字符串
Returns:
str: 帕斯卡命名字符串
"""
components = snake_str.split('_')
return ''.join(x.title() for x in components)
# ============================================================================
# 代码生成器集成
# ============================================================================
def generate_config_from_table(table_name):
"""
从数据库表生成代码生成器配置
Args:
table_name: 数据库表名
Returns:
dict: 代码生成器配置
"""
parser = TableSchemaParser(table_name)
config = parser.parse()
return config
def run_code_generator(config):
"""
运行代码生成器
Args:
config: 代码生成器配置字典
Returns:
bool: 是否成功
"""
try:
# 动态导入 CodeGenerator 类
from generator_config import CodeGenerator
# 创建代码生成器实例
generator = CodeGenerator(config)
# 执行代码生成
generator.generate()
return True
except ImportError as e:
print(f"导入 CodeGenerator 失败: {str(e)}")
print(" 请确保 generator_config.py 文件存在")
return False
except Exception as e:
print(f"代码生成失败: {str(e)}")
import traceback
traceback.print_exc()
return False
# ============================================================================
# 主程序入口
# ============================================================================
if __name__ == '__main__':
import argparse
# 解析命令行参数
parser = argparse.ArgumentParser(description='数据库表结构解析和代码生成工具')
parser.add_argument('table_name', nargs='?', help='数据库表名')
parser.add_argument('--config', '-c', help='使用已有的配置文件(JSON格式)')
parser.add_argument('--dry-run', '-d', action='store_true', help='仅生成配置,不执行代码生成')
parser.add_argument('--output', '-o', help='输出配置文件路径')
args = parser.parse_args()
print("=" * 60)
print("数据库表结构解析和代码生成工具")
print("=" * 60)
try:
# 步骤1:获取配置
if args.config:
# 从配置文件加载
print(f"从配置文件加载: {args.config}")
with open(args.config, 'r', encoding='utf-8') as f:
config = json.load(f)
print("配置文件加载成功")
elif args.table_name:
# 从数据库表解析
print(f" 正在解析表: {args.table_name}")
config = generate_config_from_table(args.table_name)
print("表结构解析成功")
# 输出配置(如果需要)
if args.output:
with open(args.output, 'w', encoding='utf-8') as f:
json.dump(config, f, ensure_ascii=False, indent=2)
print(f"📄 配置已保存到: {args.output}")
# 打印配置摘要
print("\n" + "-" * 60)
print("生成配置摘要:")
print("-" * 60)
print(f" 应用名称: {config['app_name']}")
print(f" 模块名称: {config['module_comment']}")
print(f" 模型类名: {config['model_class_name']}")
print(f" 字段数量: {len(config['fields'])}")
print(f" 是否有排序: {config['has_sort']}")
print(f" 是否有状态: {config['has_status']}")
print(f" 是否有图片: {config.get('has_image_field', False)}")
print(f" 是否有富文本: {config.get('has_rich_text_field', False)}")
# 显示字段列表(仅显示前5个)
print("\n字段列表:")
for field in config['fields'][:5]:
print(f" - {field['name']}: {field['comment']} ({field['type']})")
if field.get('choices'):
print(f" 选项: {field['choices']}")
if field.get('min_value') is not None:
print(f" 取值范围: {field.get('min_value')} ~ {field.get('max_value')}")
if len(config['fields']) > 5:
print(f" ... 还有 {len(config['fields']) - 5} 个字段")
else:
parser.print_help()
sys.exit(1)
# 步骤2:执行代码生成
if not args.dry_run:
print("\n" + "=" * 60)
print("开始执行代码生成")
print("=" * 60)
success = run_code_generator(config)
if success:
print("\n代码生成完成!")
else:
print("\n 代码生成失败!")
sys.exit(1)
else:
print("\n" + "=" * 60)
print("预览模式 - 不执行代码生成")
print("=" * 60)
print("如需生成代码,请去掉 --dry-run 参数")
# 询问是否显示完整配置
show_config = input("\n是否显示完整配置?(y/n): ").strip().lower()
if show_config == 'y':
print("\n" + json.dumps(config, ensure_ascii=False, indent=2))
print("\n" + "=" * 60)
print("操作完成!")
print("=" * 60)
except Exception as e:
print(f"\n 操作失败: {str(e)}")
import traceback
traceback.print_exc()
sys.exit(1)
"""
# ============================================================================
# 命令行执行示例
# ============================================================================
# 方式1:从数据库表生成配置并执行代码生成(解析表结构 → 生成配置 → 生成代码 → 提示迁移和菜单)
python generator.py django_example
# 方式2:只生成配置(不执行代码生成)
# 2.1 只生成配置,输出到控制台,不保存文件
python generator.py django_example --dry-run
# 2.2 只生成配置,并保存为 JSON 文件
python generator.py django_example --dry-run --output config.json
# 方式3:使用已有配置文件生成代码(跳过表解析,直接使用配置文件)
python generator.py --config config.json
# 方式4:执行代码生成并保存配置文件(生成配置 → 保存文件 → 生成代码)
python generator.py django_example --output config.json
"""