================================================================================ EasyGoAdmin 后端开发者文档 · 07 代码生成器(tool 域 + CLI) ================================================================================ 代码位置:internal/logic/generator.go(核心)、internal/logic/generator_side.go (副作用:菜单/字典/路由)、cmd/generator/main.go(CLI)、templates/(模板) -------------------------------------------------------------------------------- 1. 双入口 -------------------------------------------------------------------------------- HTTP(/api/generator,管理页面“代码生成”): GET /api/generator/page perm sys:generator:page 数据表清单(分页) POST /api/generator/preview perm sys:generator:generator 预览(不落盘) POST /api/generator/generate perm sys:generator:generator 一键生成 POST /api/generator/batchGenerate perm sys:generator:batchGenerator 批量生成 日志 logType=generate(19 代码生成) CLI(仓库根目录执行,读取同一份 config.yaml): go run ./cmd/generator -tables "goframe_order|订单表,goframe_sku|商品规格" go run ./cmd/generator -tables "goframe_order|订单表" -preview # 仅预览清单 go run ./cmd/generator -tables "goframe_order|订单表" -batch # 逐表统计输出 -tables 格式:"表名|表注释"(注释可省略,|后内容被忽略),逗号分隔多表。 CLI 空白 import internal/logic 以注册全部服务,直接调用 service.Generator().Preview/Generate/BatchGenerate。 生成后手动执行 go build ./... 并重启服务。 -------------------------------------------------------------------------------- 2. 表结构内省 -------------------------------------------------------------------------------- MySQL:查 information_schema.TABLES(表注释/引擎/行数/自增/排序规则/创建时间) 非 MySQL:gdb 元数据降级(仅表名,注释留空,模块名回退表名)。 字段元数据:g.DB().TableFields(tableName),按元数据 Index 还原物理列序。 类型映射(buildField,正则按 MySQL 类型串推断): tinyint(1)→BooleanField int 族→IntegerField decimal/numeric→DecimalField float/double→FloatField date→DateField datetime/timestamp→DateTimeField blob/binary→BinaryField text 族→TextField varchar/char/enum→CharField Go 类型:bigint→int64,decimal/float→float64,时间列→*gtime.Time, int 族→int,其余 string;行取值方法 .Int()/.Float64()/.String()。 注释智能解析: 枚举:"状态:1-正常 2-禁用"(reChoice 支持 - — - = 分隔)→ Choices 集合, 标签类型 1→success 2→danger 3→warning 其余 info。 表单标签:cleanLabel 去掉枚举尾巴保留中文名。 图片字段:注释含 图片/头像/封面 或列名含 avatar/cover/image/icon/photo。 富文本:注释含 富文本 或 TextField 且列名含 content/body/editor。 字段集合划分(buildVars): 审计列排除:id 进列表(树形/分页需要 Id);create_user/create_time 仅标记 HasCreateUser/HasCreateTime;update_user/update_time/del_flag/tenant_id 不进 前后端字段;salt 不进表单。 列表字段:排除富文本/password/salt/BinaryField。 表单字段:全部业务列(BinaryField 除外)。 筛选字段:CharField 非图片非 URL 列(website/site/url/link 不作模糊条件); 带枚举的 Integer/Boolean → Select;*_id 外键 Integer → InputNumber。 显示字段(模糊搜索列)优先级:name/title/label/username/realname/nickname/ category_name/job_name/dept_name/role_name/position_name/level_name, 未命中取列表首列。 模块命名:moduleName = 表名去 goframe_ 前缀;Pascal → 结构体类名(ConfigItem); camelLower → 前端模块名(configItem);权限前缀 sys:{camelLower}; 路由前缀 = moduleName(snake,如 /api/config_item 实际以生成模板为准)。 域归属:genDomainMap(user/role/…→system,config/…→data,article/…→content, operation_log/…→monitor,example/…→tool),未匹配默认 tool。 模板类型:有 parent_id/pid 列 → ui2(树形),否则 ui(普通列表)。 排序:有 sort 列 → "sort asc",否则 "id desc"。 -------------------------------------------------------------------------------- 3. 产出文件(renderTable) -------------------------------------------------------------------------------- 后端八件套(Go 产物统一 gofmt,format.Source 失败即报错): api/v1/{module}.go ← templates/api.tpl internal/controller/{module}.go ← templates/controller.tpl internal/logic/{module}.go ← templates/logic.tpl internal/service/{module}.go ← templates/service.tpl internal/dao/{module}.go ← templates/dao_index.tpl(挂钩子+Ctx) internal/dao/internal/{module}.go ← templates/dao_internal.tpl internal/model/entity/{module}.go ← templates/entity.tpl internal/model/do/{module}.go ← templates/do.tpl 前端(模板定界符 [[ ]],规避 Vue 插值冲突): ui 普通:src/api/{domain}/{moduleCamel}.ts、 src/views/{domain}/{moduleCamel}/{index.vue, edit.vue, detail.vue, columns.ts, querySchemas.ts} ui2 树形:src/api/{domain}/{moduleCamel}.ts、 src/views/{domain}/{moduleCamel}/{index.vue, edit.vue, detail.vue} 模板语法:Go text/template;后端模板默认 {{ }};templates/ui* 用 Delims("[[","]])。 模板函数:containsAny、isUrlField(URL 列名判定)。 -------------------------------------------------------------------------------- 4. 落盘保护策略 -------------------------------------------------------------------------------- skipTables(整表跳过文件生成):仅关联表 role_menu、user_role—— 跳过文件写入,但幂等副作用(菜单/字典/路由)照常执行。 writeFile 文件粒度四态: fileWritten 目标不存在 → 新写入(计入 Generated) fileExists 已存在且未开覆盖 → 跳过(计入 Skipped,保留手工代码) fileOverwritten 已存在且开启覆盖 → 覆盖(计入 Overwritten) fileFailed 写入/建目录失败(计入 Failed) 覆盖开关唯一入口:generator.overwrite(config.yaml;true=生成即覆盖, false/未配置=缺失补生成、存在跳过)。 4.1 writeFile 落盘决策图 目标文件存在? / \ 否 是 │ │ 写入 generator.overwrite? Generated / \ true false/缺省 │ │ 覆盖写 跳过保留 Overwritten Skipped \ / 任一失败 → Failed(MkdirAll/WriteFile 错误) 4.2 生成器总体流水线图 输入 tables[]("表名|注释") │ ▼ buildVars:TableFields 内省 → 类型/注释解析 → 字段分桶(列表/表单/筛选) │ 命名:module/Pascal/camelLower;域归属;ui|ui2 模板类型 ▼ skipTables? ──是(role_menu/user_role)──▶ 仅 buildVars(副作用用),跳过文件 │ 否 ▼ renderTable:逐模板渲染(后端 {{ }} / 前端 [[ ]])→ go 产物 gofmt │ ▼ writeFile×N(决策图见 4.1) │ ▼ 副作用(幂等):createMenu(+权限节点) → createDict(枚举字段) → registerRouter │ ▼ 结果报告(Generated/Skipped/Overwritten/Failed + Menu/Dict/Route) │ ▼ 提示:go build ./... 并重启服务 -------------------------------------------------------------------------------- 5. 幂等副作用(generator_side.go) -------------------------------------------------------------------------------- 5.1 菜单 + 权限节点(createMenu) 主菜单:parent_id = generator.parentMenuId(默认 189), path="/{domain}/{moduleCamel}",component=path+"/index", type=0,sort=同级 MAX(sort)+5,note="代码生成器自动创建"。 按 path+type=0 查重 → 已存在直接返回(幂等)。 权限节点(type=1,挂在主菜单下,permission={PermissionPrefix}:{action}): page/list/detail/add/update/delete/batchDelete 固定 7 个; HasStatus 追加 :status;HasImport/HasExport 追加 :import/:export (生成器默认二者均为 true)。sort 逐个 +5。 5.2 字典(createDict) 对每个带枚举注释的表单字段:code = {moduleCamel}_{camelField} (如 user_status),按 code 查重 → 已存在跳过; 否则建 dict(name="{模块中文名}-{字段中文名}",note 自动创建标注)+ 逐 Choice 建 dict_item(name=label,value,sort+5)。 结果逐项返回(已存在/创建成功/失败原因)。 5.3 路由注册(registerRouter) 目标文件 internal/router/router.go: 已含 new(controller.C{ModelClass}) → 返回“路由已存在”(幂等); 在最后一个 "\n\tBind(s, new(" 行之后插入 "\n\tBind(s, new(controller.C{ModelClass})) // 代码生成器注册"。 失败情形(返回消息,不中断):路由文件不存在、缺 controller 导入、 未找到锚点。 6. 生成器模板(templates/ 磁盘)与 file_template 表的区分(易混淆) 两套“模板”互不相干: * templates/*.tpl(磁盘目录):代码生成器的渲染模板,随仓库版本管理, generator.go 直接读取渲染;修改模板即改变生成产物风格。 * goframe_file_template 表 + /api/file/template 模块:业务功能(在线管理 文件/代码片段模板),提供 getTemplateByCode 查询;与代码生成器流水线 无调用关系。 修改生成产物改 templates/*.tpl;不要到 file_template 表里找生成器模板。 -------------------------------------------------------------------------------- 7. 结果报告结构 -------------------------------------------------------------------------------- Generate(一键):Generated[] / Skipped[] / Overwritten[] / Failed[] / Menu{Success,MenuId,Message} / Dict[]{Dict,Name,Items,Status} / Route{Success,Message} BatchGenerate:List[]{Table, Success, Generated, Skipped, Overwritten, Failed, Message, Menu, Dict, Route}(逐表统计;Message="生成 N,跳过 N,覆盖 N,失败 N") Preview:List[]{Path, Content}(渲染结果全文,不落盘)。 CLI 输出按上述分组打印(+ 生成 / - 跳过 / * 覆盖 / ! 失败),并提示 “下一步: go build ./... 并重启服务”。 -------------------------------------------------------------------------------- 8. 与 gf gen dao 的分工 -------------------------------------------------------------------------------- gf gen dao -c hack/config.yaml 表结构变更后刷新已有模块 dao (自定义模板 hack/tpl/dao_index.tpl:挂全局钩子 + Ctx 覆写; overwriteDao:false 保留手工扩展) 生成器 新模块一键产出(dao 四件与 gf gen dao 同构, 生成后统一 gofmt,可直接编译) gf gen service 禁止整体运行(service 接口为手工重组维护) -------------------------------------------------------------------------------- 9. 注意事项 -------------------------------------------------------------------------------- * generator.overwrite=true 会覆盖同名文件(包括手工修改过的)——批量覆盖前务必备份 或临时改回 false。 * 生成的 logic 是 ModuleSpec 基座风格的完整可用实现(含筛选/树形/字典/导入导出), 复杂业务需再手工扩展钩子。 * 新模块若为租户级表,需同步把表名加入 internal/model/hooks 的 tenantTables。 * 菜单创建后需在角色管理中为相关角色勾选权限节点方可访问。 * 非 MySQL 库生成时注释/枚举解析缺失,建议在 MySQL 上生成或人工补注释。