Go Web 开发提速 5(gos):数据库代码全自动生成 —— 多库统一+零硬编码+极致复用

0 阅读14分钟

本文是「Go Web 开发提速(gos)」系列的第5篇核心内容,前4篇我们依次落地了 Spring式注解方案、Servlet注解与参数解析、Filter通用逻辑复用与依赖解耦、自动生成代码实战与注入原理,逐步搭建起gos在Go Web服务端的自动化提效体系,所有核心能力(如@gos autogen自动注入)均在前面的文章中详细讲解。

本篇将基于前序所有能力,继续延伸 gos 的自动化边界,把提效能力落地到数据库操作层,实现数据库相关代码的全量自动生成;所有生成的DAL对象、依赖注入能力,均复用前文的核心注解与逻辑,做到整套技术体系的无缝衔接与能力复用,完成服务端「Web层 → 业务层 → 数据层」的全链路自动化闭环。

数据库代码自动生成

使用示例:当你有一个user表,包含三个核心字段如下:

IDNAMEPASSWORD
1zhangsan123456

想要执行查询语句 select * from user where name='zhangsan' 时,仅需一行业务代码即可完成:

users, err := s.UserDal.GetAll(ctx, common.OptEq(user.C_Name, "zhangsan"))

这条语句的核心优势:所有关联的实体定义、列名常量、DAL操作方法等配套代码,全部由 gos 自动生成;且这行代码完全通用,既可以无缝适配mysql数据库,也可以直接在mongo数据库中运行,无需做任何语法修改。

自动生成的代码完整说明

gos 针对数据库操作的自动化生成,是一套完整的闭环体系,会自动产出4类核心内容,覆盖数据库开发的全流程,无需手动编写任何基础代码:

  1. Entity 结构体:数据库表对应的Go结构体,精准映射表字段与属性;
  2. Column 常量:表字段对应的常量定义,彻底杜绝SQL硬编码问题;
  3. DAL 核心方法:封装所有常用的数据库增删改查操作,开箱即用;
  4. 自动注入支持:生成的DAL对象自带 @gos autogen 注解,可直接在biz层中注入使用,完全复用本系列前4篇的注入能力。

✔️ 一、Entity 实体代码说明

自动生成的实体代码,是数据库表的强类型映射,文件名为 table.gen.go,以user表为例,生成代码如下:

// Code generated by github.com/wanjm/gos DO NOT EDIT.
package user

// @gos tblName=user dbVariable=db
type User struct {
	ID       int32  `json:"id" gorm:"column:id"`             //自增id
	Name     string `json:"name" gorm:"column:name"`         //姓名
	Password string `json:"password" gorm:"column:password"` //密码
}

✅ 实体代码的自动生成特性:

  1. 字段名自动转换为标准驼峰格式,符合Go开发编码规范;
  2. 自动生成表名关联的注解,无需手动指定表与结构体的映射关系;
  3. 自动填充 tblName/dbVariable 核心注解,为后续DAL层代码生成提供基础;
  4. 自动绑定jsongorm双标签,兼顾序列化和数据库操作需求。

✔️ 二、Column 列名常量代码说明

自动生成的列名常量代码,文件名为 column.gen.go,以user表为例,生成代码如下:

// Code generated by github.com/wanjm/gos DO NOT EDIT.
package user
const (
	C_ID = "id"
	C_Name = "name"
	C_Password = "password"
)

✅ 列名常量的核心价值(解决数据库开发痛点):

  1. 自动为每个表字段生成对应的常量,查询/更新时直接引用常量,彻底告别字段名硬编码;
  2. 常量引用能百分百保证字段名书写正确,避免因字段名拼写错误导致的SQL执行失败;
  3. 支持IDE的全局引用搜索,快速定位字段在项目中的使用位置;
  4. 数据库字段变更(删除/修改)时,重新生成代码后会立即触发语法错误提示,能第一时间发现并修复问题,避免线上故障。

✔️ 三、DAL 核心操作方法说明

