项目结构拆分 | 青训营

79 阅读4分钟

这是我的上一篇笔记:Gin框架小项目再实践 | 青训营 - 掘金 (juejin.cn)在上篇笔记中,我完成了一个任务清单的小项目,能够实现对任务的增删改查。但是项目的目录结构跟企业中实际开发的结构差别还是有点大,因此我决定学习并实践一下如何改造项目的目录结构。

前言

典型的Go语言后端项目目录结构通常包括以下内容:

  1. cmd: 项目的入口点,包含主要的应用程序逻辑,可以有多个子命令。
  2. internal: 内部包,仅在项目内部使用,用于隐藏实现细节,防止外部包的直接访问。
  3. pkg: 可导出的库代码,其他项目可以使用的公共功能。
  4. api: 存放API定义、协议文件(如Protobuf或OpenAPI规范)和相关文档。
  5. web: Web服务器和处理程序,包括路由、中间件、静态资源等。
  6. db: 数据库模型、迁移脚本和数据库访问代码。
  7. config: 配置文件和配置加载代码。
  8. middleware: 自定义中间件代码,如身份验证、日志记录等。
  9. service: 业务逻辑层,实现具体的业务功能。
  10. repository: 数据库访问层的接口和实现。
  11. utils: 工具函数、帮助函数和通用的辅助代码。
  12. tests: 单元测试、集成测试和端到端测试代码。
  13. scripts: 构建脚本、部署脚本和其他项目相关的实用脚本。
  14. docs: 项目文档,包括API文档、使用手册等。

这种结构可以根据项目的规模和需求进行调整,但它提供了一种良好的组织方式,有助于保持代码的清晰性和可维护性。 接下来,以我之前写的小项目为例,进行项目目录结构的改进。

目前的目录结构展示

使用tree /f命令查看目录结构如下:

image.png 接下来,将介绍各个层如何改造

Controller

用来控制路由进来以后,执行哪些函数。

首先,简历controller文件夹下的controller.go文件,之后将各个处理器封装成函数放进去。

将请求首页的代码

image.png 改为:

image.png

在controller.controller里编写具体的函数: image.png 同理,将post请求改为如下样式:

image.png

其余几个结构均需要改名,封装,与上面的步骤相似,成果如下:

image.png

dao层

即database access object 数据操作对象,我们将所有与数据库有关的代码移到这个目录下的mysql.go中: image.png main.go中初始化数据库的部分就变成了:

err := dao.InitMySQL()
dao.DB.AutoMigrate(&Todo{})
defer dao.Close() //程序退出关闭数据库连接

model

这里主要存放模型的定义,以及对这个模型的增删改查操作。

改造之后,应该把具体的增删改查的逻辑放在model层,而controller层应该只用调用model层的逻辑就可以了。如果逻辑比较复杂,controller层应该调用service层,service层再调用model层 即步骤应该是:

url -->controller -->service -->model
请求来了-->控制器-->业务层-->模型层的增删改查

以controller层中的GetTodoList函数为例,原本controller中代码如下:

func GetTodoList(c *gin.Context) {  
var todoList []Todo  
if err = DB.Find(&todoList).Error; err != nil {  
c.JSON(http.StatusOK, gin.H{"error": err.Error()})  
} else {  
c.JSON(http.StatusOK, todoList)  
}  
}

修改完代码如下:

func GetTodoList(c *gin.Context) {  
todoList, err := models.GetAllTodo()  
if err != nil {  
c.JSON(  
http.StatusOK,  
gin.H{"error": err.Error()},  
)  
} else {  
c.JSON(http.StatusOK, todoList)  
}  
}

同时,models.GetAllTodo()函数的定义如下:

func GetAllTodo() (todoList []*Todo, err error) {  
if err := dao.DB.Find(&todoList).Error; err != nil {  
return err  
  
}  
return nil, err  
}

同理,将controller层中的DeleteATodo函数改造成:

func DeleteATodo(c *gin.Context) {  
id, ok := c.Params.Get("id")  
if !ok {  
c.JSON(http.StatusOK, gin.H{"error": "无效的id"})  
return  
}  
if err := models.DeleteATodo(id); err != nil {  
c.JSON(http.StatusOK, gin.H{"error": err.Error()})  
} else {  
c.JSON(http.StatusOK, gin.H{id: "deleted"})  
}  
}

modeler层中的DeleteATodo函数如下:

func DeleteATodo(id string) (err error) {  
err = dao.DB.Where("id=?", id).Delete(&Todo{}).Error  
}

routers

main.go中还包含很多关于路由注册代码,其实这部分代码也是可以单独拿出来的。 此时main函数的代码如下:

image.png 非常的简洁 routers.go的代码如下: image.png

image.png

改造完的目录结构展示

image.png

image.png

最后项目也推送至了github上面,感兴趣的同学可以下载下来研究一下:yehowlong/bubble: Gin框架小项目再实践之任务清单增删改查 (github.com)

踩的一些小坑

老是容易把函数的返回值(第二个括号里的参数)写到函数的参数列表里面去(第一个括号里的参数)

参考

lesson27_企业级项目结构拆分_哔哩哔哩_bilibili