Go项目结构整洁实现|GitHub 3.5k
一、前言
hi,大家好,这里是白泽。今天给大家分享一个GitHub 🌟 3.5k 的 Go项目:go-backend-clean-arch
https://github.com/amitshekhariitbhu/go-backend-clean-architecture
这个项目是一位老外写的,通过一个 HTTP demo 介绍了一个优雅的项目结构。
我也在b站出了一期30多分钟的视频,讲解了这个仓库,欢迎你的关注 📺 B站:白泽talk,qq交流群:622383022。

🌟 当然,如果您是一位 Go 学习的新手,您可以在我开源的学习仓库:https://github.com/BaiZe1998/go-learning 中,找到我往期翻译的英文书籍,或者Go学习路线。

二、项目架构

三、目录详解
.
├── Dockerfile # 镜像文件
├── api
│ ├── controller # 接口
│ ├── middleware # 中间件(JWT鉴权)
│ └── route # 路由绑定
├── bootstrap
│ ├── app.go # 核心类
│ ├── database.go # 数据库
│ └── env.go # 配置类
├── cmd
│ └── main.go # 启动命令
├── docker-compose.yaml
├── domain # 实例层
├── go.mod
├── go.sum
├── internal # 内部工具
│ └── tokenutil
├── mongo # mongodb
│ └── mongo.go
├── repository # 仓储层
└── usecase # 业务层
3.1 参数配置 & 项目启动
./cmd/main.go
type Application struct {Env *EnvMongo mongo.Client
}
type Env struct {AppEnv string `mapstructure:"APP_ENV"`ServerAddress string `mapstructure:"SERVER_ADDRESS"`ContextTimeout int `mapstructure:"CONTEXT_TIMEOUT"`DBHost string `mapstructure:"DB_HOST"`DBPort string `mapstructure:"DB_PORT"`...
}
func main() {// app 是整个应用的实例,管理生命周期中的重要资源app := bootstrap.App()// 配置变量env := app.Env// 数据库实例db := app.Mongo.Database(env.DBName)defer app.CloseDBConnection()
timeout := time.Duration(env.ContextTimeout) * time.Second// gin 实例创建gin := gin.Default()// 路由绑定route.Setup(env, timeout, db, gin)// 运行服务gin.Run(env.ServerAddress)
}
🌟 接下来的讲解将以登陆逻辑为例,讲解三层架构。
3.2 接口层
./api/controller/login_controller.go
LoginController 持有配置类,以及 LoginUsecase 接口(定义了业务层的行为)
// 业务层接口
type SignupUsecase interface {Create(c context.Context, user *User) errorGetUserByEmail(c context.Context, email string) (User, error)CreateAccessToken(user *User, secret string, expiry int) (accessToken string, err error)CreateRefreshToken(user *User, secret string, expiry int) (refreshToken string, err error)
}
type LoginController struct {LoginUsecase domain.LoginUsecaseEnv *bootstrap.Env
}
func (lc *LoginController) Login(c *gin.Context) {var request domain.LoginRequest
err := c.ShouldBind(&request)if err != nil {c.JSON(http.StatusBadRequest, domain.ErrorResponse{Message: err.Error()})return}
user, err := lc.LoginUsecase.GetUserByEmail(c, request.Email)if err != nil {c.JSON(http.StatusNotFound, domain.ErrorResponse{Message: "User not found with the given email"})return}
if bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(request.Password)) != nil {c.JSON(http.StatusUnauthorized, domain.ErrorResponse{Message: "Invalid credentials"})return}
accessToken, err := lc.LoginUsecase.CreateAccessToken(&user, lc.Env.AccessTokenSecret, lc.Env.AccessTokenExpiryHour)if err != nil {c.JSON(http.StatusInternalServerError, domain.ErrorResponse{Message: err.Error()})return}
refreshToken, err := lc.LoginUsecase.CreateRefreshToken(&user, lc.Env.RefreshTokenSecret, lc.Env.RefreshTokenExpiryHour)if err != nil {c.JSON(http.StatusInternalServerError, domain.ErrorResponse{Message: err.Error()})return}
loginResponse := domain.LoginResponse{AccessToken: accessToken,RefreshToken: refreshToken,}
c.JSON(http.StatusOK, loginResponse)
}
3.3 业务层
./usecase/login_usecase.go
loginUsecase 结构实现 LoginUsecase 接口,同时在 loginUsecase 结构中,持有了 UserRepository 接口(定义了仓储层的行为)。
// 数据防腐层接口
type UserRepository interface {Create(c context.Context, user *User) errorFetch(c context.Context) ([]User, error)GetByEmail(c context.Context, email string) (User, error)GetByID(c context.Context, id string) (User, error)
}
type loginUsecase struct {userRepository domain.UserRepositorycontextTimeout time.Duration
}
func NewLoginUsecase(userRepository domain.UserRepository, timeout time.Duration) domain.LoginUsecase {return &loginUsecase{userRepository: userRepository,contextTimeout: timeout,}
}
func (lu *loginUsecase) GetUserByEmail(c context.Context, email string) (domain.User, error) {ctx, cancel := context.WithTimeout(c, lu.contextTimeout)defer cancel()return lu.userRepository.GetByEmail(ctx, email)
}
func (lu *loginUsecase) CreateAccessToken(user *domain.User, secret string, expiry int) (accessToken string, err error) {return tokenutil.CreateAccessToken(user, secret, expiry)
}
func (lu *loginUsecase) CreateRefreshToken(user *domain.User, secret string, expiry int) (refreshToken string, err error) {return tokenutil.CreateRefreshToken(user, secret, expiry)
}
3.4 防腐层
./repository/user_repository.go
userRepository 结构实现了 UserRepository 接口,内部持有 mongo.Database 接口(定义数据层行为),以及 collection 实例的名称。
// 数据操作层接口
type Database interface {Collection(string) CollectionClient() Client
}
type userRepository struct {database mongo.Databasecollection string
}
func NewUserRepository(db mongo.Database, collection string) domain.UserRepository {return &userRepository{database: db,collection: collection,}
}
func (ur *userRepository) Create(c context.Context, user *domain.User) error {collection := ur.database.Collection(ur.collection)
_, err := collection.InsertOne(c, user)
return err
}
func (ur *userRepository) Fetch(c context.Context) ([]domain.User, error) {collection := ur.database.Collection(ur.collection)
opts := options.Find().SetProjection(bson.D{{Key: "password", Value: 0}})cursor, err := collection.Find(c, bson.D{}, opts)
if err != nil {return nil, err}
var users []domain.User
err = cursor.All(c, &users)if users == nil {return []domain.User{}, err}
return users, err
}
func (ur *userRepository) GetByEmail(c context.Context, email string) (domain.User, error) {collection := ur.database.Collection(ur.collection)var user domain.Usererr := collection.FindOne(c, bson.M{"email": email}).Decode(&user)return user, err
}
func (ur *userRepository) GetByID(c context.Context, id string) (domain.User, error) {collection := ur.database.Collection(ur.collection)
var user domain.User
idHex, err := primitive.ObjectIDFromHex(id)if err != nil {return user, err}
err = collection.FindOne(c, bson.M{"_id": idHex}).Decode(&user)return user, err
}
3.5 数据层
./mongo/mongo.go
实现了 mongo.Database 接口,通过 mongoDatabase 结构体的两个方法可以获取对应的 Client 实例和 Collection 实例,从而操作数据库。
type mongoDatabase struct {db *mongo.Database
}
func (md *mongoDatabase) Collection(colName string) Collection {collection := md.db.Collection(colName)return &mongoCollection{coll: collection}
}
func (md *mongoDatabase) Client() Client {client := md.db.Client()return &mongoClient{cl: client}
}
四、单例与封装
查看 ./cmd/main.go 的路由绑定逻辑:route.Setup(env, timeout, db, gin)。
func Setup(env *bootstrap.Env, timeout time.Duration, db mongo.Database, gin *gin.Engine) {publicRouter := gin.Group("")// All Public APIsNewSignupRouter(env, timeout, db, publicRouter)NewLoginRouter(env, timeout, db, publicRouter)NewRefreshTokenRouter(env, timeout, db, publicRouter)
protectedRouter := gin.Group("")// Middleware to verify AccessTokenprotectedRouter.Use(middleware.JwtAuthMiddleware(env.AccessTokenSecret))// All Private APIsNewProfileRouter(env, timeout, db, protectedRouter)NewTaskRouter(env, timeout, db, protectedRouter)
}
进一步查看 NewLoginRouter,会发现,在注册路由触发的 controller 方法的时候,已经将所需要的 db 创建出来,并且在数据层共享,同时防腐层、业务层、控制层的实例,在服务启动前创建,依次嵌套持有,因此所有的结构都是单例,且类似树形结构,依次串联。
func NewLoginRouter(env *bootstrap.Env, timeout time.Duration, db mongo.Database, group *gin.RouterGroup) {ur := repository.NewUserRepository(db, domain.CollectionUser)lc := &controller.LoginController{LoginUsecase: usecase.NewLoginUsecase(ur, timeout),Env: env,}group.POST("/login", lc.Login)
}
通过这种方式,实现了资源的约束,使得开发者无法跨模块调用实例,导致循环依赖等安全问题。
相关文章:
Go项目结构整洁实现|GitHub 3.5k
一、前言 hi,大家好,这里是白泽。今天给大家分享一个GitHub 🌟 3.5k 的 Go项目:go-backend-clean-arch https://github.com/amitshekhariitbhu/go-backend-clean-architecture 这个项目是一位老外写的,通过一个 HTT…...
Python读取PDF文字 去掉页眉页脚
使用PyMuPDF(即fitz)读取PDF中的text时,会把页码也读进来。所以,有时候就需要让程序忽略页眉和页脚,或者直接删除页眉和页脚。 根据fitz的文档:Page - PyMuPDF 1.24.0 documentation get_text的clip参数可…...
Linux:入门篇
文章目录 前言1. Linuxd的安装环境2.Linux的简单介绍2.1 新建目录2.2 新建文件 3.指令到底是什么?4.shell命令以及运行原理5.总结 前言 很多人对于Linux的学习总是感觉无法下手,不知道从何开始学习,相信这篇文章将会为你提供一个清晰的思路。…...
NSSCTF Round#20 Basic 真亦假,假亦真 CSDN_To_PDF V1.2 出题笔记 (附wp+源码)
真亦假,假亦真 简介:java伪造php一句话马。实则信息泄露一扫就出,flag在/flag里面。 题目描述:开开心心签个到吧,祝各位师傅们好运~ 静态flag:NSS{Checkin_h4v3_4_g00D_tINNe!} /路由显示 <?php e…...
处理关于 React lazy 白屏的两种方案
这篇文章是今天在阅读 React 官方文档的时候看到的一个关于 处理 lazy 体验问题的小技巧,在这里记录一下 当我们使用 React.lazy 去懒加载一个路由组件,在不做任何其他处理的情况下从其他页面首次进入到这个懒加载的路由页面时,肯定会出现白屏…...
Nginx 基础
文章目录 Nginx概念安装下载上传安装包执行准备条件指定安装位置编译和安装启动服务创建启动脚本 linux文件目录nginx运行原理nginx配置域名概念和原理域名配置 Nginx 概念 Nginx 是一个高性能的HTTP和反向代理web服务器,同时也提供了IMAP/POP3/SMTP服务。Nginx是…...
C++完美转发(适合小白)
我们知道,C中有左值引用和右值引用,首先我们要知道什么是左值什么是右值。 左值:表达式结束后依然存在的持久对象。左值可以出现在赋值语句的左边或右边。例如,变量和函数返回的引用都是左值。左值通常有持久的地址,可…...
如何创建自己的 Spring Boot Starter 并为其编写单元测试
当我们想要封装一些自定义功能给别人使用的时候,创建Spring Boot Starter的形式是最好的实现方式。如果您还不会构建自己的Spring Boot Starter的话,本文将带你一起创建一个自己的Spring Boot Starter。 快速入门 创建一个新的 Maven 项目。第三方封装的…...
C++ :STL中deque的原理
deque的结构类似于哈希表,使用一个指针数组存储固定大小的数组首地址,当数据分布不均匀时将指针数组内的数据进行偏移,桶不够用的时候会像vector一样扩容然后将之前数组中存储的指针拷贝过来,从原理可以看出deque的性能是非常高的…...
AttributeError: ‘Namespace‘ object has no attribute ‘EarlyStopping‘
报错原因 这个报错信息表明在Python脚本train.py中尝试访问命令行参数args.EarlyStopping时出错,具体错误是AttributeError: Namespace对象没有名为EarlyStopping的属性。 在Python的argparse模块中,当我们通过命令行传递参数并解析时,解析…...
深度学习pytorch——卷积神经网络(持续更新)
计算机如何解析图片? 在计算机的眼中,一张灰度图片,就是许多个数字组成的二维矩阵,每个数字就是此点的像素值(图-1)。在存储时,像素值通常位于[0, 255]区间,在深度学习中࿰…...
【edge浏览器无法登录某些网站,以及迅雷插件无法生效的解决办法】
edge浏览器无法登录某些网站,以及迅雷插件无法生效的解决办法 edge浏览器无法登录某些网站,但chrome浏览器可以登录浏览器插件无法使用,比如迅雷如果重装插件重装浏览器重装迅雷后仍然出现问题 edge浏览器无法登录某些网站,但chro…...
OpenHarmony无人机MAVSDK开源库适配方案分享
MAVSDK 是 PX4 开源团队贡献的基于 MavLink 通信协议的用于无人机应用开发的 SDK,支持多种语言如 C/C、python、Java 等。通常用于无人机间、地面站与通信设备的消息传输。 MAVLink 是一种非常轻量级的消息传递协议,用于与无人机(以及机载无…...
模型训练----parser.add_argument添加配置参数
现在需要配置参数来达到修改训练的方式,我现在需要新建一个参数来开关wandb的使用。 首先就是在def parse_option():函数里添加上你要使用的变量名 parser.add_argument("--open_wandb",type bool,defaultFalse,helpopen wandb) 到config文件里增加你的…...
数字未来:探索 Web3 的革命性潜力
在当今数字化的时代,Web3作为互联网的新兴范式正逐渐崭露头角,引发了广泛的关注和探讨。本文将深入探索数字未来中Web3所蕴含的革命性潜力,探讨其对社会、经济和技术的深远影响。 1. Web3:数字世界的下一个阶段 Web3是一个正在崛…...
群晖NAS使用Docker部署大语言模型Llama 2结合内网穿透实现公网访问本地GPT聊天服务
文章目录 1. 拉取相关的Docker镜像2. 运行Ollama 镜像3. 运行Chatbot Ollama镜像4. 本地访问5. 群晖安装Cpolar6. 配置公网地址7. 公网访问8. 固定公网地址 随着ChatGPT 和open Sora 的热度剧增,大语言模型时代,开启了AI新篇章,大语言模型的应用非常广泛,包括聊天机…...
[选型必备基础信息] 存储器
存储芯片根据断电后是否保留存储的信息可分为易失性存储芯片(RAM)和非易失性存储芯片(ROM)。 简单说,存储类IC分为 ROM和RAM ROM:EEPROM / Flash / eMMC RAM:SRAM/SDRAM/DDR2/DDR3/DDR4/DDR5…...
C++——C++11线程库
目录 一,线程库简介 二,线程库简单使用 2.1 传函数指针 编辑 2.2 传lamdba表达式 2.3 简单综合运用 2.4 线程函数参数 三,线程安全问题 3.1 为什么会有这个问题? 3.2 锁 3.2.1 互斥锁 3.2.2 递归锁 3.3 原子操作 3…...
机器学习 | 线性判别分析(Linear Discriminant Analysis)
1 机器学习中的建模 1.1 描述性建模 以方便的形式给出数据的主要特征,实质上是对数据的概括,以便在大量的或有噪声的数据中仍能观察到重要特征。重在认识数据的主要概貌,理解数据的重要特征。 Task:聚类分析,数据降…...
TypeScript-数组、函数类型
1.数组类型 1.1类型 方括号 let arry:number[][5,2,0,1,3,1,4] 1.2 数组泛型 let arry2:Array<number>[5,2,0,1,3,1,4] 1.3接口类型 interface makeArryRule{[index:number]:number }let arry3:makeArryRule[5,2,0,1,3,1,4] 1.4伪数组 说明: argument…...
AI Agent与Agentic AI:原理、应用、挑战与未来展望
文章目录 一、引言二、AI Agent与Agentic AI的兴起2.1 技术契机与生态成熟2.2 Agent的定义与特征2.3 Agent的发展历程 三、AI Agent的核心技术栈解密3.1 感知模块代码示例:使用Python和OpenCV进行图像识别 3.2 认知与决策模块代码示例:使用OpenAI GPT-3进…...
MongoDB学习和应用(高效的非关系型数据库)
一丶 MongoDB简介 对于社交类软件的功能,我们需要对它的功能特点进行分析: 数据量会随着用户数增大而增大读多写少价值较低非好友看不到其动态信息地理位置的查询… 针对以上特点进行分析各大存储工具: mysql:关系型数据库&am…...
(二)原型模式
原型的功能是将一个已经存在的对象作为源目标,其余对象都是通过这个源目标创建。发挥复制的作用就是原型模式的核心思想。 一、源型模式的定义 原型模式是指第二次创建对象可以通过复制已经存在的原型对象来实现,忽略对象创建过程中的其它细节。 📌 核心特点: 避免重复初…...
C++八股 —— 单例模式
文章目录 1. 基本概念2. 设计要点3. 实现方式4. 详解懒汉模式 1. 基本概念 线程安全(Thread Safety) 线程安全是指在多线程环境下,某个函数、类或代码片段能够被多个线程同时调用时,仍能保证数据的一致性和逻辑的正确性…...
稳定币的深度剖析与展望
一、引言 在当今数字化浪潮席卷全球的时代,加密货币作为一种新兴的金融现象,正以前所未有的速度改变着我们对传统货币和金融体系的认知。然而,加密货币市场的高度波动性却成为了其广泛应用和普及的一大障碍。在这样的背景下,稳定…...
学校时钟系统,标准考场时钟系统,AI亮相2025高考,赛思时钟系统为教育公平筑起“精准防线”
2025年#高考 将在近日拉开帷幕,#AI 监考一度冲上热搜。当AI深度融入高考,#时间同步 不再是辅助功能,而是决定AI监考系统成败的“生命线”。 AI亮相2025高考,40种异常行为0.5秒精准识别 2025年高考即将拉开帷幕,江西、…...
使用Matplotlib创建炫酷的3D散点图:数据可视化的新维度
文章目录 基础实现代码代码解析进阶技巧1. 自定义点的大小和颜色2. 添加图例和样式美化3. 真实数据应用示例实用技巧与注意事项完整示例(带样式)应用场景在数据科学和可视化领域,三维图形能为我们提供更丰富的数据洞察。本文将手把手教你如何使用Python的Matplotlib库创建引…...
JVM虚拟机:内存结构、垃圾回收、性能优化
1、JVM虚拟机的简介 Java 虚拟机(Java Virtual Machine 简称:JVM)是运行所有 Java 程序的抽象计算机,是 Java 语言的运行环境,实现了 Java 程序的跨平台特性。JVM 屏蔽了与具体操作系统平台相关的信息,使得 Java 程序只需生成在 JVM 上运行的目标代码(字节码),就可以…...
使用Spring AI和MCP协议构建图片搜索服务
目录 使用Spring AI和MCP协议构建图片搜索服务 引言 技术栈概览 项目架构设计 架构图 服务端开发 1. 创建Spring Boot项目 2. 实现图片搜索工具 3. 配置传输模式 Stdio模式(本地调用) SSE模式(远程调用) 4. 注册工具提…...
基于SpringBoot在线拍卖系统的设计和实现
摘 要 随着社会的发展,社会的各行各业都在利用信息化时代的优势。计算机的优势和普及使得各种信息系统的开发成为必需。 在线拍卖系统,主要的模块包括管理员;首页、个人中心、用户管理、商品类型管理、拍卖商品管理、历史竞拍管理、竞拍订单…...
