代码生成
框架的目的是让开发者将精力聚焦于业务逻辑实现本身。controller
,dao/entity/do
,service
都可以通过开发工具生成
需要注意的是,manifest/config.yaml
是部署用的配置,而开发时使用的配置在 hack/config.yml
请确认,您已在一个 goframe2
的项目中,或者通过 gf init demoProj -u
生成一个项目。
生成dao
代码生成依赖于数据库表结构,所以我们先创建一个实际数据库和表结构
创建数据库及表结构
sqlite3 db.sqlite3
进入 sqlite命令行界面
CREATE TABLE IF NOT EXISTS "detect_item" (
"id" INTEGER,
"name" VARCHAR(55),
"create_at" DATETIME,
"updated_at" DATETIME,
"deleted_at" DATETIME,
PRIMARY KEY("id")
);
INSERT INTO detect_item VALUES(1,'C++源代码基础检测','2024-01-01 01:01:01',NULL,NULL);
INSERT INTO detect_item VALUES(2,'C++源代码规范检测','2024-01-01 01:01:01',NULL,NULL);
INSERT INTO detect_item VALUES(3,'浏览器JS源代码检测','2024-01-01 01:01:01',NULL,NULL);
INSERT INTO detect_item VALUES(4,'浏览器CSS源代码检测','2024-01-01 01:01:01',NULL,NULL);
INSERT INTO detect_item VALUES(5,'浏览器HTML源代码检测','2024-01-01 01:01:01',NULL,NULL);
INSERT INTO detect_item VALUES(6,'二进制文件检测','2024-01-01 01:01:01',NULL,NULL);
INSERT INTO detect_item VALUES(7,'二进制依赖检测','2024-01-01 01:01:01',NULL,NULL);
.tables -- 查看所有表
.schema user -- 查看user表结构
.exit -- 退出sqlite命令行界面
sqlite3 db.sqlite3 ".output sqlite3.data.sql" ".dump"
sqlite3 db.sqlite3 ".read sqlite3.data.sql"
配置开发数据库
# hack/config.yaml
gfcli:
gen:
dao: # 注意层级关系
#link: "mysql:root:root@tcp(127.0.0.1:3306)/goframe?loc=Local&parseTime=true"
link: "sqlite::@file(./resource/db.sqlite3)"
tables: "detect_item" # 对应要生成的表
更多配置选项请参考这里
确定配置数据库表都正确时,可以直接在项目根目录下执行 make dao
$ make dao
generated: internal/dao/detect_item.go
generated: internal/dao/internal/detect_item.go
generated: internal/model/do/detect_item.go
generated: internal/model/entity/detect_item.go
done!
编写好诸如 `mysql` 的字段注释,可以生成更详细的字段注释信息
生成ctrl
make ctrl
命令会分析给定的api接口定义目录下的代码,自动生成对应的控制器/SDK Go代码文件
如何编写
api
呢?可以先看看以下示例代码
// 默认值的struct tag名称为d(也可以使用default)
type GetListReq struct {
g.Meta `path:"/" method:"get"`
Type string `v:"required#请选择内容模型" dc:"内容模型"`
Page int `v:"min:0#分页号码错误" dc:"分页号码" d:"1"`
Size int `v:"max:50#分页数量最大50条" dc:"分页数量,最大50" d:"10"`
Sort int `v:"in:0,1,2#排序类型不合法" dc:"排序类型(0:最新, 默认。1:活跃, 2:热度)"`
}
type GetListRes struct {
Items []Item `dc:"内容列表"`
}
type Item struct {
Id int64 `dc:"内容ID"`
Title string `dc:"内容标题"`
}
// RegisterReq 注册请求数据结构
type RegisterReq struct {
Name string `p:"username" v:"required|length:4,30#请输入账号|账号长度为:{min}到:{max}位"`
Pass string `p:"password1" v:"required|length:6,30#请输入密码|密码长度不够"`
Pass2 string `p:"password2" v:"required|length:6,30|same:password1#请确认密码|密码长度不够|两次密码不一致"`
}
// RegisterRes 注册返回数据结构
type RegisterRes struct {
Code int `json:"code"`
Error string `json:"error"`
Data interface{} `json:"data"`
}
参考 以上代码示例
新建自己的 api
接口文件。还可以参考这里 和 这里
GoFrame
的struct tag
(标签) 有哪些?
参数请求、数据校验、OpenAPIv3
、命令管理、数据库ORM。
Tag(简写) | 全称 | 描述 | 相关文档 |
---|---|---|---|
v |
valid |
数据校验标签。 | Struct校验-基本使用 |
p |
param |
自定义请求参数匹配。 | 请求输入-对象处理 |
d |
default |
请求参数默认值绑定。 | 请求输入-默认值绑定 |
orm |
orm |
ORM标签,用于指定表名、关联关系。 | 数据规范-gen dao 模型关联-静态关联-With特性 |
dc |
description |
通用结构体属性描述,ORM和接口使用 |
我这里使用以下内容成功进行了control
代码生成。
// internal/model/common.go // 综合考虑后,将以下内容存放在 model 下可以共同使用
package model
type CommonPaginationReq struct {
Page int `json:"page" in:"query" d:"1" v:"min:0#分页号码错误" dc:"分页号码,默认1"`
Size int `json:"size" in:"query" d:"10" v:"max:50#分页数量最大50条" dc:"分页数量,最大50"`
}
type CommonPaginationRes struct {
Total int `dc:"总数"`
Page int `json:"page" in:"query" d:"1" v:"min:0#分页号码错误" dc:"分页号码,默认1"`
Size int `json:"size" in:"query" d:"10" v:"max:50#分页数量最大50条" dc:"分页数量,最大50"`
}
// /api/compat/v1/detect_item.go
package v1
import (
"demo/internal/model"
"demo/internal/model/entity"
"github.com/gogf/gf/v2/frame/g"
)
type DetectItemGetAllReq struct {
g.Meta `path:"/detect_item/all" method:"get" summary:"查询所有的检测项目" tags:"检测项目"`
}
type DetectItemGetAllRes struct {
List []*entity.DetectItem `json:"list" dc:"所有的检测项"` //
}
type DetectItemGetListReq struct {
g.Meta `path:"/detect_item" method:"get" summary:"查询检测项目列表(分页)" tags:"检测项目"`
model.CommonPaginationReq
}
type DetectItemGetListRes struct {
List []*entity.DetectItem `json:"list" dc:"检测项列表(分页)"` //
model.CommonPaginationRes
}
type DetectItemSearchReq struct {
g.Meta `path:"/detect_item/search" method:"get" summary:"查询检测项目列表(搜索分页)" tags:"检测项目"`
Name string `json:"name" v:"required|length:2,30#搜索项目名|项目名长度为:{min}到:{max}位" dc:"检测项编号"`
model.CommonPaginationReq
}
type DetectItemSearchRes struct {
List []*entity.DetectItem `json:"list" dc:"检测项列表(搜索分页)"` //
model.CommonPaginationRes
}
type DetectItemGetOneReq struct {
g.Meta `path:"/detect_item/{id}" method:"get" tags:"检测项目" summary:"查询单个检测项目信息"`
Id uint `json:"id" in:"path" v:"min:1#检测项的编号不正确" dc:"检测项编号"`
}
type DetectItemGetOneRes struct {
entity.DetectItem
// Id uint `json:"id" dc:"检测项编号"` //
// Name string `json:"name" dc:"检测项名称"` //
}
type DetectItemCreateReq struct {
g.Meta `path:"/detect_item/create" method:"get" tags:"检测项目" summary:"创建检测项目信息"`
Name string `json:"name" v:"required|length:2,50#项目名|项目名长度为:{min}到:{max}位" dc:"检测项名称"` //
// entity.DetectItem
}
type DetectItemCreateRes struct {
NewID uint `json:"new_id" dc:"ID"`
}
type DetectItemUpdateReq struct {
g.Meta `path:"/detect_item/update/{id}" method:"post" tags:"检测项目" summary:"修改内容接口"`
Id uint `json:"id" in:"path" v:"min:1#检测项的编号不正确" dc:"检测项编号"`
Name string `json:"name" v:"required|length:2,50#项目名|项目名长度为:{min}到:{max}位" dc:"检测项名称"` //
}
type DetectItemUpdateRes struct {
}
type DetectItemDeleteReq struct {
g.Meta `path:"/detect_item/delete" method:"delete" tags:"检测项目" summary:"删除内容接口"`
Id uint `json:"id" v:"min:1#检测项的编号不正确" dc:"检测项编号"`
}
type DetectItemDeleteRes struct {
}
编写好自己的 api 接口文件后,就可以通过 make ctrl
来生成控制器
$ make ctrl
generated: /home/lee/Projects/compat-detect-tools/demo/api/compat/compat.go
generated: internal/controller/compat/compat.go
generated: internal/controller/compat/compat_new.go
generated: internal/controller/compat/compat_v1_detect_item_get_list.go
generated: internal/controller/compat/compat_v1_detect_item_get_one.go
generated: internal/controller/compat/compat_v1_detect_item_create.go
generated: internal/controller/compat/compat_v1_detect_item_update.go
generated: internal/controller/compat/compat_v1_detect_item_delete.go
generated: /home/lee/Projects/compat-detect-tools/demo/api/hello/hello.go
done!
路由注册
// internal/cmd/cmd.go
var (
Main = gcmd.Command{
Name: "main",
Usage: "main",
Brief: "start http server",
Func: func(ctx context.Context, parser *gcmd.Parser) (err error) {
s := g.Server()
s.Group("/", func(group *ghttp.RouterGroup) {
group.Middleware(ghttp.MiddlewareHandlerResponse)
group.Bind(
compat.NewV1(), // 添加这一行,注册路由,就可以开始访问接口了
hello.NewV1(),
)
})
s.Run()
return nil
},
}
)
使用swagger
接口文档
参考这篇接口文档-自定义ui
粘贴以下html
到 resource/template/apidoc.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="description" content="SwaggerUI"/>
<title>SwaggerUI</title>
<link rel="stylesheet" href="https://unpkg.com/swagger-ui-dist@latest/swagger-ui.css" />
</head>
<body>
<div id="swagger-ui"></div>
<script src="https://unpkg.com/swagger-ui-dist@latest/swagger-ui-bundle.js" crossorigin></script>
<script>
window.onload = () => {
window.ui = SwaggerUIBundle({
url: '/api.json',
dom_id: '#swagger-ui',
});
};
</script>
</body>
</html>
在 internal/cmd/cmd.go
中添加路由绑定
var (
Main = gcmd.Command{
Name: "main",
Usage: "main",
Brief: "start http server",
Func: func(ctx context.Context, parser *gcmd.Parser) (err error) {
s := g.Server()
// --- begin ------------------------------
// 添加下面三行
s.BindHandler("/apidoc", func(r *ghttp.Request) {
r.Response.WriteTpl("/apidoc.html")
})
// --- end --------------------------------
s.Group("/", func(group *ghttp.RouterGroup) {
group.Middleware(ghttp.MiddlewareHandlerResponse)
group.Bind(
compat.NewV1(),
hello.NewV1(),
)
})
s.Run()
return nil
},
}
)
然后服务自动重启,就可以访问 http://localhost:8000/apidoc
尝试 try it out
了。
另外值得一提的是:如果从 swagger-ui 下载最新版本,解压其中的 dist
到 serve/resource/public/resource/
目录下 ,比如 swagger-ui-5.11.2.dist
目录。
然后在 manifest/config/config.yaml
下配置 server:serverRoot
的值为 /resource/public/resource
,手动重启服务,就可以通过 http://localhost:8000/swagger-ui-5.11.2.dist/
来访问了。此时打开的是别的接口地址,只需要替换 resource/public/resource/swagger-ui-5.11.2.dist/swagger-initializer.js
中的 url
为 /api.json
即可。
虽然可以访问所有接口,但是其实,我们的接口都只是构造好了,还没有具体实现。 下面我们就开始实现具体的接口功能。
生成service
make service
该命令通过分析给定的logic
业务逻辑模块目录下的代码,自动生成service
目录接口代码。
所以现在我们开始尝试编写 logic
业务逻辑代码。
// /internal/logic/detectitem/detectitem.go
package detectitem
import (
"context"
v1 "demo/api/compat/v1"
"demo/internal/dao"
"demo/internal/service"
"github.com/gogf/gf/frame/g"
)
type (
sDetectItem struct{}
)
func New() *sDetectItem {
return &sDetectItem{}
}
func (s *sDetectItem) GetAll(ctx context.Context, in *v1.DetectItemGetAllReq) (out *v1.DetectItemGetAllRes, e error) {
out = &v1.DetectItemGetAllRes{}
e = dao.DetectItem.Ctx(ctx).Scan(&out.List, true)
return
}
func (s *sDetectItem) GetList(ctx context.Context, in *v1.DetectItemGetListReq) (out *v1.DetectItemGetListRes, e error) {
out = &v1.DetectItemGetListRes{}
out.Page = in.Page
out.Size = in.Size
orm := dao.DetectItem.Ctx(ctx)
e = orm.Page(in.Page, in.Size).ScanAndCount(&out.List, &out.Total, true)
return
}
func (s *sDetectItem) Search(ctx context.Context, in *v1.DetectItemSearchReq) (out *v1.DetectItemSearchRes, e error) {
out = &v1.DetectItemSearchRes{}
out.Page = in.Page
out.Size = in.Size
orm := dao.DetectItem.Ctx(ctx)
orm = orm.WhereLike(dao.DetectItem.Columns().Name, "%"+in.Name+"%")
e = orm.Page(in.Page, in.Size).ScanAndCount(&out.List, &out.Total, true)
return
}
func (s *sDetectItem) GetOne(ctx context.Context, in *v1.DetectItemGetOneReq) (out *v1.DetectItemGetOneRes, e error) {
out = &v1.DetectItemGetOneRes{}
if err := dao.DetectItem.Ctx(ctx).WherePri(in.Id).Scan(&out); err != nil {
return nil, err
}
return
}
func (s *sDetectItem) Create(ctx context.Context, in *v1.DetectItemCreateReq) (out *v1.DetectItemCreateRes, e error) {
lastInsertID, err := dao.DetectItem.Ctx(ctx).Data(in).InsertAndGetId()
if err != nil {
return out, err
}
out = &v1.DetectItemCreateRes{NewID: uint(lastInsertID)}
return
}
func (s *sDetectItem) Update(ctx context.Context, in *v1.DetectItemUpdateReq) (out *v1.DetectItemUpdateRes, e error) {
_, e = dao.DetectItem.Ctx(ctx).Data(in).
FieldsEx(dao.DetectItem.Columns().Id).
Where(dao.DetectItem.Columns().Id, in.Id).
Update()
return
}
func (s *sDetectItem) Delete(ctx context.Context, in *v1.DetectItemDeleteReq) (out *v1.DetectItemDeleteRes, e error) {
_, e = dao.DetectItem.Ctx(ctx).Where(g.Map{
dao.DetectItem.Columns().Id: in.Id,
}).Delete()
return
}
然后执行 make service
命令,即可生成 service
目录下的 detectitem.go
文件。
执行后,再修改一次 logic
文件,注册 service
,即添加 func init()
func init() {
service.RegisterDetectItem(New())
}
Visual Studio Code
可以安装插件 RunOnSave
随后配置插件,可以在修改 logic
(比如新增函数) 时自动更新 service
。:
"emeraldwalk.runonsave": {
"commands": [
{
"match": ".*logic.*go",
"isAsync": true,
"cmd": "gf gen service"
}
]
}
注意配置数据库 manifest/config/config.yaml
# https://goframe.org/pages/viewpage.action?pageId=1114245
database:
default:
link: "sqlite::@file(./resource/db.sqlite3)"
注意导入驱动 /main.go
// https://github.com/gogf/gf/blob/master/contrib/drivers/README.MD
import _ "github.com/gogf/gf/contrib/drivers/mysql/v2" // MySQL/MariaDB/TiDB
import _ "github.com/gogf/gf/contrib/drivers/sqlite/v2" // SQLite
import _ "github.com/gogf/gf/contrib/drivers/pgsql/v2" // PostgreSQL
import _ "github.com/gogf/gf/contrib/drivers/mssql/v2" // SQL Server
import _ "github.com/gogf/gf/contrib/drivers/oracle/v2" // Oracle
import _ "github.com/gogf/gf/contrib/drivers/clickhouse/v2" // ClickHouse
import _ "github.com/gogf/gf/contrib/drivers/dm/v2" // DM (达梦)
最后访问接口,发现接口均提示"未实现"。下面我们将 controller 和 service 对接上。
// internal/controller/compat/compat_v1_detect_item_create.go
func (c *ControllerV1) DetectItemCreate(ctx context.Context, req *v1.DetectItemCreateReq) (res *v1.DetectItemCreateRes, err error) {
res, err = service.DetectItem().Create(ctx, req)
return
}
// internal/controller/compat/compat_v1_detect_item_delete.go
func (c *ControllerV1) DetectItemDelete(ctx context.Context, req *v1.DetectItemDeleteReq) (res *v1.DetectItemDeleteRes, err error) {
res, err = service.DetectItem().Delete(ctx, req)
return
}
// internal/controller/compat/compat_v1_detect_item_get_all.go
func (c *ControllerV1) DetectItemGetAll(ctx context.Context, req *v1.DetectItemGetAllReq) (res *v1.DetectItemGetAllRes, err error) {
res, err = service.DetectItem().GetAll(ctx, req)
return
}
// internal/controller/compat/compat_v1_detect_item_get_list.go
func (c *ControllerV1) DetectItemGetList(ctx context.Context, req *v1.DetectItemGetListReq) (res *v1.DetectItemGetListRes, err error) {
res, err = service.DetectItem().GetList(ctx, req)
return
}
// internal/controller/compat/compat_v1_detect_item_get_one.go
func (c *ControllerV1) DetectItemGetOne(ctx context.Context, req *v1.DetectItemGetOneReq) (res *v1.DetectItemGetOneRes, err error) {
res, err = service.DetectItem().GetOne(ctx, req)
return
}
// internal/controller/compat/compat_v1_detect_item_search.go
func (c *ControllerV1) DetectItemSearch(ctx context.Context, req *v1.DetectItemSearchReq) (res *v1.DetectItemSearchRes, err error) {
res, err = service.DetectItem().Search(ctx, req)
return
}
// internal/controller/compat/compat_v1_detect_item_update.go
func (c *ControllerV1) DetectItemUpdate(ctx context.Context, req *v1.DetectItemUpdateReq) (res *v1.DetectItemUpdateRes, err error) {
res, err = service.DetectItem().Update(ctx, req)
return
}
其实 controller
本身是介于API接口
和 service服务
之间的存在,一个 controller
可能需要调用多个 service
才能完成接口功能。此处示例较为简单,所以不需要调用多个 service
。
上述代码,应该可以完成一个 基础的增删改查了。