gos 为数据库表自动生成全量的常用操作方法,生成的文件名为 mysql.dal.gen.go(Mongo为mongo.dal.gen.go),覆盖日常开发99%的数据库操作场景,无需手动封装,包含以下11类核心方法:

  1. Create - 单条数据创建
  2. GetAll - 根据条件查询所有符合条件的记录
  3. GetLimitAll - 根据条件查询指定数量的记录
  4. GetLimitAllWithStart - 从指定偏移量开始查询指定数量的记录
  5. GetOne - 根据条件查询单条匹配记录
  6. GetOneById - 根据主键ID精准查询单条记录
  7. List - 分页查询,返回数据列表+总条数(业务开发高频需求)
  8. Update - 根据条件更新匹配的记录
  9. UpdateById - 根据主键ID精准更新单条记录
  10. Delete - 根据条件删除匹配的记录
  11. DeleteByIds - 根据主键ID列表批量删除记录

DAL方法的核心通用特性

  1. 自动生成的代码同时完美支持MySQL和Mongo,两个数据库的调用方法、入参格式完全一致;
  2. 数据库之间的语法差异、操作差异,由common模块提供的屏蔽函数统一处理,对业务层完全透明;
  3. 所有查询类方法的入参格式统一为:(ctx context.Context, options []common.Optioner, cols ...[]string),学习成本极低。

✔️ 四、DAL 自动生成的核心代码展示

自动生成的DAL层代码是整套能力的核心,核心结构与关键能力如下:

// Code generated by github.com/wanjm/gos DO NOT EDIT.
package dal

import (
	context "context"
	user "github.com/wan_jm/servlet_example/business/package/entity/mysql/user"
	common "github.com/wanjm/common"
	gorm "gorm.io/gorm"
)

// @gos autogen
type UserDal struct {
	Db *gorm.DB 
}

func (a *UserDal) getDB(ctx context.Context) *gorm.DB {
	return a.Db.WithContext(ctx).Table("user")
}
func (c *UserDal) getDBOperation(context context.Context) common.DbOperation {
    // 封装数据库通用操作,屏蔽MySQL/Mongo差异
}

✅ 核心特性补充:

  • 生成的UserDal结构体自带 @gos autogen 注解,支持本系列前4篇提到的自动注入能力;
  • 结构体中的Db *gorm.DB对象会被gos自动注入,无需手动初始化;
  • 内置getDB()方法直接返回绑定上下文的*gorm.DB对象,可无缝对接原生gorm的所有方法;
  • 内置getDBOperation()方法返回通用的数据库操作对象,统一处理多数据库的差异逻辑。

自定义业务代码扩展:无缝补充,永不覆盖

gos 做的自动化生成,并非「一刀切」的不可修改,而是完美兼顾自动生成+自定义扩展,解决了「自动生成代码被覆盖」的行业痛点,核心设计逻辑如下:

  1. 自动生成的基础DAL代码,文件命名为 mysql.dal.gen.go(Mongo为mongo.dal.gen.go),该文件由gos维护,重新生成时会覆盖,但不会修改用户代码
  2. 为用户预留了专属的自定义扩展文件:user.dal.go,该文件永远不会被gos覆盖,用户可在其中自由添加业务专属的DAL方法;
  3. 扩展时可直接复用自动生成的能力:通过getDB()获取原生gorm对象,通过getDBOperation()获取通用操作对象,无缝对接自定义逻辑。

common 通用模块能力说明

为了实现「一套代码适配多数据库」的核心目标,github.com/wanjm/common 模块提供了全量的通用能力封装,是整套数据库自动生成体系的核心支撑,主要包含两大核心内容:

1. 通用数据库操作方法

提供common.DbOperation对象,封装了所有数据库的基础操作,统一调用格式,屏蔽MySQL和Mongo的语法差异:

  • Query:通用查询(单/多条)
  • QueryCV:带总数的分页查询(业务高频)
  • Delete:条件删除
  • Update:条件更新
  • Create:数据新增

2. 统一查询条件:Optioner 接口

