前端er Go-Frame 的学习笔记:实现 to-do 功能(一)

简介

个人写前端比较多,来学习一个 go-frame 框架

后端并不熟练,所以没有很细致的步骤,所以写作的顺序是偏前端思考,属于是想到哪儿就写到哪里了,更像是流水账

我的目标:做一个非常小的项目 todo-list

想了解并练习以下内容:

  • RESTFul 架构
  • go-frame 刚接触的学习,包括自动生成的命令, 根据 api 生成代码
  • 思考:实现一个功能 => 设计一个接口,从哪儿开始入手,我能想到的,大概就是:先分析这个接口的需求是什么,应该有哪些字段,接着定义表结构,之后实现增删改查等操作...

技术栈:前端采用 React19 ,或者 Vue3 ,随便,后端用 go-frame,数据库 MySql,(本地的,官方教程用的是 dockermysql,都可以,用本地的话,我觉得可以用一些可视化的软件比方说 navcat,比较方便而已)


开始!

先从后端数据库字段的设计开始


建立数据库,设计表

如果想设计一个软件,就要想有哪些功能,对于 todo 这样的小项目,功能也就如下几个

  • todo 的名称
  • todo 的状态:是否做完
  • 创建时间 & 更新时间
  • 先实现最简单的, 核心功能,之后有更复杂的功能再另说,因为可能要加新的字段之类的操作

好,有了功能之后,应该能映射出一些需要的字段了,也就是数据模型

根据上面的功能,我觉的应该有的字段: id, title, done, created_at,updated_at ,然后建一个表

然后发现,我还没有数据库,那先创建一个数据库

连接 mysql,输入密码

bash 复制代码
mysql -u root -p

然后创建一个数据库,发现我之前已经有 todo_app 的数据库了,那就创建一个叫 todo_app_go_frame 的数据库

sql 复制代码
CREATE DATABASE todo_app_go_frame CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;

切换数据库

sql 复制代码
USE todo_app_go_frame;

然后创建一个表

sql 复制代码
CREATE TABLE todo (
    id INT AUTO_INCREMENT PRIMARY KEY,        -- 主键,自增,用来唯一标识一条 todo
    title VARCHAR(255) NOT NULL,              -- 任务标题,最长 255 字符,必填
    done TINYINT(1) NOT NULL DEFAULT 0,        -- 是否完成:0 未完成,1 完成;默认未完成 数据库中并没有布尔类型,所以用TINYINT 最小整数来判断
    created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,           -- 创建时间,插入时自动填充
    updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP   -- 更新时间,更新时自动刷新
);

其中的创建时间和更新时间的解释如下图。

此时,表应该创建好了,用下面的命令查看一下

sql 复制代码
SHOW TABLES;
DESCRIBE todo;

至此,数据库,表结构算是建立完成了,接下来用 go-frame 脚手架把后端搭起来


后端 go-frame 脚手架

首先通过脚手架,搭建出一个空的壳子出来

这里用的是 gf 命令,就是 go-frame 的命令

bash 复制代码
gf init 项目名称 -u
gf init back-end -u

跑完之后,大概长这个样子,工程目录设计的话,官方也有解释,这里就不赘述了

此时运行 go run main.go 就能跑起来项目了!


生成 dao、do、entity

有了数据库,可以执行代码生成了!参考官方文档,来试一下

好的,果不其然报错了,来改一下数据库的名称,之后再试一下

哦!这次成功了,可以看到他生成了 4 个文件

其中的三个文件都有 Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. 的标记,说明他是自动生成的,每次生成都会被覆盖掉的

对于这个命令,官方的解释是

看这个样子,我只需要修改一个文件就够了,官方文档是这样说的:

ok,接着我们来写 crud 了!


编写接口定义CRUD

我还是结合官方文档的代码设计风格,gpt 来辅助

我们在 api 文件夹下,新增 todo 文件夹,然后新增 v1 文件夹

同样的,我们默认开始使用v1版本。使用版本号做为良好的开发习惯,有利于未来接口的兼容性维护。

尝试写了一个,感觉写这个也挺麻烦的。

接口定义中,使用g.Meta来管理接口的元数据信息,这些元数据信息通过标签的形式定义在g.Meta属性上。这里的元数据信息包括:path路由地址、method请求方式、tags接口分组(用于生成接口文档)、summary接口描述。这些元数据信息都是OpenAPIv3里面的东西,我们这里不做详细介绍,大家了解即可 原文链接
原文:只有返回的参数结构体中带有 json 标签,因为返回的数据往往需要转换为 json 格式给前端使用,通过 snake 的参数命名的方式更符合前端命名习惯 这个 snake 命名是什么? "snake 命名"就是 蛇形命名法(snake_case),在后端开发中非常常见

