一、概述
CRUD、参数校验、权限控制、菜单注册、前端页面,这些基本是机械重复的活。代码生成器(Code Generator)就是把这部分从手写里解放出来。
整个流程是:数据库元数据逆向 → 语义推断 → 配置持久化 → 模板渲染 → 全栈交付。一张物理表进去,后端五层接口加 Vue3 页面就出来了。
css
flowchart LR
A[物理数据库表\nMySQL / Postgres] -->|元数据逆向| B[元数据捕获层\nGenDBTable]
B -->|智能推断与初始化| C[生成配置中心\nGenTable & GenTableColumn]
C -->|开发者可视化微调| D[配置持久化\nORM元数据库]
D -->|Jinja2 异步模板引擎| E[全栈代码生成]
E --> F1[后端五层代码\nModel/Schema/CRUD/Service/Controller]
E --> F2[前端交互代码\nVue3 SFC + TS API]
E --> F3[权限与菜单 SQL\nRBAC 体系]
设计要点
- 字段名、注释、物理类型直接推断前端控件(Radio、Select、Upload、Editor)、查询方式(EQ、LIKE、BETWEEN)和类型映射,不用手动配。
- 生成的后端代码就是标准五层(Model → Schema → CRUD → Service → Controller),前端走组合式 API 规范。
- 支持主子表(Master-Detail)外键关联,级联保存、联合事务、嵌套 Schema、主从联动前端组件都会自动生成。
- 数据库字段变了可以做差异同步(DB Sync):先预览差异,增量补新字段,同时保留页面上已配置的业务属性。
- 三种交付方式:直接写进本地工程、批量打包 ZIP 下载、在线高亮预览。
二、模块划分与目录结构
代码生成模块的结构:
bash
backend/app/api/v1/module_generator/gencode/
├── __init__.py
├── controller.py # RESTful API 控制器(导入、预览、生成、同步等)
├── service.py # 核心编排服务(元数据抓取、文件 IO、ZIP 压缩、DB 同步)
├── crud.py # 元数据表 (gen_table/gen_table_column) 的持久层操作
├── model.py # 元数据持久化 ORM 模型定义
├── schema.py # Pydantic V2 请求/响应数据校验模型
├── gen_util.py # 智能语义推断工具类(字段类型、UI控件、查询条件推断)
└── jinja2_template_util.py # Jinja2 模板引擎封装、上下文构建与自定义过滤器
backend/templates/ # 统一代码生成模板库
├── python/ # 后端 FastAPI 模板
│ ├── __init__.py.jinja2
│ ├── model.py.jinja2 # SQLAlchemy ORM 模型模板
│ ├── schema.py.jinja2 # Pydantic 校验模型模板
│ ├── crud.py.jinja2 # 基础持久化层模板
│ ├── service.py.jinja2 # 业务逻辑与事务编排模板
│ └── controller.py.jinja2 # HTTP 路由与权限切面模板
├── ts/
│ └── api.ts.jinja2 # 前端 TypeScript 接口与请求定义
└── vue/
└── index.vue.jinja2 # Vue3 + Element Plus 全功能表格/表单页面
三、实现环节拆解
3.1 元数据逆向(Metadata Reverse Engineering)
先拿物理表的元数据。GenTableService 对不同数据库(MySQL、PostgreSQL 等)走 information_schema 或 SQL 驱动反查表与列信息。
rust
sequenceDiagram
autonumber
actor Dev as 开发者 / 前端
participant Ctrl as GenRouter (/gencode/import)
participant Svc as GenTableService
participant CRUD as GenTableCRUD
participant DB as 业务数据库 (information_schema)
Dev->>Ctrl: 提交待导入表名列表 [table_names]
Ctrl->>Svc: import_gen_table()
Svc->>DB: 查询表结构 (table_name, comment)
Svc->>DB: 查询列元数据 (column_name, data_type, is_nullable, column_key...)
DB-->>Svc: 返回物理表与列的原始元数据
Svc->>Svc: 执行 GenUtils.init_table() 与 init_column_field() 推断
Svc->>CRUD: 持久化至 gen_table 与 gen_table_column
CRUD-->>Ctrl: 返回导入结果
Ctrl-->>Dev: 提示导入成功
列信息里提取:列名、物理类型(column_type)、长度、默认值、是否主键(is_pk)、是否自增(is_increment)、是否可空(is_nullable)。
3.2 语义推断(Rule Inference Engine)
原始字段不能直接对应上层需要的维度:Python 类型、TS 类型、表单控件、列表是否显示、查询方式等。这些靠 gen_util.py 里的规则推断。
1. 模块与包路径推断
当导入一张表(如 tb_order_detail 或 gen_strategy_config)时:
- 剥离前缀 :去掉
tb_、gen_等前缀。 - 实体类名转驼峰 :
tb_order_detail\rightarrowOrderDetail。 - 模块与路径 :推断出
module_name = "order_detail"、package_name = "module_order_detail"。
2. 数据类型三重映射
物理数据库类型 → Python/SQLAlchemy 类型 → TypeScript 类型的映射:
| 物理数据库类型(DB Type) | Python 类型 (Pydantic) | SQLAlchemy 类型 | TypeScript 类型 |
|---|---|---|---|
int, tinyint, bigint |
int |
Integer / BigInteger |
number |
float, double, decimal |
float / Decimal |
Float / Numeric |
number |
varchar, char, text |
str |
String(length) / Text |
string |
datetime, timestamp |
datetime |
DateTime |
string |
date |
date |
Date |
string |
boolean, tinyint(1) |
bool |
Boolean |
boolean |
json, jsonb |
dict / list |
JSON |
Record<string, any> |
3. 字段名与 UI 控件
按命名惯例 + 类型特征推断控件:
bash
# gen_util.py 核心推断逻辑片段
lower_name = column_name.lower()
if lower_name.endswith("status"):
column.html_type = GenConstant.HTML_RADIO # 状态类 -> 单选框组 (Radio)
elif lower_name.endswith("type") or lower_name.endswith("sex"):
column.html_type = GenConstant.HTML_SELECT # 类型/性别类 -> 下拉选择框 (Select)
elif lower_name.endswith("image") or lower_name.endswith("avatar"):
column.html_type = GenConstant.HTML_IMAGE_UPLOAD # 图片类 -> 图片上传控件
elif lower_name.endswith("file") or lower_name.endswith("url"):
column.html_type = GenConstant.HTML_FILE_UPLOAD # 文件类 -> 文件上传控件
elif lower_name.endswith("content") or lower_name.endswith("description"):
column.html_type = GenConstant.HTML_EDITOR # 内容/描述类 -> 富文本编辑器 (Editor)
elif cls.arrays_contains(GenConstant.COLUMNTYPE_TIME, data_type):
column.html_type = GenConstant.HTML_DATETIME # 时间类 -> 日期时间选择器 (DatePicker)
4. CRUD 行为与查询方式推断
- 审计字段黑名单 :主键(
id)和系统基类字段(created_time、updated_time、created_id、deleted_time等)在新增、编辑、列表里默认屏蔽或只读。 - 查询算子 :带
name、title或字符串类型的默认LIKE;数值、状态类默认EQ;日期时间类默认BETWEEN。
3.3 Jinja2 异步模板渲染
渲染引擎封装在 Jinja2TemplateUtil,开了 Jinja2 异步(enable_async=True),并发生成时不会阻塞事件循环。
1. 模板环境与自定义过滤器
ini
cls._env = Environment(
loader=FileSystemLoader(TEMPLATE_DIR),
autoescape=False,
trim_blocks=True, # 消除模板标签产生的空行
lstrip_blocks=True, # 消除行首多余空白
keep_trailing_newline=True,# 保留标准 POSIX 文件结尾换行
enable_async=True, # 开启原生异步渲染支持
)
cls._env.filters.update({
"camel_to_snake": SnakeCaseUtil.camel_to_snake,
"snake_to_camel": CamelCaseUtil.snake_to_camel,
"get_sqlalchemy_type": cls.get_sqlalchemy_type,
"python_to_ts_type": cls.python_type_to_ts_type,
})
2. 上下文变量(Context)
调 env.get_template().render_async(**context) 之前,先组装好上下文数据字典:
table:当前表的元数据配置对象。columns:排好序的所有列对象列表。pk_column/pk_python_type:主键列信息。has_datetime/has_decimal:控制要不要生成datetime、Decimal的导入语句。permission_prefix:按模块和业务名算出的权限前缀(如order:detail)。sub_table/sub_columns:配置了主子表才有,子表关联上下文。
3.4 模板设计
1. 模型模板(model.py.jinja2)
用 SQLAlchemy 2.0 的 Mapped / mapped_column 强类型注解,支持主子表 relationship 和外键级联:
python
class {{ class_name }}Model(ModelMixin, UserMixin):
"""{{ function_name }}表"""
__tablename__: str = '{{ table_name }}'
__table_args__: dict[str, str] = {'comment': '{{ function_name }}'}
{% for column in columns %}
{% if column.column_name not in ['id', 'uuid', 'tenant_id', 'created_time', 'updated_time', 'created_id', 'updated_id', 'is_deleted', 'deleted_time', 'deleted_id'] %}
{% set sqlalchemy_type = column|get_sqlalchemy_type %}
{{ column.column_name }}: Mapped[{{ column.python_type }}{% if column.is_nullable %} | None{% endif %}] = mapped_column(
{{ sqlalchemy_type }},
{% if column.is_pk %}primary_key=True, {% endif %}
{% if column.is_increment %}autoincrement=True, {% endif %}
nullable={{ column.is_nullable }},
comment='{{ column.column_comment }}'
)
{% endif %}
{% endfor %}
{% if table.sub %}
{{ sub_rel_list_name }} = relationship('{{ sub_model_class_name }}', back_populates='{{ parent_rel_name }}', cascade='all, delete-orphan')
{% endif %}
2. 控制器模板(controller.py.jinja2)
集成依赖注入、RBAC 权限校验(AuthPermission)、操作日志切面(OperationLogRoute)和统一响应:
ini
{{ class_name }}Router = APIRouter(route_class=OperationLogRoute, prefix="/{{ module_name }}", tags=["{{ function_name }}模块"])
@{{ class_name }}Router.get("/list", summary="分页查询{{ function_name }}", response_model=ResponseSchema[PageResultSchema[{{ class_name }}OutSchema]])
async def get_obj_list_controller(
auth: Annotated[AuthSchema, Depends(AuthPermission(["{{ permission_prefix }}:query"]))],
page: Annotated[PaginationQueryParam, Depends()],
search: Annotated[{{ class_name }}QueryParam, Query()],
db: Annotated[AsyncSession, Depends(db_getter)],
) -> JSONResponse:
service = {{ class_name }}Service(auth, db)
result_dict = await service.page(page_no=page.page_no, page_size=page.page_size, search=search, order_by=page.order_by)
return SuccessResponse(data=result_dict, msg="查询{{ function_name }}列表成功")
3.5 交付与持久化
三种交付方式:
css
graph TD
A[模板渲染完成\nMemory Buffer] --> B{选择交付模式}
B -->|直接生成到项目| C[本地文件系统 I/O\nWrite to backend/ & frontend/]
B -->|下载代码包| D[内存 ZIP 压缩流\nio.BytesIO + StreamingResponse]
B -->|在线代码预览| E[JSON 返回字典结构\nMonaco Editor / Highlight.js]
1. 本地写入(generate_code)
按 package_name、module_name 把渲染结果直接写进后端 API 目录和前端 src/views:
- 创建缺失的中间目录;
- 安全写入
model.py、schema.py、crud.py、service.py、controller.py、api.ts、index.vue; - 路径分隔符自动处理,Windows / Linux 都能跑。
2. ZIP 打包(batch_gen_code)
多人协作或想先审查再合并的场景,用 zipfile.ZipFile 在内存里打包 io.BytesIO(),遍历选中的表分层归档:
scss
zip_buffer = io.BytesIO()
with zipfile.ZipFile(zip_buffer, "w", zipfile.ZIP_DEFLATED) as zip_file:
for template_file, output_path in zip(templates, output_paths):
render_content = await env.get_template(template_file).render_async(**context)
zip_file.writestr(output_path, render_content)
zip_buffer.seek(0)
# 返回流式响应
return bytes2file_response(zip_buffer.getvalue())
3.6 增量同步与差异检测(Diff & Sync)
迭代中数据库经常加列、改长度、删废弃列。如果直接重新生成,页面上配好的表单类型、查询规则、字典设置就全丢了。
所以做了差异比对 + 增量同步:
css
flowchart TD
A[触发库表同步: sync_db] --> B[读取物理数据库当前最新列]
B --> C[读取 gen_table_column 已持久化的配置列]
C --> D[执行集合比对 Diff]
D -->|新增物理列| E[执行 init_column_field 推断并 INSERT]
D -->|已存在列| F[仅更新物理属性 data_type/length/comment\n保留用户配置的 html_type/query_type/dict_type]
D -->|数据库已删除列| G[从 gen_table_column 中 DELETE]
E & F & G --> H[同步完成,更新 gen_table 状态]
差异预览机制(Sync Preview)
正式同步前,先调 /sync_db/preview/{table_name} 拿差异概览(GenSyncPreviewSchema):
added_columns:待新增的字段列表;modified_columns:字段类型/注释/长度发生变化的字段列表;deleted_columns:在物理库中已不存在的冗余字段列表。
四、主子表(Master-Detail)生成
不只支持单表,还支持一对多主子表(比如订单 tb_order + 明细 tb_order_item,或策略表 + 运行日志表)。
4.1 关联配置
GenTableModel 里定义关联元信息:
sub_table_name:关联子表的物理表名。sub_table_fk_name:子表中指向主表主键的外键列名。
4.2 级联代码生成机制
-
主表 ORM :自动生成
relationship(..., cascade='all, delete-orphan')关系引用。 -
子表 ORM :自动在指定的外键列上添加
ForeignKey('parent_table.id', ondelete='CASCADE')。 -
Pydantic Schema:
- 主表 Create/Update Schema 自动嵌套子表 Schema 列表:
items: list[OrderItemCreateSchema] | None = []。
- 主表 Create/Update Schema 自动嵌套子表 Schema 列表:
-
Service 事务层:
create():同一事务里先插主表拿到主键 ID,再给子表批量设置外键落库。update():比对子表主键,存在则更新、不存在则插入、缺失则删除。
-
前端 UI:
- 主表详情/编辑表单里渲染可增删行的子表格。
五、使用建议
5.1 标准流程
markdown
1. 数据库建模规范设计(必须包含主键、注释、状态等标准字段)
↓
2. 在系统界面「代码生成」中点击「导入」,选择业务表
↓
3. 在可视化抽屉中微调:
- 检查模块名(module_name)与业务名(business_name)
- 配置字典绑定(如 status 绑定 sys_normal_disable)
- 设置查询方式(模糊查询 / 精确匹配 / 时间范围)
- 若为主从业务,配置关联子表与外键
↓
4. 点击「代码预览」,检查 Python 与 Vue 代码语法
↓
5. 点击「生成代码」,代码自动同步至本地项目目录
↓
6. 自动/手动执行生成的菜单 SQL,刷新前端即可查看完整功能模块
5.2 字段命名规范推荐表
想让自动推断准一点,建表时按下面这些命名来:
| 字段命名模式 | 推荐物理类型 | 自动推断结果 | 界面呈现形式 |
|---|---|---|---|
*_status |
tinyint / int |
html_type: radio, query_type: EQ |
单选框组(支持绑定字典) |
*_type, *_category |
varchar / int |
html_type: select, query_type: EQ |
下拉选择器 |
*_name, *_title |
varchar |
html_type: input, query_type: LIKE |
文本输入框 + 模糊搜索 |
*_time, *_date |
datetime / date |
html_type: datetime, query_type: BETWEEN |
日期范围选择器 |
*_image, *_avatar |
varchar |
html_type: imageUpload |
图片上传与缩略图展示 |
*_file, *_attachment |
varchar |
html_type: fileUpload |
文件上传与下载链接 |
*_content, *_remark |
text |
html_type: editor / textarea |
富文本编辑器 / 多行文本域 |
六、总结
整体链路就是"表 → 推断 → 配置 → 渲染 → 交付"。字段命名规范点、注释写清楚,生成出来的东西基本不用改;主子表和差异同步,用到的时候翻前面的对应章节就行。