定义了标准化的查询条件接口Optioner,并提供了所有常用条件的具体实现,所有条件均可在MySQL/Mongo中通用,无需修改,包含: Eq / Ne / In / NotIn / Gt / Gte / Lt / Lte / Exist 业务层通过 common.OptEq(字段常量, 值) 即可构建条件,简单易用且无侵入性。

自动生成文件的标准目录结构

gos 生成的所有数据库相关代码,均遵循规范化的目录结构,分层清晰、职责明确,适配大型项目的工程化规范,目录结构如下:

business/package
├── entity/          // 实体层:存放表对应的结构体+常量
│   ├── mysql/       // MySQL专属实体
│   │   ├── student/ // 按表名划分包,单表一个包
│   │   │   ├── table.gen.go    // 自动生成:表对应的ENTITY结构体
│   │   │   ├── column.gen.go   // 自动生成:列名常量,杜绝硬编码
│   │   │   ├── const.gen.go    // 自动生成:数据库相关常量
│   ├── mongo/       // Mongo专属实体(结构和MySQL一致)
│   │   ├── student/
│   │   │   ├── table.go         // 手动编写/Mongo配置生成
│   │   │   ├── column.gen.go    // 自动生成:列名常量
│   │   │   ├── const.gen.go     // 自动生成:数据库常量
├── dal/             // 数据访问层:数据库操作方法
│   ├── user.dal.go        // 自定义扩展:用户业务专属DAL方法,永不覆盖
│   ├── mysql.dal.gen.go   // 自动生成:MySQL基础DAL方法
│   ├── mongo.dal.gen.go   // 自动生成:Mongo基础DAL方法

代码生成的配置方式与执行命令

✔️ 配置文件(工程目录下)

gos 的数据库生成能力通过toml配置文件管理,分为公共配置和私有配置,兼顾安全性和通用性:

  1. project.public.toml:存放公开配置,如需要生成的表名、输出路径,可提交至Git;
  2. project.private.toml:存放私有敏感配置,如数据库连接DSN,禁止提交至Git

配置文件示例:

[[DBConfig]]
DSN="user:passwd@tcp(dbhost:3306)/dbplaso"
DBName = "DBNAME"
DBType = "mysql"

[[DBConfig.DbGenCfgs]]
OutPath = "business/package"
Tables = [
  { Name = "student", Arrays = ["StudentId"], Maps = ["StudentId"] }
]

✔️ 执行生成命令

在项目根目录下,执行一行命令即可完成全量/指定表的代码生成,简单高效:

# 生成配置中所有数据库的所有表
gos -dbname all

# 生成指定表(如仅生成student表)
gos -dbname student

✅ 为什么有了AI,gos这类自动化工具依然是刚需?

很多开发者会有疑问:当下AI的代码生成能力已经很强了,为什么还需要 gos 这种基于工程规范的自动化代码生成工具?两者并不冲突,反而各司其职、互为补充,gos做基础工程代码自动化,AI做高阶业务代码创作,选择gos的核心原因有4点,也是AI现阶段无法替代的核心优势:

  1. 生成速度更快、输出结果完全可控:gos 是毫秒级一键生成,产出的代码100%符合项目工程规范、命名规范、目录规范,没有任何冗余内容;而AI生成代码需要反复调试,很难做到一次生成就完全符合预期,总会有格式/逻辑的细微偏差。
  2. 零等待、零成本生成,无任何附加条件:gos 本地命令行执行即可生成,无需联网、无需等待AI响应,也没有token/次数限制,开发过程中表结构变更后,重新生成仅需一行命令,极致提效。
  3. 无需编写提示词,配置即生成,解放心智:gos 只需要简单的toml配置表名、输出路径,即可全自动生成整套代码,无需和工具做任何「交互」;而AI需要编写精准的提示词,且每次生成的风格、写法都有细微差异,还要手动对齐项目规范,反而增加额外工作量。
  4. 各司其职,让AI聚焦更有价值的智能任务:gos 负责解决「重复、标准化、无技术含量」的基础代码编写工作(如实体、DAL、常量),把开发者和AI都从繁琐的重复劳动中解放出来;此时AI可以聚焦做更需要智能的高阶任务 —— 基于gos搭建的标准化框架,编写业务逻辑、实现复杂的业务场景,这才是AI价值最大化的方式。

