当前位置: 首页 > news >正文

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。

image-20240401202825006

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

image-20240401222006030

二、项目架构

image-20240401202921385

三、目录详解

.
├── 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+源码)

真亦假&#xff0c;假亦真 简介&#xff1a;java伪造php一句话马。实则信息泄露一扫就出&#xff0c;flag在/flag里面。 题目描述&#xff1a;开开心心签个到吧&#xff0c;祝各位师傅们好运~ 静态flag&#xff1a;NSS{Checkin_h4v3_4_g00D_tINNe!} /路由显示 <?php e…...

处理关于 React lazy 白屏的两种方案

这篇文章是今天在阅读 React 官方文档的时候看到的一个关于 处理 lazy 体验问题的小技巧&#xff0c;在这里记录一下 当我们使用 React.lazy 去懒加载一个路由组件&#xff0c;在不做任何其他处理的情况下从其他页面首次进入到这个懒加载的路由页面时&#xff0c;肯定会出现白屏…...

Nginx 基础

文章目录 Nginx概念安装下载上传安装包执行准备条件指定安装位置编译和安装启动服务创建启动脚本 linux文件目录nginx运行原理nginx配置域名概念和原理域名配置 Nginx 概念 Nginx 是一个高性能的HTTP和反向代理web服务器&#xff0c;同时也提供了IMAP/POP3/SMTP服务。Nginx是…...

C++完美转发(适合小白)

我们知道&#xff0c;C中有左值引用和右值引用&#xff0c;首先我们要知道什么是左值什么是右值。 左值&#xff1a;表达式结束后依然存在的持久对象。左值可以出现在赋值语句的左边或右边。例如&#xff0c;变量和函数返回的引用都是左值。左值通常有持久的地址&#xff0c;可…...

如何创建自己的 Spring Boot Starter 并为其编写单元测试

当我们想要封装一些自定义功能给别人使用的时候&#xff0c;创建Spring Boot Starter的形式是最好的实现方式。如果您还不会构建自己的Spring Boot Starter的话&#xff0c;本文将带你一起创建一个自己的Spring Boot Starter。 快速入门 创建一个新的 Maven 项目。第三方封装的…...

C++ :STL中deque的原理

deque的结构类似于哈希表&#xff0c;使用一个指针数组存储固定大小的数组首地址&#xff0c;当数据分布不均匀时将指针数组内的数据进行偏移&#xff0c;桶不够用的时候会像vector一样扩容然后将之前数组中存储的指针拷贝过来&#xff0c;从原理可以看出deque的性能是非常高的…...

AttributeError: ‘Namespace‘ object has no attribute ‘EarlyStopping‘

报错原因 这个报错信息表明在Python脚本train.py中尝试访问命令行参数args.EarlyStopping时出错&#xff0c;具体错误是AttributeError: Namespace对象没有名为EarlyStopping的属性。 在Python的argparse模块中&#xff0c;当我们通过命令行传递参数并解析时&#xff0c;解析…...

深度学习pytorch——卷积神经网络(持续更新)

计算机如何解析图片&#xff1f; 在计算机的眼中&#xff0c;一张灰度图片&#xff0c;就是许多个数字组成的二维矩阵&#xff0c;每个数字就是此点的像素值&#xff08;图-1&#xff09;。在存储时&#xff0c;像素值通常位于[0, 255]区间&#xff0c;在深度学习中&#xff0…...

【edge浏览器无法登录某些网站,以及迅雷插件无法生效的解决办法】

edge浏览器无法登录某些网站&#xff0c;以及迅雷插件无法生效的解决办法 edge浏览器无法登录某些网站&#xff0c;但chrome浏览器可以登录浏览器插件无法使用&#xff0c;比如迅雷如果重装插件重装浏览器重装迅雷后仍然出现问题 edge浏览器无法登录某些网站&#xff0c;但chro…...

OpenHarmony无人机MAVSDK开源库适配方案分享

MAVSDK 是 PX4 开源团队贡献的基于 MavLink 通信协议的用于无人机应用开发的 SDK&#xff0c;支持多种语言如 C/C、python、Java 等。通常用于无人机间、地面站与通信设备的消息传输。 MAVLink 是一种非常轻量级的消息传递协议&#xff0c;用于与无人机&#xff08;以及机载无…...

模型训练----parser.add_argument添加配置参数

现在需要配置参数来达到修改训练的方式&#xff0c;我现在需要新建一个参数来开关wandb的使用。 首先就是在def parse_option():函数里添加上你要使用的变量名 parser.add_argument("--open_wandb",type bool,defaultFalse,helpopen wandb) 到config文件里增加你的…...

数字未来:探索 Web3 的革命性潜力

在当今数字化的时代&#xff0c;Web3作为互联网的新兴范式正逐渐崭露头角&#xff0c;引发了广泛的关注和探讨。本文将深入探索数字未来中Web3所蕴含的革命性潜力&#xff0c;探讨其对社会、经济和技术的深远影响。 1. Web3&#xff1a;数字世界的下一个阶段 Web3是一个正在崛…...

群晖NAS使用Docker部署大语言模型Llama 2结合内网穿透实现公网访问本地GPT聊天服务

文章目录 1. 拉取相关的Docker镜像2. 运行Ollama 镜像3. 运行Chatbot Ollama镜像4. 本地访问5. 群晖安装Cpolar6. 配置公网地址7. 公网访问8. 固定公网地址 随着ChatGPT 和open Sora 的热度剧增,大语言模型时代,开启了AI新篇章,大语言模型的应用非常广泛&#xff0c;包括聊天机…...

