# +====================================================================== # | 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 """