✅ 基于gos框架的AI业务代码编写专属提示词

### Package Structure
- 整体说明, *.gen.go文件是由gos命令自动生成的,无需编辑
- `biz`: 完成一个具体的业务代码
- `dal`: 完成数据库操作,包括mongo和mysql 
- `entity`: domain models; split by backend (`mysql`, `mongo`). dir under `mysql` or `mongo` is information of one table, the name of the dir is the table name also used as 
- `entity/mysql` 目录是mysql表名的蛇形命名,table.gen.go, column.gen.go,const.gen.go gos根据DDL生成;
- `entity/mongo` 目录是mongo表名的蛇形命名,其中table.go 是数据库结构,column.gen.go是gos根据table.go 自动生成;
mongo的表结构体定义,需要在根结构体,添加 // @gos tblName=蛇形表名,dbVariable=plasoMongo
- `schema` is for reqeust and response for http request;

### biz
biz 定义对外的api接口,或者完成一个特定的的业务;
如果是api接口,则结构体需要添加 // @gos type=servlet
// 对外函数需要填写 // @gos url=***, title=***
// 函数定义为:func (receiver *结构体名) 根据业务定义函数名(ctx context.Context, req *schema.请求的结构体) 返回值,error 

### 数据查询
1. 查询使用的dal在结构体中定义,fieldName根结构体名相同,类型为指针,定义在业务biz结构体中
2. 也biz函数中就可以直接使用了,自动生成了一系列dal,可以直接使用;Create,Update,List,GetOne,GetAll,GetLimitAll,GetOneById,UpdateById,DeleteByIds,Delete
3. Create 是保存结构;
4. Update是更新接口;
5. Set是mongo专用的Update只执行set的操作的接口;
6. GetAll是查询所有符合条件的;
7. GetLimitAll 查询指定个数的结果;
8. GetOne,仅查询第一条满足要求的结果;
9. Delete, 根据条件删除记录;
10. UpdateById,DeleteByIds 根据Id删除记录;
11. 数据库使用列名的地方,请从column.gen.go中获取;
12. mongo的子结构体,列名拼接,请使用common.C2,C3等;

✅ 下一篇内容重磅预告

系列第6篇前瞻:前端代码自动化生成 · 服务端驱动全链路提效

在完成服务端「Web层-业务层-数据层」的全链路代码自动生成后,我们的 gos 提效能力将正式突破服务端边界,延伸至前端开发环节

下一篇我们将为大家详细讲解:基于服务端已定义的完整接口元信息、Request请求结构体、Response响应结构体等全部内容,gos 将实现 前端代码的全自动生成 —— 核心包含标准化的HTTP请求调用工具、前后端数据结构一致的Request/Response对象、接口地址枚举常量、入参校验规则等所有前端对接服务端的核心代码。

做到「服务端定义变更,前端代码一键更新」,彻底消除前后端对接的沟通成本与数据格式不一致的问题,敬请期待本系列第6篇内容!


✔️ DAL 完整自动生成代码展示

// Code generated by github.com/wanjm/gos DO NOT EDIT.
package dal

import (
	context "context"
	user "github.com/wan_jm/servlet_example/business/package/entity/mysql/user"
	common "github.com/wanjm/common"
	gorm "gorm.io/gorm"
)

// user 表数据访问层
// @gos autogen
type UserDal struct {
	Db *gorm.DB
}

func (a *UserDal) getDB(ctx context.Context) *gorm.DB {
	return a.Db.WithContext(ctx).Table("user")
}
func (c *UserDal) getDBOperation(context context.Context) common.DbOperation {
	return common.DbOperation{
		Db:        c.Db,
		TableName: "user",
		Context:   context,
	}
}

// Create 创建单条用户数据
func (a *UserDal) Create(ctx context.Context, item *user.User) error {
	dbOperation := a.getDBOperation(ctx)
	err := dbOperation.Create(item)
	if err != nil {
		common.Error(ctx, "insert data to user failed", common.Err(err))
	}
	return err
}