[选型必备基础信息] 存储器

存储芯片根据断电后是否保留存储的信息可分为易失性存储芯片&#xff08;RAM&#xff09;和非易失性存储芯片&#xff08;ROM&#xff09;。 简单说&#xff0c;存储类IC分为 ROM和RAM ROM&#xff1a;EEPROM / Flash / eMMC RAM&#xff1a;SRAM/SDRAM/DDR2/DDR3/DDR4/DDR5…...

C++——C++11线程库

目录 一&#xff0c;线程库简介 二&#xff0c;线程库简单使用 2.1 传函数指针 ​编辑 2.2 传lamdba表达式 2.3 简单综合运用 2.4 线程函数参数 三&#xff0c;线程安全问题 3.1 为什么会有这个问题&#xff1f; 3.2 锁 3.2.1 互斥锁 3.2.2 递归锁 3.3 原子操作 3…...

机器学习 | 线性判别分析(Linear Discriminant Analysis)

1 机器学习中的建模 1.1 描述性建模 以方便的形式给出数据的主要特征&#xff0c;实质上是对数据的概括&#xff0c;以便在大量的或有噪声的数据中仍能观察到重要特征。重在认识数据的主要概貌&#xff0c;理解数据的重要特征。 Task&#xff1a;聚类分析&#xff0c;数据降…...

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伪数组 说明&#xff1a; argument…...

Python爬虫实战:研究MechanicalSoup库相关技术

一、MechanicalSoup 库概述 1.1 库简介 MechanicalSoup 是一个 Python 库,专为自动化交互网站而设计。它结合了 requests 的 HTTP 请求能力和 BeautifulSoup 的 HTML 解析能力,提供了直观的 API,让我们可以像人类用户一样浏览网页、填写表单和提交请求。 1.2 主要功能特点…...

椭圆曲线密码学(ECC)

一、ECC算法概述 椭圆曲线密码学&#xff08;Elliptic Curve Cryptography&#xff09;是基于椭圆曲线数学理论的公钥密码系统&#xff0c;由Neal Koblitz和Victor Miller在1985年独立提出。相比RSA&#xff0c;ECC在相同安全强度下密钥更短&#xff08;256位ECC ≈ 3072位RSA…...

微软PowerBI考试 PL300-选择 Power BI 模型框架【附练习数据】

微软PowerBI考试 PL300-选择 Power BI 模型框架 20 多年来&#xff0c;Microsoft 持续对企业商业智能 (BI) 进行大量投资。 Azure Analysis Services (AAS) 和 SQL Server Analysis Services (SSAS) 基于无数企业使用的成熟的 BI 数据建模技术。 同样的技术也是 Power BI 数据…...

【SpringBoot】100、SpringBoot中使用自定义注解+AOP实现参数自动解密

在实际项目中,用户注册、登录、修改密码等操作,都涉及到参数传输安全问题。所以我们需要在前端对账户、密码等敏感信息加密传输,在后端接收到数据后能自动解密。 1、引入依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId...

聊聊 Pulsar:Producer 源码解析

一、前言 Apache Pulsar 是一个企业级的开源分布式消息传递平台&#xff0c;以其高性能、可扩展性和存储计算分离架构在消息队列和流处理领域独树一帜。在 Pulsar 的核心架构中&#xff0c;Producer&#xff08;生产者&#xff09; 是连接客户端应用与消息队列的第一步。生产者…...

【JavaWeb】Docker项目部署

引言 之前学习了Linux操作系统的常见命令&#xff0c;在Linux上安装软件&#xff0c;以及如何在Linux上部署一个单体项目&#xff0c;大多数同学都会有相同的感受&#xff0c;那就是麻烦。 核心体现在三点&#xff1a; 命令太多了&#xff0c;记不住 软件安装包名字复杂&…...

MySQL用户和授权

开放MySQL白名单 可以通过iptables-save命令确认对应客户端ip是否可以访问MySQL服务&#xff1a; test: # iptables-save | grep 3306 -A mp_srv_whitelist -s 172.16.14.102/32 -p tcp -m tcp --dport 3306 -j ACCEPT -A mp_srv_whitelist -s 172.16.4.16/32 -p tcp -m tcp -…...

基于TurtleBot3在Gazebo地图实现机器人远程控制

1. TurtleBot3环境配置 # 下载TurtleBot3核心包 mkdir -p ~/catkin_ws/src cd ~/catkin_ws/src git clone -b noetic-devel https://github.com/ROBOTIS-GIT/turtlebot3.git git clone -b noetic https://github.com/ROBOTIS-GIT/turtlebot3_msgs.git git clone -b noetic-dev…...

Mysql8 忘记密码重置,以及问题解决

1.使用免密登录 找到配置MySQL文件&#xff0c;我的文件路径是/etc/mysql/my.cnf&#xff0c;有的人的是/etc/mysql/mysql.cnf 在里最后加入 skip-grant-tables重启MySQL服务 service mysql restartShutting down MySQL… SUCCESS! Starting MySQL… SUCCESS! 重启成功 2.登…...

push [特殊字符] present

push &#x1f19a; present 前言present和dismiss特点代码演示 push和pop特点代码演示 前言 在 iOS 开发中&#xff0c;push 和 present 是两种不同的视图控制器切换方式&#xff0c;它们有着显著的区别。 present和dismiss 特点 在当前控制器上方新建视图层级需要手动调用…...