本文是「Go Web 开发提速(gos)」系列的第5篇核心内容,前4篇我们依次落地了 Spring式注解方案、Servlet注解与参数解析、Filter通用逻辑复用与依赖解耦、自动生成代码实战与注入原理,逐步搭建起gos在Go Web服务端的自动化提效体系,所有核心能力(如@gos autogen自动注入)均在前面的文章中详细讲解。
本篇将基于前序所有能力,继续延伸 gos 的自动化边界,把提效能力落地到数据库操作层,实现数据库相关代码的全量自动生成;所有生成的DAL对象、依赖注入能力,均复用前文的核心注解与逻辑,做到整套技术体系的无缝衔接与能力复用,完成服务端「Web层 → 业务层 → 数据层」的全链路自动化闭环。
数据库代码自动生成
使用示例:当你有一个user表,包含三个核心字段如下:
| ID | NAME | PASSWORD |
|---|---|---|
| 1 | zhangsan | 123456 |
想要执行查询语句 select * from user where name='zhangsan' 时,仅需一行业务代码即可完成:
users, err := s.UserDal.GetAll(ctx, common.OptEq(user.C_Name, "zhangsan"))
这条语句的核心优势:所有关联的实体定义、列名常量、DAL操作方法等配套代码,全部由 gos 自动生成;且这行代码完全通用,既可以无缝适配mysql数据库,也可以直接在mongo数据库中运行,无需做任何语法修改。
自动生成的代码完整说明
gos 针对数据库操作的自动化生成,是一套完整的闭环体系,会自动产出4类核心内容,覆盖数据库开发的全流程,无需手动编写任何基础代码:
- Entity 结构体:数据库表对应的Go结构体,精准映射表字段与属性;
- Column 常量:表字段对应的常量定义,彻底杜绝SQL硬编码问题;
- DAL 核心方法:封装所有常用的数据库增删改查操作,开箱即用;
- 自动注入支持:生成的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"` //密码
}
✅ 实体代码的自动生成特性:
- 字段名自动转换为标准驼峰格式,符合Go开发编码规范;
- 自动生成表名关联的注解,无需手动指定表与结构体的映射关系;
- 自动填充
tblName/dbVariable核心注解,为后续DAL层代码生成提供基础; - 自动绑定
json和gorm双标签,兼顾序列化和数据库操作需求。
✔️ 二、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"
)
✅ 列名常量的核心价值(解决数据库开发痛点):
- 自动为每个表字段生成对应的常量,查询/更新时直接引用常量,彻底告别字段名硬编码;
- 常量引用能百分百保证字段名书写正确,避免因字段名拼写错误导致的SQL执行失败;
- 支持IDE的全局引用搜索,快速定位字段在项目中的使用位置;
- 数据库字段变更(删除/修改)时,重新生成代码后会立即触发语法错误提示,能第一时间发现并修复问题,避免线上故障。
✔️ 三、DAL 核心操作方法说明
gos 为数据库表自动生成全量的常用操作方法,生成的文件名为 mysql.dal.gen.go(Mongo为mongo.dal.gen.go),覆盖日常开发99%的数据库操作场景,无需手动封装,包含以下11类核心方法:
- Create - 单条数据创建
- GetAll - 根据条件查询所有符合条件的记录
- GetLimitAll - 根据条件查询指定数量的记录
- GetLimitAllWithStart - 从指定偏移量开始查询指定数量的记录
- GetOne - 根据条件查询单条匹配记录
- GetOneById - 根据主键ID精准查询单条记录
- List - 分页查询,返回数据列表+总条数(业务开发高频需求)
- Update - 根据条件更新匹配的记录
- UpdateById - 根据主键ID精准更新单条记录
- Delete - 根据条件删除匹配的记录
- DeleteByIds - 根据主键ID列表批量删除记录
DAL方法的核心通用特性
- 自动生成的代码同时完美支持MySQL和Mongo,两个数据库的调用方法、入参格式完全一致;
- 数据库之间的语法差异、操作差异,由
common模块提供的屏蔽函数统一处理,对业务层完全透明; - 所有查询类方法的入参格式统一为:
(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 做的自动化生成,并非「一刀切」的不可修改,而是完美兼顾自动生成+自定义扩展,解决了「自动生成代码被覆盖」的行业痛点,核心设计逻辑如下:
- 自动生成的基础DAL代码,文件命名为
mysql.dal.gen.go(Mongo为mongo.dal.gen.go),该文件由gos维护,重新生成时会覆盖,但不会修改用户代码; - 为用户预留了专属的自定义扩展文件:
user.dal.go,该文件永远不会被gos覆盖,用户可在其中自由添加业务专属的DAL方法; - 扩展时可直接复用自动生成的能力:通过
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配置文件管理,分为公共配置和私有配置,兼顾安全性和通用性:
project.public.toml:存放公开配置,如需要生成的表名、输出路径,可提交至Git;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现阶段无法替代的核心优势:
- 生成速度更快、输出结果完全可控:gos 是毫秒级一键生成,产出的代码100%符合项目工程规范、命名规范、目录规范,没有任何冗余内容;而AI生成代码需要反复调试,很难做到一次生成就完全符合预期,总会有格式/逻辑的细微偏差。
- 零等待、零成本生成,无任何附加条件:gos 本地命令行执行即可生成,无需联网、无需等待AI响应,也没有token/次数限制,开发过程中表结构变更后,重新生成仅需一行命令,极致提效。
- 无需编写提示词,配置即生成,解放心智:gos 只需要简单的toml配置表名、输出路径,即可全自动生成整套代码,无需和工具做任何「交互」;而AI需要编写精准的提示词,且每次生成的风格、写法都有细微差异,还要手动对齐项目规范,反而增加额外工作量。
- 各司其职,让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)})
}