// GetAll 根据条件查询所有用户数据
func (a *UserDal) GetAll(ctx context.Context, options []common.Optioner, cols ...[]string) (item []*user.User, err error) {
	return a.GetLimitAll(ctx, options, 0, cols...)
}

// GetLimitAll 根据条件查询指定数量的用户数据
func (a *UserDal) GetLimitAll(ctx context.Context, options []common.Optioner, count int, cols ...[]string) (item []*user.User, err error) {
	return a.GetLimitAllWithStart(ctx, options, 0, count, cols...)
}

// GetLimitAllWithStart 从指定偏移量开始查询指定数量的用户数据
func (a *UserDal) GetLimitAllWithStart(ctx context.Context, options []common.Optioner, start, count int, cols ...[]string) (item []*user.User, err error) {
	var colNames []string
	if len(cols) > 0 {
		colNames = cols[0]
	}
	dbOperation := a.getDBOperation(ctx)
	err = dbOperation.Query(
		&common.SqlQueryOptions{
			QueryFields:  options,
			Offset:       start,
			Limit:        count,
			SelectFields: colNames,
		},
		&item,
	)
	if err != nil {
		common.Error(ctx, "GetAll DB record from user failed", common.Err(err))
	}
	return
}

// GetOne 根据条件查询单条用户数据
func (a *UserDal) GetOne(ctx context.Context, options []common.Optioner, cols ...[]string) (item *user.User, err error) {
	res, err := a.GetLimitAll(ctx, options, 1, cols...)
	if err != nil {
		return
	}
	if len(res) > 0 {
		item = res[0]
	}
	return
}

// GetOneById 根据主键ID查询单条用户数据
func (a *UserDal) GetOneById(ctx context.Context, id int32, cols ...[]string) (item *user.User, err error) {
	return a.GetOne(ctx, []common.Optioner{common.Eq("id", id)}, cols...)
}

// List 分页查询用户数据,返回列表+总条数
func (a *UserDal) List(ctx context.Context, option []common.Optioner, pageNo, pageSize int, cols ...[]string) (list []*user.User, total int64, err error) {
	var colNames []string
	if len(cols) > 0 {
		colNames = cols[0]
	}
	dbop := a.getDBOperation(ctx)
	err = dbop.QueryCV(
		&common.SqlQueryOptions{
			QueryFields: option,
			Offset:      int(pageNo * pageSize),
			Limit:       int(pageSize),
			OrderFields: []common.OrderByParam{
				{
					Field:     "id",
					Direction: common.ASCStr,
				},
			},
			SelectFields: colNames,
		},
		&total,
		&list,
	)
	if err != nil {
		common.Error(ctx, "List record of user failed", common.Err(err))
	}
	return
}

// Update 根据条件更新用户数据
func (a *UserDal) Update(ctx context.Context, options []common.Optioner, updates map[string]any) (err error) {
	op := a.getDBOperation(ctx)
	err = op.Update(&common.SqlUpdateOptions{
		QueryFields: options,
		Updates:     updates,
	})
	if err != nil {
		common.Error(ctx, "Update record of user failed", common.Err(err))
	}
	return
}

// UpdateById 根据主键ID更新用户数据
func (a *UserDal) UpdateById(ctx context.Context, id int32, updates map[string]any) error {
	return a.Update(ctx, []common.Optioner{common.Eq("id", id)}, updates)
}

// Delete 根据条件删除用户数据
func (a *UserDal) Delete(ctx context.Context, options []common.Optioner) error {
	op := a.getDBOperation(ctx)
	err := op.Delete(options)
	if err != nil {
		common.Error(ctx, "Delete record of user failed", common.Err(err))
	}
	return err
}

// DeleteByIds 根据主键ID列表批量删除用户数据
func (a *UserDal) DeleteByIds(ctx context.Context, ids []int32) error {
	return a.Delete(ctx, []common.Optioner{common.In("id", ids)})
}