其他的接口,我就复制过来改吧改吧好了

好的,增加和删除都好做,来到了更新,那么就有 done 这个字段,我该如何定义呢?

看了一下官网的代码,这里有个 status 的状态似乎很符合 done 字段的逻辑,所以我照葫芦画瓢试一下。

这里为什么要用指针呢?gpt 给的例子很好理解

查询的方法也是照葫芦画瓢写的

go 复制代码
// 查询单个接口
type GetOneReq struct {
	g.Meta `path:"/todo/{id}" method:"get" tags:"todo" summary:"Get one todo"`
	Id     int64 `v:"required" dc:"todo id"`
}
type GetOneRes struct {
	*entity.Todo `dc:"todo"`
	// 这里的返回结果我们使用了*entity.User结构体,该结构是前面我们通过make dao命令生成的entity,该数据结构与数据表字段一一对应。
}

// 查询多个接口,加上了查询
type GetListReq struct {
	g.Meta `path:"/todo" method:"get" tags:"Todo" summary:"Get todo"`
	Title  *string `v:"length:1,10" dc:"todo title"`
	Done   *Status `v:"in:0,1" dc:"todo done"`
}
type GetListRes struct {
	List []*entity.Todo `json:"list" dc:"todo list"`
}

生成 controller 代码

又一个爽的地方来了,没错就是代码生成!

写好 api 之后,可以通过命令 make ctrl 生成基础的代码

不过增删改查,只是个模板,具体的实现需要我们接着手写。


完成接口逻辑实现

好,开始写创建接口,照葫芦画瓢

go 复制代码
func (c *ControllerV1) Create(ctx context.Context, req *v1.CreateReq) (res *v1.CreateRes, err error) {
	insertId, err := dao.Todo.Ctx(ctx).Data(do.Todo{
		Title: req.Title,
	}).InsertAndGetId()

	if err != nil {
		return nil, err
	}

	res = &v1.CreateRes{
		Id: insertId,
	}
	return
}

ok,每行拆开来学习一下

跳转官方内容,截取部分如下Create 实现方法中:

  • 我们通过dao.User通过dao组件操作user表。
  • 每个 dao 操作都需要传递ctx参数,因此我们通过 Ctx(ctx)方法创建一个 gdb.Model 对象,该对象是框架的模型对象,用于操作特定的数据表。
  • 通过 Data 传递需要写入数据表的数据,我们这里使用 do 转换模型对象输入我们的数据。do 转换模型会自动过滤 nil 数据,并在底层自动转换为对应的数据表字段类型。
  • 在绝大部分时候,我们都使用 do 转换模型来给数据库操作对象传递写入/更新参数、查询条件等数据。

通过 InsertAndGetId 方法将 Data 的参数写入数据库,并返回新创建的记录主键 id。

接着是强制错误处理

然后是我比较疑惑的地方,为什么 res 用的是指针呢?

  • 首先,代码生成的模板中,函数签名,返回的 res 就是指针

ok 当我写到删除的时候,又有了新的问题

还有一个 go 语法的问题,gpt 是这样回答我的

再来写更新接口,这个更新接口好像上面的增加和删除的写法的结合

首先创建数据结构,然后根据 id ,找到表中的数据,然后调用 update 更新数据(当然都是我猜的)

go 复制代码
func (c *ControllerV1) Update(ctx context.Context, req *v1.UpdateReq) (res *v1.UpdateRes, err error) {
	_, err = dao.Todo.Ctx(ctx).Data(do.Todo{
		Title: req.Title,
		Done:  req.Done,
	}).WherePri(req.Id).Update()
	return
}

然后来实现查找吧,查找分为单条查找和多条查找

单条查找的话,我按照我前面所学的内容,憋了一条出来,很明显不对, 好吧,方法都用错了,是 wherePri,看了眼官方的例子:

go 复制代码
func (c *ControllerV1) GetOne(ctx context.Context, req *v1.GetOneReq) (res *v1.GetOneRes, err error) {
	res = &v1.GetOneRes{}
	err = dao.Todo.Ctx(ctx).WherePri(req.Id).Scan(&res.Todo)
	return
}

