🚀 代码生成器
Gin-Vue-Admin 提供强大的代码生成器,支持根据数据库表结构自动生成完整的 CRUD 代码,包括后端 API、前端页面、路由配置等,大幅提升开发效率。
🎯 功能概述
生成内容
- 后端代码:Model、Service、API、Router
- 前端代码:Vue 页面、API 接口、路由配置
- 数据库:自动建表、字段验证
- 权限配置:API 权限、菜单权限
支持特性
- 🔄 增删改查:完整的 CRUD 操作
- 🔍 条件查询:支持多字段条件筛选
- 📄 分页查询:自动分页处理
- 📁 文件上传:支持文件字段处理
- 🔗 关联查询:支持表关联操作
- 🎨 自定义模板:可自定义代码模板
🛠️ 使用方式
1. Web 界面生成
访问系统管理 → 代码生成器,通过可视化界面配置:
系统工具 → 代码生成器 → 新增
配置步骤
基础信息配置
- 表名称
- 表描述
- 结构体名称
- 包名
- 文件名
字段配置
- 字段名称
- 字段类型
- 数据库类型
- 字段描述
- 是否必填
- 查询条件
- 字典类型
生成选项
- 生成模块
- 生成路径
- 是否覆盖
2. 命令行生成
bash
# 进入项目目录
cd server
# 运行代码生成器
go run main.go -c=config.yaml -gva=gen
3. API 接口生成
bash
# 使用 curl 调用生成接口
curl -X POST "http://localhost:8888/autoCode/createTemp" \
-H "Content-Type: application/json" \
-d '{
"structName": "User",
"tableName": "sys_users",
"packageName": "system",
"fields": [
{
"fieldName": "Name",
"fieldDesc": "用户名",
"fieldType": "string",
"dataType": "varchar(255)",
"fieldJson": "name",
"require": true,
"errorText": "请输入用户名"
}
]
}'
📋 配置详解
基础配置结构
go
type AutoCodeStruct struct {
StructName string `json:"structName"` // 结构体名称
TableName string `json:"tableName"` // 表名
PackageName string `json:"packageName"` // 包名
HumpPackageName string `json:"humpPackageName"` // 驼峰包名
Abbreviation string `json:"abbreviation"` // 缩写
Description string `json:"description"` // 描述
AutoCreateApiToSql bool `json:"autoCreateApiToSql"` // 自动创建API
AutoCreateResource bool `json:"autoCreateResource"` // 自动创建资源
AutoMoveFile bool `json:"autoMoveFile"` // 自动移动文件
Fields []Field `json:"fields"` // 字段列表
DictTypes []string `json:"dictTypes"` // 字典类型
Package string `json:"package"` // 完整包路径
PackageT string `json:"packageT"` // 模板包路径
NeedValid bool `json:"needValid"` // 需要验证
HasTimer bool `json:"hasTimer"` // 包含时间字段
NeedSort bool `json:"needSort"` // 需要排序
HasSearchTimer bool `json:"hasSearchTimer"` // 包含搜索时间
HasFile bool `json:"hasFile"` // 包含文件字段
HasDataSource bool `json:"hasDataSource"` // 包含数据源
HasRichText bool `json:"hasRichText"` // 包含富文本
HasPic bool `json:"hasPic"` // 包含图片
LogicDelete bool `json:"logicDelete"` // 逻辑删除
MenuID string `json:"menuID"` // 菜单ID
}
字段配置结构
go
type Field struct {
FieldName string `json:"fieldName"` // 字段名
FieldDesc string `json:"fieldDesc"` // 字段描述
FieldType string `json:"fieldType"` // Go字段类型
FieldJson string `json:"fieldJson"` // JSON标签
DataType string `json:"dataType"` // 数据库字段类型
ColumnName string `json:"columnName"` // 数据库列名
Comment string `json:"comment"` // 注释
Require bool `json:"require"` // 是否必填
ErrorText string `json:"errorText"` // 错误提示
Clearable bool `json:"clearable"` // 是否可清空
Sort bool `json:"sort"` // 是否排序字段
PrimaryKey bool `json:"primaryKey"` // 是否主键
DefaultValue string `json:"defaultValue"` // 默认值
DictType string `json:"dictType"` // 字典类型
Front bool `json:"front"` // 前端显示
Desc bool `json:"desc"` // 降序
SearchType string `json:"searchType"` // 搜索类型
FieldSearchType string `json:"fieldSearchType"` // 字段搜索类型
FieldSearchHide bool `json:"fieldSearchHide"` // 隐藏搜索
FieldIndexType string `json:"fieldIndexType"` // 索引类型
CheckDataSource bool `json:"checkDataSource"` // 检查数据源
DataSource struct {
Association int `json:"association"` // 关联类型
Table string `json:"table"` // 关联表
Label string `json:"label"` // 显示字段
Value string `json:"value"` // 值字段
} `json:"dataSource"` // 数据源配置
}
🎨 模板系统
模板目录结构
server/resource/template/
├── web/
│ ├── api.js.tpl # 前端API模板
│ ├── form.vue.tpl # 表单页面模板
│ ├── table.vue.tpl # 列表页面模板
│ └── ...
└── server/
├── api.go.tpl # 后端API模板
├── model.go.tpl # 模型模板
├── request.go.tpl # 请求结构模板
├── router.go.tpl # 路由模板
├── service.go.tpl # 服务模板
└── ...
自定义模板
后端模板示例
go
// model.go.tpl
package {{.PackageName}}
import (
"github.com/flipped-aurora/gin-vue-admin/server/global"
{{- if .HasTimer}}
"time"
{{- end}}
)
// {{.StructName}} 结构体 {{.Description}}
type {{.StructName}} struct {
global.GVA_MODEL {{- range .Fields}}
{{- if .PrimaryKey}}
{{.FieldName}} {{.FieldType}} `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"primarykey{{- if .Comment}};comment:{{.Comment}}{{- end}}"`
{{- else}}
{{.FieldName}} {{.FieldType}} `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"{{- if .ColumnName}}column:{{.ColumnName}};{{- end}}{{- if .Comment}}comment:{{.Comment}};{{- end}}{{- if .DataType}}type:{{.DataType}};{{- end}}{{- if .DefaultValue}}default:{{.DefaultValue}};{{- end}}"`
{{- end}}{{- end}}
}
// TableName {{.StructName}} 表名
func ({{.StructName}}) TableName() string {
return "{{.TableName}}"
}
前端模板示例
vue
<!-- table.vue.tpl -->
<template>
<div>
<div class="gva-search-box">
<el-form ref="elSearchFormRef" :inline="true" :model="searchInfo" class="demo-form-inline" :rules="searchRule" @keyup.enter="onSubmit">
{{- range .Fields}}
{{- if .FieldSearchType}}
<el-form-item label="{{.FieldDesc}}" prop="{{.FieldJson}}">
{{- if eq .FieldSearchType "LIKE" }}
<el-input v-model="searchInfo.{{.FieldJson}}" placeholder="搜索条件" />
{{- else if eq .FieldSearchType "BETWEEN" }}
<template v-if="searchInfo.{{.FieldJson}} && searchInfo.{{.FieldJson}}.length === 2">
<el-date-picker
v-model="searchInfo.{{.FieldJson}}"
type="datetimerange"
range-separator="至"
start-placeholder="开始日期"
end-placeholder="结束日期"
/>
</template>
{{- end}}
</el-form-item>
{{- end}}
{{- end}}
<el-form-item>
<el-button type="primary" icon="search" @click="onSubmit">查询</el-button>
<el-button icon="refresh" @click="onReset">重置</el-button>
</el-form-item>
</el-form>
</div>
<div class="gva-table-box">
<div class="gva-btn-list">
<el-button type="primary" icon="plus" @click="openDialog">新增</el-button>
<el-button icon="delete" style="margin-left: 10px;" :disabled="!multipleSelection.length" @click="onDelete">删除</el-button>
</div>
<el-table
ref="multipleTable"
style="width: 100%"
tooltip-effect="dark"
:data="tableData"
row-key="ID"
@selection-change="handleSelectionChange"
>
<el-table-column type="selection" width="55" />
{{- range .Fields}}
{{- if .Front}}
<el-table-column align="left" label="{{.FieldDesc}}" prop="{{.FieldJson}}" width="120" />
{{- end}}
{{- end}}
<el-table-column align="left" label="操作">
<template #default="scope">
<el-button type="primary" link class="table-button" @click="getDetails(scope.row)"><icon name="edit" />变更</el-button>
<el-button type="primary" link class="table-button" @click="deleteRow(scope.row)"><icon name="delete" />删除</el-button>
</template>
</el-table-column>
</el-table>
<div class="gva-pagination">
<el-pagination
layout="total, sizes, prev, pager, next, jumper"
:current-page="page"
:page-size="pageSize"
:page-sizes="[10, 30, 50, 100]"
:total="total"
@current-change="handleCurrentChange"
@size-change="handleSizeChange"
/>
</div>
</div>
</div>
</template>
🔧 生成器核心代码
代码生成服务
go
type AutoCodeService struct{}
// CreateTemp 创建代码
func (autoCodeService *AutoCodeService) CreateTemp(autoCode system.AutoCodeStruct, ids ...uint) (err error) {
// 1. 创建基础目录
basePath := "autocode_template/"
if autoCode.AutoMoveFile {
basePath = ""
}
// 2. 生成后端代码
for _, value := range autoCode.Fields {
if value.FieldType == "time.Time" {
autoCode.HasTimer = true
}
if value.FieldSearchType != "" {
autoCode.NeedSort = true
}
if value.DictType != "" {
autoCode.DictTypes = append(autoCode.DictTypes, value.DictType)
}
}
// 3. 解析模板并生成文件
templates := []string{
"server/api.go.tpl",
"server/model.go.tpl",
"server/request.go.tpl",
"server/router.go.tpl",
"server/service.go.tpl",
"web/api.js.tpl",
"web/table.vue.tpl",
"web/form.vue.tpl",
}
for _, tmpl := range templates {
if err = autoCodeService.generateFile(tmpl, autoCode, basePath); err != nil {
return err
}
}
// 4. 自动注入路由和API
if autoCode.AutoCreateApiToSql {
if err = autoCodeService.AutoCreateApi(&autoCode); err != nil {
return err
}
}
return nil
}
// generateFile 生成单个文件
func (autoCodeService *AutoCodeService) generateFile(templatePath string, data system.AutoCodeStruct, basePath string) error {
// 读取模板文件
templateContent, err := ioutil.ReadFile("resource/template/" + templatePath)
if err != nil {
return err
}
// 解析模板
tmpl, err := template.New("autocode").Parse(string(templateContent))
if err != nil {
return err
}
// 生成目标文件路径
outputPath := autoCodeService.getOutputPath(templatePath, data, basePath)
// 创建目录
if err = os.MkdirAll(filepath.Dir(outputPath), 0755); err != nil {
return err
}
// 生成文件
file, err := os.Create(outputPath)
if err != nil {
return err
}
defer file.Close()
// 执行模板
return tmpl.Execute(file, data)
}
自动注入功能
go
// AutoCreateApi 自动创建API
func (autoCodeService *AutoCodeService) AutoCreateApi(autoCode *system.AutoCodeStruct) error {
apis := []system.SysApi{
{
Path: "/" + autoCode.Abbreviation + "/create" + autoCode.StructName,
Description: "新增" + autoCode.Description,
ApiGroup: autoCode.Description,
Method: "POST",
},
{
Path: "/" + autoCode.Abbreviation + "/delete" + autoCode.StructName,
Description: "删除" + autoCode.Description,
ApiGroup: autoCode.Description,
Method: "DELETE",
},
{
Path: "/" + autoCode.Abbreviation + "/delete" + autoCode.StructName + "ByIds",
Description: "批量删除" + autoCode.Description,
ApiGroup: autoCode.Description,
Method: "DELETE",
},
{
Path: "/" + autoCode.Abbreviation + "/update" + autoCode.StructName,
Description: "更新" + autoCode.Description,
ApiGroup: autoCode.Description,
Method: "PUT",
},
{
Path: "/" + autoCode.Abbreviation + "/find" + autoCode.StructName,
Description: "根据ID获取" + autoCode.Description,
ApiGroup: autoCode.Description,
Method: "GET",
},
{
Path: "/" + autoCode.Abbreviation + "/get" + autoCode.StructName + "List",
Description: "获取" + autoCode.Description + "列表",
ApiGroup: autoCode.Description,
Method: "GET",
},
}
for _, api := range apis {
if err := apiService.CreateApi(api); err != nil {
return err
}
}
return nil
}
// AutoCreateMenu 自动创建菜单
func (autoCodeService *AutoCodeService) AutoCreateMenu(autoCode *system.AutoCodeStruct) error {
menu := system.SysBaseMenu{
ParentId: "0",
Path: autoCode.Abbreviation,
Name: autoCode.StructName,
Component: "view/" + autoCode.PackageName + "/" + autoCode.PackageName + ".vue",
Sort: 999,
Meta: system.Meta{
Title: autoCode.Description,
Icon: "setting",
},
}
return menuService.AddBaseMenu(menu)
}
🎯 高级功能
1. 关联表生成
go
// 一对多关联配置
type Association struct {
Type string `json:"type"` // 关联类型:hasOne, hasMany, belongsTo
ForeignKey string `json:"foreignKey"` // 外键
References string `json:"references"` // 引用字段
Table string `json:"table"` // 关联表
StructName string `json:"structName"` // 关联结构体
}
// 生成关联查询代码
func generateAssociationCode(associations []Association) string {
var code strings.Builder
for _, assoc := range associations {
switch assoc.Type {
case "hasMany":
code.WriteString(fmt.Sprintf(
"db.Preload(\"%s\").Find(&result)\n",
assoc.StructName,
))
case "belongsTo":
code.WriteString(fmt.Sprintf(
"db.Preload(\"%s\").Find(&result)\n",
assoc.StructName,
))
}
}
return code.String()
}
2. 字典类型支持
go
// 字典字段处理
func processDictField(field Field) string {
if field.DictType != "" {
return fmt.Sprintf(`
<el-select v-model="formData.%s" placeholder="请选择%s">
<el-option
v-for="item in %sOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>`,
field.FieldJson,
field.FieldDesc,
field.DictType,
)
}
return fmt.Sprintf(`<el-input v-model="formData.%s" placeholder="请输入%s" />`,
field.FieldJson, field.FieldDesc)
}
3. 文件上传字段
go
// 文件上传字段模板
const fileUploadTemplate = `
<el-upload
class="upload-demo"
:action="path"
:headers="{ 'x-token': userStore.token }"
:on-success="handleFileSuccess"
:before-upload="beforeFileUpload"
>
<el-button type="primary">点击上传</el-button>
<template #tip>
<div class="el-upload__tip">只能上传jpg/png文件,且不超过500kb</div>
</template>
</el-upload>
`
// 生成文件上传处理函数
func generateFileUploadMethods(fields []Field) string {
var methods strings.Builder
for _, field := range fields {
if field.FieldType == "file" {
methods.WriteString(fmt.Sprintf(`
// %s文件上传成功回调
const handle%sSuccess = (response) => {
formData.value.%s = response.data.file.url
}
// %s文件上传前校验
const before%sUpload = (file) => {
const isJPG = file.type === 'image/jpeg' || file.type === 'image/png'
const isLt2M = file.size / 1024 / 1024 < 2
if (!isJPG) {
ElMessage.error('上传头像图片只能是 JPG/PNG 格式!')
}
if (!isLt2M) {
ElMessage.error('上传头像图片大小不能超过 2MB!')
}
return isJPG && isLt2M
}`,
strings.Title(field.FieldJson),
field.FieldJson,
field.FieldDesc,
strings.Title(field.FieldJson),
))
}
}
return methods.String()
}
📊 生成统计
代码生成记录
go
type AutoCodeHistory struct {
ID uint `json:"ID" gorm:"primarykey"`
CreatedAt time.Time `json:"CreatedAt"`
UpdatedAt time.Time `json:"UpdatedAt"`
StructName string `json:"structName" gorm:"comment:结构体名称"`
TableName string `json:"tableName" gorm:"comment:表名"`
RequestMeta string `json:"requestMeta" gorm:"type:text;comment:请求meta信息"`
AutoCodePath string `json:"autoCodePath" gorm:"comment:自动生成代码路径"`
InjectionMeta string `json:"injectionMeta" gorm:"type:text;comment:注入meta信息"`
ApiIDs string `json:"apiIDs" gorm:"comment:api表注册内容"`
Flag int `json:"flag" gorm:"comment:表示对应状态 0 代表创建, 1 代表回滚 ...."`
}
// 记录生成历史
func (autoCodeService *AutoCodeService) CreateAutoCodeHistory(meta, path, injectionMeta, apiIDs string, autoCode system.AutoCodeStruct) error {
return global.GVA_DB.Create(&system.SysAutoCodeHistory{
RequestMeta: meta,
AutoCodePath: path,
InjectionMeta: injectionMeta,
StructName: autoCode.StructName,
TableName: autoCode.TableName,
ApiIDs: apiIDs,
Flag: 0,
}).Error
}
🔄 代码回滚
go
// RollBack 回滚自动生成的代码
func (autoCodeService *AutoCodeService) RollBack(id uint) error {
var history system.SysAutoCodeHistory
if err := global.GVA_DB.First(&history, id).Error; err != nil {
return err
}
// 删除生成的文件
var meta system.AutoCodeStruct
if err := json.Unmarshal([]byte(history.RequestMeta), &meta); err != nil {
return err
}
// 删除后端文件
serverFiles := []string{
fmt.Sprintf("app/%s/model/%s.go", meta.PackageName, meta.PackageName),
fmt.Sprintf("app/%s/request/%s.go", meta.PackageName, meta.PackageName),
fmt.Sprintf("app/%s/service/%s.go", meta.PackageName, meta.PackageName),
fmt.Sprintf("app/%s/api/%s.go", meta.PackageName, meta.PackageName),
fmt.Sprintf("app/%s/router/%s.go", meta.PackageName, meta.PackageName),
}
for _, file := range serverFiles {
if err := os.Remove(file); err != nil && !os.IsNotExist(err) {
return err
}
}
// 删除前端文件
webFiles := []string{
fmt.Sprintf("../web/src/api/%s.js", meta.PackageName),
fmt.Sprintf("../web/src/view/%s/%s.vue", meta.PackageName, meta.PackageName),
fmt.Sprintf("../web/src/view/%s/%sForm.vue", meta.PackageName, meta.PackageName),
}
for _, file := range webFiles {
if err := os.Remove(file); err != nil && !os.IsNotExist(err) {
return err
}
}
// 删除API记录
var apiIDs []uint
if err := json.Unmarshal([]byte(history.ApiIDs), &apiIDs); err == nil {
global.GVA_DB.Delete(&system.SysApi{}, apiIDs)
}
// 更新历史记录状态
return global.GVA_DB.Model(&history).Update("flag", 1).Error
}
🎨 前端集成
代码生成器页面
vue
<template>
<div class="auto-code">
<el-card>
<template #header>
<div class="card-header">
<span>代码生成器</span>
</div>
</template>
<el-form ref="autoCodeForm" :model="form" :rules="rules" label-width="120px">
<el-form-item label="结构体名称" prop="structName">
<el-input v-model="form.structName" placeholder="请输入结构体名称" />
</el-form-item>
<el-form-item label="表名" prop="tableName">
<el-input v-model="form.tableName" placeholder="请输入表名" />
</el-form-item>
<el-form-item label="包名" prop="packageName">
<el-input v-model="form.packageName" placeholder="请输入包名" />
</el-form-item>
<el-form-item label="结构体描述" prop="description">
<el-input v-model="form.description" placeholder="请输入结构体描述" />
</el-form-item>
<!-- 字段配置 -->
<el-form-item label="字段配置">
<el-table :data="form.fields" border>
<el-table-column label="字段名" prop="fieldName" />
<el-table-column label="字段描述" prop="fieldDesc" />
<el-table-column label="字段类型" prop="fieldType" />
<el-table-column label="数据库类型" prop="dataType" />
<el-table-column label="操作">
<template #default="scope">
<el-button @click="editField(scope.$index)">编辑</el-button>
<el-button @click="deleteField(scope.$index)">删除</el-button>
</template>
</el-table-column>
</el-table>
<el-button @click="addField" style="margin-top: 10px;">添加字段</el-button>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="generateCode">生成代码</el-button>
<el-button @click="resetForm">重置</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</template>
<script setup>
import { ref, reactive } from 'vue'
import { createTemp } from '@/api/autoCode'
import { ElMessage } from 'element-plus'
const form = reactive({
structName: '',
tableName: '',
packageName: '',
description: '',
fields: []
})
const rules = {
structName: [{ required: true, message: '请输入结构体名称', trigger: 'blur' }],
tableName: [{ required: true, message: '请输入表名', trigger: 'blur' }],
packageName: [{ required: true, message: '请输入包名', trigger: 'blur' }]
}
const generateCode = async () => {
try {
const res = await createTemp(form)
if (res.code === 0) {
ElMessage.success('代码生成成功')
}
} catch (error) {
ElMessage.error('代码生成失败')
}
}
</script>
🐛 常见问题
Q: 生成的代码编译失败?
A: 检查字段类型配置是否正确,确保 Go 类型和数据库类型匹配。
Q: 前端页面显示异常?
A: 检查字段的前端显示配置,确保必要的字段已设置为前端显示。
Q: 如何自定义生成模板?
A: 修改 server/resource/template/
目录下的模板文件,重启服务即可生效。
Q: 生成后如何添加自定义逻辑?
A: 在生成的代码基础上添加自定义方法,避免直接修改生成的核心 CRUD 方法。