我让 gpt 来解释了一下这个代码

非常困惑这个 scan 的方法,然后我问 gpt

嗯,gpt 回答的还算是我能理解的,又学到个新的 scan() 方法,不过应该是框架封装的。

然后实现返回列表的,有了查询单个的学习,那这个查询多个的,只是多加了 where 的条件,where 就是数据库的条件查询了,这个好理解,scan 也好理解。

go 复制代码
func (c *ControllerV1) GetList(ctx context.Context, req *v1.GetListReq) (res *v1.GetListRes, err error) {
	res = &v1.GetListRes{}
	err = dao.Todo.Ctx(ctx).Where(do.Todo{
		Title: req.Title,
		Done:  req.Done,
	}).Scan(&res.List)
	return
}

至此,所有的接口都实现的差不多啦!但是我还没测试,所以可能会有问题!


配置与路由

好,官网上让我们首先引入数据库驱动,我先不添加数据驱动,因为作为正常的新手,我现在更急迫的想测试一下接口写的咋样了?

所以先注册一下路由吧,然后重启一下项目

可以看到这些路由已经注册好了,现在开始访问一下!

哦吼,果然报错了,不错不错,好事情

问题:所以,我理解的是,在这个 go 项目启动的时候,是不会再启动的时候去链接数据库,只有在访问资源的时候,后端发现需要链接数据库的时候才去访问数据库?

gpt 给的答案:

好的,又学到了,延迟链接

ok,现在我来配好驱动和数据库的密码,加入驱动爆红

说明没安装驱动啊老弟,安装一下,有魔法的开魔法

bash 复制代码
 go get github.com/gogf/gf/contrib/drivers/mysql/v2

安装完之后就好了,不飘红了

如果只是安装驱动,没做配置项的话,报这个错误

来改一下密码


启动和测试接口

重启项目

可以了,那我们试着加一个数据进去

bash 复制代码
curl -X POST 'http://127.0.0.1:8000/todo' -d '{"title":"测试我的 go-frame 的新增接口"}'

之后返回的数据如下,看来是触碰到了那个校验哈 看一下他的返回,原来如此,是 titletm 长了。那我想把校验改的长一点,合理吧?

然后重启一下后端,再试一下

bash 复制代码
curl -X POST 'http://127.0.0.1:8000/todo' -d '{"title":"测试我的 go-frame 的新增接口"}'

ok 啊,终于添加进去了,用 navcat 看一下表里,也成功添加进去了!

拿浏览器来请求一下,不错也有数据了!

再来试试修改!

bash 复制代码
curl -X PUT 'http://127.0.0.1:8000/todo/1' -d '{"done":1}'

不错,也好使,再来试试删除!

bash 复制代码
curl -X DELETE 'http://127.0.0.1:8000/todo/1'


总结

后端,基本的操作已经过了一遍

在这个过程中,产生了一些疑问

  • todo 表,如果新增了字段,怎么处理?要不要再泡一下代码生成?
  • 什么场景需要创建一个新表?
  • 什么时候需要创建新的数据库?
  • 目前接口用的都是 v1的版本,我如何用 v2,怎么切换?

这些问题我都挺好奇的,不过就先到这里,下次把前端的东西搭起来

相关推荐
喵个咪34 分钟前
初学者导引:在 Go-Kratos 中用 go-crud 实现 Ent ORM CRUD 操作
后端·go
计算机毕设匠心工作室35 分钟前
【python大数据毕设实战】全国健康老龄化数据分析系统、Hadoop、计算机毕业设计、包括数据爬取、数据分析、数据可视化、机器学习
后端·python
脾气有点小暴37 分钟前
uniapp通用递进式步骤组件
前端·javascript·vue.js·uni-app·uniapp
问道飞鱼38 分钟前
【前端知识】从前端请求到后端返回:Gzip压缩全链路配置指南
前端·状态模式·gzip·请求头
小杨累了44 分钟前
CSS Keyframes 实现 Vue 无缝无限轮播
前端
v***870444 分钟前
Spring Boot实现多数据源连接和切换
spring boot·后端·oracle
哈哈哈笑什么44 分钟前
企业级追踪业务数据变动的通用组件
后端
小扎仙森1 小时前
html引导页
前端·html
稚辉君.MCA_P8_Java1 小时前
Gemini永久会员 go数组中最大异或值
数据结构·后端·算法·golang·哈希算法